Skip to content

js-interpreter including ESM and in the npm ecosystem #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ test262-es5-tests/

test262.js
/compiler.jar

interpreter-esm.js
acorn-esm.js
27 changes: 19 additions & 8 deletions acorn.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,25 @@
// [dammit]: acorn_loose.js
// [walk]: util/walk.js

(function(root, mod) {
if (typeof exports === "object" && typeof module === "object") return mod(exports); // CommonJS
if (typeof define === "function" && define.amd) return define(["exports"], mod); // AMD
mod(root.acorn || (root.acorn = {})); // Plain browser env
})((typeof globalThis === 'undefined') ? this || window : globalThis, function(exports) {
var parse
var version

(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS. The commonJS module will export 'parse' and 'version'
factory(exports);
} else if (typeof asEsm !== 'undefined'){
// ESM
factory({})
} else {
// Browser globals. In this case, only window.acorn will be set,
// which will contain 'parse' and 'version'
factory(root.acorn || (root.acorn = {}));
}
}(typeof self !== 'undefined' ? self : this, function (exports) {
"use strict";

exports.version = "0.5.0";
exports.version = version = "0.5.0";
// Plus additional edits marked with 'JS-Interpreter change' comments.

// JS-Interpreter change:
Expand Down Expand Up @@ -73,7 +84,7 @@
* @param {Object=} opts
* @returns
*/
exports.parse = function(inpt, opts) {
exports.parse = parse = function(inpt, opts) {
input = String(inpt);
inputLen = input.length;
setOptions(opts);
Expand Down Expand Up @@ -2280,4 +2291,4 @@
return finishNode(node, "Identifier");
}

});
}))
25 changes: 25 additions & 0 deletions createEsmFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const fs = require('fs');

// File paths and content to add
const filesToModify = [
{ original: 'interpreter.js', new: 'interpreter-esm.js', insertionAnchor: 'var Interpreter', contentToAdd: "import { parse, version } from './acorn-esm.js';\nvar asEsm = true;\nexport { Interpreter, parse, version as acornVersion };\n\n" },
{ original: 'acorn.js', new: 'acorn-esm.js', insertionAnchor: 'var version', contentToAdd: "export { parse, version };\nvar asEsm = true;\n\n" }
];

// Loop through each file to modify
filesToModify.forEach(file => {
const originalContent = fs.readFileSync(file.original, 'utf8');
const insertionIndex = originalContent.indexOf(file.insertionAnchor);

if (insertionIndex !== -1) {
const insertionPoint = insertionIndex + file.insertionAnchor.length;
const newContent = originalContent.substring(0, insertionPoint) + '\n' + // Add newline character
file.contentToAdd +
originalContent.substring(insertionPoint);

fs.writeFileSync(file.new, newContent);
console.log(`Modified ${file.original} and saved as ${file.new}`);
} else {
console.error(`Insertion anchor not found in ${file.original}`);
}
});
112 changes: 112 additions & 0 deletions index-esm.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS-Interpreter Demo</title>
<link href="demos/style.css" rel="stylesheet" type="text/css">

<script type="module">

import { Interpreter, parse, acornVersion } from './interpreter-esm.js'

console.log('Acorn version:', acornVersion)
var myInterpreter;
function initAlert(interpreter, globalObject) {
var wrapper = function alert(text) {
return window.alert(arguments.length ? text : '');
};
interpreter.setProperty(globalObject, 'alert',
interpreter.createNativeFunction(wrapper));
}

function parseButton() {
var code = document.getElementById('code').value;
myInterpreter = new Interpreter(code, initAlert);
disable('');
}
window.parseButton = parseButton


function stepButton() {
var stack = myInterpreter.getStateStack();
if (stack.length) {
var node = stack[stack.length - 1].node;
var start = node.start;
var end = node.end;
} else {
var start = 0;
var end = 0;
}
createSelection(start, end);
try {
var ok = myInterpreter.step();
} finally {
if (!ok) {
disable('disabled');
}
}
}
window.stepButton = stepButton

function runButton() {
disable('disabled');
if (myInterpreter.run()) {
// Async function hit. There's more code to run.
setTimeout(runButton, 100);
}
}
window.runButton = runButton

function disable(disabled) {
document.getElementById('stepButton').disabled = disabled;
document.getElementById('runButton').disabled = disabled;
}

function createSelection(start, end) {
var field = document.getElementById('code');
if (field.createTextRange) {
var selRange = field.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
} else if (field.setSelectionRange) {
field.setSelectionRange(start, end);
} else if (field.selectionStart) {
field.selectionStart = start;
field.selectionEnd = end;
}
field.focus();
}
</script>
</head>
<body>
<h1>JS-Interpreter Demo</h1>

<p>Enter JavaScript code below, then click <em>Parse</em>. To execute, either
click <em>Step</em> repeatedly, or click <em>Run</em> once.
Open your browser's console for errors.</p>
<p><textarea id="code" spellcheck="false">
var result = [];
function fibonacci(n, output) {
var a = 1, b = 1, sum;
for (var i = 0; i &lt; n; i++) {
output.push(a);
sum = a + b;
a = b;
b = sum;
}
}
fibonacci(16, result);
alert(result.join(', '));
</textarea><br>
<button onclick="parseButton()">Parse</button>
<button onclick="stepButton()" id="stepButton" disabled="disabled">Step</button>
<button onclick="runButton()" id="runButton" disabled="disabled">Run</button>
</p>

<p>Read the <a href="docs.html">JS-Interpreter documentation</a>.</p>
<p>Get the <a href="https://github.com/NeilFraser/JS-Interpreter">source code</a>.</p>

</body>
</html>
55 changes: 39 additions & 16 deletions interpreter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@
* SPDX-License-Identifier: Apache-2.0
*/

var Interpreter

(function (root, factory) {
if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
//
// A one-liner to test commonJS is to run node and feed this to it:
// int = require('./interpreter.js');i = new int.Interpreter('a = new String(); 4+4;');i.run();i.value;
var acorn = require('./acorn.js')
exports.parse = acorn.parse
exports.acornVersion = acorn.version
factory(exports);
} else if (typeof asEsm !== 'undefined'){
// ESM
// No need to define `parse`, since it will be defined in an injected line
factory({ parse, acornVersion: version })
} else {
// Browser globals
// if (!root.acorn) throw new Error('Acorn needs to be in the `window` namespace')
factory(root);
}
}(typeof self !== 'undefined' ? self : this, function (exports) {

/**
* @fileoverview Interpreting JavaScript in JavaScript.
* @author [email protected] (Neil Fraser)
Expand All @@ -18,7 +41,7 @@
* global scope object.
* @constructor
*/
var Interpreter = function(code, opt_initFunc) {
Interpreter = function(code, opt_initFunc) {
if (typeof code === 'string') {
code = this.parse_(code, 'code');
}
Expand Down Expand Up @@ -200,13 +223,6 @@ Interpreter.vm = null;
*/
Interpreter.currentInterpreter_ = null;

/**
* The global object. Ideally use `globalThis`. Failing that try `this` or
* `window`. Other options to consider are `self` and `global`.
* Same logic as in Acorn.
*/
Interpreter.nativeGlobal =
(typeof globalThis === 'undefined') ? this || window : globalThis;

/**
* Code for executing regular expressions in a thread.
Expand Down Expand Up @@ -364,7 +380,7 @@ Interpreter.prototype.parse_ = function(code, sourceFile) {
options[name] = Interpreter.PARSE_OPTIONS[name];
}
options.sourceFile = sourceFile;
return Interpreter.nativeGlobal.acorn.parse(code, options);
return exports.parse(code, options);
};

/**
Expand Down Expand Up @@ -1557,8 +1573,9 @@ Interpreter.prototype.initString = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// String constructor.
var OriginalString = String
wrapper = function String(value) {
value = arguments.length ? Interpreter.nativeGlobal.String(value) : '';
value = arguments.length ? OriginalString(value) : '';
if (thisInterpreter.calledWithNew()) {
// Called as `new String()`.
this.data = value;
Expand Down Expand Up @@ -1803,8 +1820,9 @@ Interpreter.prototype.initBoolean = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Boolean constructor.
var OriginalBoolean = Boolean
wrapper = function Boolean(value) {
value = Interpreter.nativeGlobal.Boolean(value);
value = OriginalBoolean(value);
if (thisInterpreter.calledWithNew()) {
// Called as `new Boolean()`.
this.data = value;
Expand All @@ -1827,8 +1845,9 @@ Interpreter.prototype.initNumber = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Number constructor.
var OriginalNumber = Number
wrapper = function Number(value) {
value = arguments.length ? Interpreter.nativeGlobal.Number(value) : 0;
value = arguments.length ? OriginalNumber(value) : 0;
if (thisInterpreter.calledWithNew()) {
// Called as `new Number()`.
this.data = value;
Expand Down Expand Up @@ -1911,17 +1930,18 @@ Interpreter.prototype.initNumber = function(globalObject) {
Interpreter.prototype.initDate = function(globalObject) {
var thisInterpreter = this;
var wrapper;
var OriginalDate = Date
// Date constructor.
wrapper = function Date(_value, var_args) {
if (!thisInterpreter.calledWithNew()) {
// Called as `Date()`.
// Calling Date() as a function returns a string, no arguments are heeded.
return Interpreter.nativeGlobal.Date();
return OriginalDate();
}
// Called as `new Date(...)`.
var args = [null].concat(Array.from(arguments));
this.data = new (Function.prototype.bind.apply(
Interpreter.nativeGlobal.Date, args));
OriginalDate, args));
return this;
};
this.DATE = this.createNativeFunction(wrapper, true);
Expand Down Expand Up @@ -1990,6 +2010,7 @@ Interpreter.prototype.initRegExp = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// RegExp constructor.
var OriginalRegExp = RegExp
wrapper = function RegExp(pattern, flags) {
if (thisInterpreter.calledWithNew()) {
// Called as `new RegExp()`.
Expand All @@ -2011,7 +2032,7 @@ Interpreter.prototype.initRegExp = function(globalObject) {
'Invalid regexp flag: ' + flags);
}
try {
var nativeRegExp = new Interpreter.nativeGlobal.RegExp(pattern, flags)
var nativeRegExp = new OriginalRegExp(pattern, flags)
} catch (e) {
// Throws if flags are repeated.
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, e.message);
Expand Down Expand Up @@ -4803,7 +4824,7 @@ Interpreter.prototype['stepWhileStatement'] =

// Preserve top-level API functions from being pruned/renamed by JS compilers.
// Add others as needed.
Interpreter.nativeGlobal['Interpreter'] = Interpreter;
exports.Interpreter = Interpreter;
Interpreter.prototype['step'] = Interpreter.prototype.step;
Interpreter.prototype['run'] = Interpreter.prototype.run;
Interpreter.prototype['appendCode'] = Interpreter.prototype.appendCode;
Expand All @@ -4824,3 +4845,5 @@ Interpreter.prototype['getStateStack'] = Interpreter.prototype.getStateStack;
Interpreter.prototype['setStateStack'] = Interpreter.prototype.setStateStack;
Interpreter['VALUE_IN_DESCRIPTOR'] = Interpreter.VALUE_IN_DESCRIPTOR;
Interpreter['Status'] = Interpreter.Status;
}))

25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "js-interpreter-esm",
"version": "1.0.3",
"description": "Neil Fraser's official JS-Interpreter",
"main": "interpreter.js",
"module": "interpreter-esm.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"postinstall": "node createEsmFiles.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/NeilFraser/JS-Interpreter.git"
},
"keywords": [
"JS-Interpreter",
"acorn"
],
"author": "Neil Fraser",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/NeilFraser/JS-Interpreter/issues"
},
"homepage": "https://github.com/NeilFraser/JS-Interpreter#readme"
}