chore: initial

This commit is contained in:
cha0s 2022-02-27 07:32:34 -06:00
commit d4e3ead74d
27 changed files with 7373 additions and 0 deletions

116
.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

23
README.md Normal file
View File

@ -0,0 +1,23 @@
<div align="center">
<h1>Babel<strong>er</strong></h1>
<p>
Babeler takes Babel AST and brings it to life! Ever wanted to take your Babel AST and actually
interpret and execute it? Well, now you can.
</p>
</div>
## Table of Contents
1. [Install](#install)
2. [Introduction](#introduction)
## Install
```
npm install babeler
```
## Introduction
TODO

View File

@ -0,0 +1,7 @@
const config = require('@flecks/core/build/.eslint.defaults.js');
config.rules['babel/no-unused-expressions'] = 0;
config.rules['import/no-extraneous-dependencies'] = 0;
module.exports = config;

3
build/flecks.yml Normal file
View File

@ -0,0 +1,3 @@
'@flecks/core': {}
'@flecks/fleck': {}

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "babeler",
"version": "1.0.0",
"description": "Babeler interprets Babel AST",
"main": "index.js",
"author": "cha0s",
"license": "MIT",
"scripts": {
"build": "flecks build",
"lint": "flecks lint",
"test": "flecks test"
},
"files": [
"index.js",
"index.js.map"
],
"dependencies": {
"@babel/types": "^7.17.0"
},
"devDependencies": {
"@babel/parser": "^7.17.3",
"@flecks/core": "^1.0.0",
"@flecks/fleck": "^1.0.0",
"chai": "^4.3.6"
}
}

1
src/index.js Normal file
View File

@ -0,0 +1 @@
export {default as Sandbox, default} from './sandbox';

822
src/sandbox.js Normal file
View File

@ -0,0 +1,822 @@
import * as types from '@babel/types';
import Scope from './scope';
export default class Sandbox {
constructor(ast, context) {
this.ast = ast;
this.parents = new WeakMap();
this.scopes = new WeakMap();
this.context = context;
this.reset();
}
get context() {
return this.rootScope?.context || {};
}
set context(context) {
const {rootScope} = this;
if (rootScope) {
rootScope.context = context;
}
else {
this.setNodeScope(this.ast, new Scope(context));
}
}
destructureArray(id, init, scope) {
this.setNextScope(id, scope);
const {elements} = id;
for (let i = 0; i < elements.length; ++i) {
const element = elements[i];
if (null === element) {
// eslint-disable-next-line no-continue
continue;
}
this.setNextScope(element, scope);
if (types.isIdentifier(element)) {
scope.allocate(element.name, init[i]);
}
else {
// eslint-disable-next-line no-console
console.error("destructureArray(): Can't handle type", element.type);
return undefined;
}
}
return undefined;
}
destructureObject(id, init, scope) {
this.setNextScope(id, scope);
const {properties} = id;
const promises = [];
for (let i = 0; i < properties.length; ++i) {
const property = properties[i];
this.setNextScope(property, scope);
const k = property.computed ? this.evaluate(property.key) : {value: property.key.name};
if (k.async) {
promises.push(Promise.resolve(k.value).then((k) => {
if (types.isIdentifier(property.value)) {
const {name} = property.value;
scope.allocate(name, init[k]);
return undefined;
}
return this.destructureObject(property.value, init[k], scope);
}));
// eslint-disable-next-line no-continue
continue;
}
if (types.isIdentifier(property.value)) {
scope.allocate(property.value.name, init[k.value]);
}
else {
const promiseOrVoid = this.destructureObject(property.value, init[k.value], scope);
if (promiseOrVoid) {
promises.push(promiseOrVoid);
}
}
}
return promises.length > 0 ? Promise.all(promises) : undefined;
}
evaluate(node) {
this.setNextScope(node);
const {type} = node;
let evaluator = `evaluate${type}`;
if (!this[evaluator]) {
const keys = types.ALIAS_KEYS[type];
for (let i = keys.length - 1; i >= 0; --i) {
// eslint-disable-next-line no-cond-assign
if (this[evaluator = `evaluate${keys[i]}`]) {
break;
}
}
}
return this[evaluator]
? this[evaluator](node)
// eslint-disable-next-line no-console
: console.error("evaluate(): Can't handle", node.type);
}
evaluateArrayExpression(node) {
const elements = [];
let isAsync = false;
for (let i = 0; i < node.elements.length; i++) {
const {async, value} = this.evaluate(node.elements[i]);
// eslint-disable-next-line no-bitwise
isAsync |= async;
elements.push(value);
}
return {
async: !!isAsync,
value: isAsync ? Promise.all(elements) : elements,
};
}
evaluateAssignmentExpression(node) {
const {operator, left} = node;
const scope = this.nodeScope(node);
const right = this.evaluate(node.right);
if (!types.isMemberExpression(left)) {
const assign = (value) => {
switch (operator) {
/* eslint-disable no-multi-spaces, switch-colon-spacing */
case '=' : return scope.set(left.name, value);
case '+=' : return scope.set(left.name, scope.get(left.name) + value);
case '-=' : return scope.set(left.name, scope.get(left.name) - value);
case '*=' : return scope.set(left.name, scope.get(left.name) * value);
case '/=' : return scope.set(left.name, scope.get(left.name) / value);
case '%=' : return scope.set(left.name, scope.get(left.name) % value);
case '**=' : return scope.set(left.name, scope.get(left.name) ** value);
/* eslint-disable no-bitwise */
case '<<=' : return scope.set(left.name, scope.get(left.name) << value);
case '>>=' : return scope.set(left.name, scope.get(left.name) >> value);
case '>>>=': return scope.set(left.name, scope.get(left.name) >>> value);
case '|=' : return scope.set(left.name, scope.get(left.name) | value);
case '^=' : return scope.set(left.name, scope.get(left.name) ^ value);
case '&=' : return scope.set(left.name, scope.get(left.name) & value);
/* eslint-enable no-bitwise, no-multi-spaces */
case '||=' : return scope.set(left.name, scope.get(left.name) || value);
case '&&=' : return scope.set(left.name, scope.get(left.name) && value);
case '??=' : {
/* eslint-enable switch-colon-spacing */
const l = scope.get(left.name);
return scope.set(left.name, (l === null || l === undefined) ? value : l);
}
default:
// eslint-disable-next-line no-console
console.error("evaluateAssignmentExpression(): Can't handle operator", node.operator);
return undefined;
}
};
if (right.async) {
return {
async: true,
value: Promise.resolve(right.value).then(assign),
};
}
return {value: assign(right.value)};
}
const {
computed,
object,
optional,
property,
} = left;
const memberAssign = (O, P, value) => {
if (optional && !O) {
return undefined;
}
switch (operator) {
// eslint-disable-next-line max-len
/* eslint-disable no-param-reassign, no-return-assign, no-multi-spaces, switch-colon-spacing */
case '=' : return O[P] = value;
case '+=' : return O[P] += value;
case '-=' : return O[P] -= value;
case '*=' : return O[P] *= value;
case '/=' : return O[P] /= value;
case '%=' : return O[P] %= value;
case '**=' : return O[P] **= value;
/* eslint-disable no-bitwise */
case '<<=' : return O[P] <<= value;
case '>>=' : return O[P] >>= value;
case '>>>=': return O[P] >>>= value;
case '|=' : return O[P] |= value;
case '^=' : return O[P] ^= value;
case '&=' : return O[P] &= value;
/* eslint-enable no-bitwise */
case '||=' : return O[P] ||= value;
case '&&=' : return O[P] &&= value;
case '??=' : {
return O[P] = (O[P] === null || O[P] === undefined) ? value : O[P];
/* eslint-enable no-param-reassign, no-return-assign */
}
default:
// eslint-disable-next-line no-console
console.error("evaluateAssignmentExpression(): Can't handle operator", node.operator);
return undefined;
}
};
const makeAsync = (O, P, value) => (
Promise.all([O, P, value]).then(([O, P, value]) => memberAssign(O, P, value))
);
const O = this.evaluate(object);
const P = computed ? this.evaluate(property) : {value: property.name};
// eslint-disable-next-line no-bitwise
if (right.async | O.async | P.async) {
return {
async: true,
value: makeAsync(O.value, P.value, right.value),
};
}
return {value: memberAssign(O.value, P.value, right.value)};
}
evaluateAwaitExpression({argument}) {
const {value} = this.evaluate(argument);
return {
async: true,
value: value instanceof Promise ? value : Promise.resolve(value),
};
}
evaluateBinaryExpression(node) {
const binary = (left, right) => {
switch (node.operator) {
/* eslint-disable no-multi-spaces, switch-colon-spacing */
case '+' : return left + right;
case '-' : return left - right;
case '/' : return left / right;
case '%' : return left % right;
case '*' : return left * right;
case '>' : return left > right;
case '<' : return left < right;
case 'in' : return left in right;
case '>=' : return left >= right;
case '<=' : return left <= right;
case '**' : return left ** right;
case '===': return left === right;
case '!==': return left !== right;
/* eslint-disable no-bitwise */
case '^' : return left ^ right;
case '&' : return left & right;
case '|' : return left | right;
case '>>' : return left >> right;
case '<<' : return left << right;
case '>>>': return left >>> right;
/* eslint-enable no-bitwise */
/* eslint-disable eqeqeq */
case '==' : return left == right;
case '!=' : return left != right;
/* eslint-enable eqeqeq, no-multi-spaces, switch-colon-spacing */
case 'instanceof': return left instanceof right;
default:
// eslint-disable-next-line no-console
console.error("evaluateBinaryExpression(): Can't handle operator", node.operator);
return undefined;
}
};
const left = this.evaluate(node.left);
const right = this.evaluate(node.right);
if (left.async || right.async) {
return {
async: true,
value: Promise
.all([left.value, right.value])
.then(([left, right]) => binary(left, right)),
};
}
return {value: binary(left.value, right.value)};
}
evaluateCallExpression(node) {
let asyncArgs = false;
const args = [];
for (let i = 0; i < node.arguments.length; i++) {
const arg = node.arguments[i];
this.setNodeScope(arg, this.nodeScope(node));
const {async, value} = this.evaluate(arg);
// eslint-disable-next-line no-bitwise
asyncArgs |= async;
args.push(value);
}
const {callee, optional: callOptional} = node;
/* eslint-disable switch-colon-spacing */
const invoke = (fn, holder, args) => {
if (callOptional && !fn) {
return undefined;
}
return this.constructor.fastCall(fn, holder, args);
};
/* eslint-enable switch-colon-spacing */
if (!types.isMemberExpression(callee)) {
const {async, value} = this.evaluate(callee);
if (asyncArgs || async) {
return {
async: true,
value: Promise
.all([value, Promise.all(args)])
.then(([callee, args]) => invoke(callee, undefined, args)),
};
}
return {value: invoke(value, undefined, args)};
}
const {
computed,
object,
optional: memberOptional,
property,
} = callee;
this.setNextScope(callee);
const O = this.evaluate(object);
const P = computed ? this.evaluate(property) : {value: property.name};
if (asyncArgs || O.async || P.async) {
return {
async: true,
value: Promise
.all([O.value, P.value, Promise.all(args)])
.then(([O, P, args]) => invoke(memberOptional ? O?.[P] : O[P], O, args)),
};
}
return {value: invoke(memberOptional ? O.value?.[P.value] : O.value[P.value], O.value, args)};
}
evaluateConditionalExpression(node) {
const test = this.evaluate(node.test);
if (test.async) {
return {
async: true,
value: Promise.resolve(test.value)
.then((test) => this.evaluate(test ? node.consequent : node.alternate).value),
};
}
return this.evaluate(test.value ? node.consequent : node.alternate);
}
// eslint-disable-next-line class-methods-use-this
evaluateDirectiveLiteral({value}) {
return {value};
}
evaluateIdentifier(node) {
const scope = this.nodeScope(node);
return {value: scope.get(node.name)};
}
// eslint-disable-next-line class-methods-use-this
evaluateLiteral({value}) {
return {value};
}
evaluateLogicalExpression(node) {
const logic = (left, right) => {
switch (node.operator) {
case '||': return left || right;
case '&&': return left && right;
case '??': return (left === null || left === undefined) ? right : left;
default:
// eslint-disable-next-line no-console
console.error("evaluateLogicalExpression(): Can't handle operator", node.operator);
return undefined;
}
};
const left = this.evaluate(node.left);
const right = this.evaluate(node.right);
if (left.async || right.async) {
return {
async: true,
value: Promise
.all([left.value, right.value])
.then(([left, right]) => logic(left, right)),
};
}
return {value: logic(left.value, right.value)};
}
evaluateMemberExpression({
computed,
object,
optional,
property,
}) {
const member = (O, P) => (optional ? O?.[P] : O[P]);
const O = this.evaluate(object);
const P = computed ? this.evaluate(property) : {value: property.name};
if (O.async || P.async) {
return {
async: true,
value: Promise.all([O.value, P.value]).then(([O, P]) => member(O, P)),
};
}
return {value: member(O.value, P.value)};
}
evaluateObjectExpression(node) {
const {properties} = node;
let isAsync = false;
const entries = [];
for (let i = 0; i < properties.length; i++) {
if (types.isObjectProperty(properties[i])) {
this.setNodeScope(properties[i], this.nodeScope(node));
this.setNextScope(properties[i]);
const {computed, key, value} = properties[i];
let k;
if (computed) {
k = this.evaluate(key);
}
else if (types.isIdentifier(key)) {
k = {value: key.name};
}
else if (types.isStringLiteral(key)) {
k = {value: key.value};
}
else {
// eslint-disable-next-line no-console
console.error("evaluateObjectExpression(): Can't handle key type", key.type);
k = {value: undefined};
}
const v = this.evaluate(value);
// eslint-disable-next-line no-bitwise
isAsync |= k.async | v.async;
if (k.async || v.async) {
entries.push(Promise.all([k.value, v.value]));
}
else {
entries.push([k.value, v.value]);
}
}
if (types.isSpreadElement(properties[i])) {
const {argument} = properties[i];
const spreading = this.evaluate(argument);
// eslint-disable-next-line no-bitwise
isAsync |= spreading.async;
if (spreading.async) {
entries.push(Promise.resolve(spreading.value).then((spreading) => {
const entries = [];
const keys = Object.keys(spreading);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
entries.push([key, spreading[key]]);
}
return entries;
}));
}
else {
const keys = Object.keys(spreading.value);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
entries.push([key, spreading.value[key]]);
}
}
}
}
return {
async: !!isAsync,
value: isAsync
? Promise.all(entries)
.then((entries) => {
const flat = [];
for (let i = 0; i < entries.length; ++i) {
const entry = entries[i];
if (Array.isArray(entry[0])) {
for (let j = 0; j < entry.length; j++) {
flat.push(entry[j]);
}
}
else {
flat.push(entry);
}
}
return Object.fromEntries(flat);
})
: Object.fromEntries(entries),
};
}
evaluateUnaryExpression(node) {
const unary = (arg) => {
switch (node.operator) {
/* eslint-disable no-multi-spaces, switch-colon-spacing */
case '+' : return +arg;
case '-' : return -arg;
case '!' : return !arg;
// eslint-disable-next-line no-bitwise
case '~' : return ~arg;
case 'typeof': return typeof arg;
// eslint-disable-next-line no-void
case 'void' : return undefined;
// case 'delete': ...
case 'throw' : throw arg;
/* no-multi-spaces, switch-colon-spacing */
default:
// eslint-disable-next-line no-console
console.error("evaluateUnaryExpression(): Can't handle operator", node.operator);
return undefined;
}
};
const arg = this.evaluate(node.argument);
if (arg.async) {
return {
async: true,
value: Promise.resolve(arg.value).then(unary),
};
}
return {value: unary(arg.value)};
}
evaluateUpdateExpression(node) {
const {argument, operator, prefix} = node;
const {async, value} = this.evaluate(argument);
const scope = this.nodeScope(node);
const update = (value) => {
if (prefix) {
switch (operator) {
case '++': return scope.set(argument.name, value + 1);
case '--': return scope.set(argument.name, value - 1);
default:
}
}
switch (operator) {
case '++':
scope.set(argument.name, value + 1);
return value;
case '--':
scope.set(argument.name, value - 1);
return value;
default:
}
// eslint-disable-next-line no-console
console.error("evaluateUpdateExpression(): Can't handle", operator);
return undefined;
};
if (async) {
return {
async: true,
value: Promise.resolve(value).then((value) => update(value)),
};
}
return {value: update(value)};
}
static fastCall(fn, holder, args) {
if (holder) {
const {name} = fn;
if (name in holder && holder[name] === fn) {
switch (args.length) {
case 0 : return holder[name]();
case 1 : return holder[name](args[0]);
case 2 : return holder[name](args[0], args[1]);
case 3 : return holder[name](args[0], args[1], args[2]);
case 4 : return holder[name](args[0], args[1], args[2], args[3]);
case 5 : return holder[name](args[0], args[1], args[2], args[3], args[4]);
default: return holder[name](...args);
}
}
const bound = fn.bind(holder);
switch (args.length) {
case 0 : return bound();
case 1 : return bound(args[0]);
case 2 : return bound(args[0], args[1]);
case 3 : return bound(args[0], args[1], args[2]);
case 4 : return bound(args[0], args[1], args[2], args[3]);
case 5 : return bound(args[0], args[1], args[2], args[3], args[4]);
default: return bound(...args);
}
}
switch (args.length) {
case 0 : return fn();
case 1 : return fn(args[0]);
case 2 : return fn(args[0], args[1]);
case 3 : return fn(args[0], args[1], args[2]);
case 4 : return fn(args[0], args[1], args[2], args[3]);
case 5 : return fn(args[0], args[1], args[2], args[3], args[4]);
default: return fn(...args);
}
}
next() {
return this.runner.next();
}
nextNodes(node, keys) {
if (!keys) {
return [];
}
const nodes = [];
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const subNode = node[key];
if (Array.isArray(subNode)) {
for (let i = 0; i < subNode.length; i++) {
const child = subNode[i];
if (child) {
this.setNodeParent(child, node);
nodes.push(child);
}
}
}
else if (subNode) {
this.setNodeParent(subNode, node);
nodes.push(subNode);
}
}
return nodes;
}
nodeParent(node) {
return this.parents.get(node);
}
nodeScope(node) {
return this.scopes.get(node);
}
reset() {
const self = this;
const {context} = this;
this.parents = new WeakMap();
this.scopes = new WeakMap();
this.context = context;
this.runner = (function* traverse() {
return yield* self.traverse(self.ast);
}());
return this;
}
get rootScope() {
return this.nodeScope(this.ast);
}
async run(max = 10000) {
for (
let {done, value: {async, value}} = this.runner.next();
// eslint-disable-next-line no-param-reassign
--max > 0
&& done !== true
&& async
// eslint-disable-next-line no-await-in-loop
&& await value;
{done, value: {async, value}} = this.runner.next()
// eslint-disable-next-line no-empty
) {}
return this;
}
setNextScope(node, scope = this.nodeScope(node)) {
const nodes = this.nextNodes(node, types.VISITOR_KEYS[node.type]);
for (let i = 0; i < nodes.length; i++) {
this.setNodeScope(nodes[i], scope);
}
}
setNodeParent(node, parent) {
this.parents.set(node, parent);
}
setNodeScope(node, scope) {
this.scopes.set(node, scope);
}
* traverse(node) {
let keys = types.VISITOR_KEYS[node.type];
if (!keys) {
return;
}
// Scope...
let scope = this.nodeScope(node);
if (types.isBlockStatement(node)) {
scope = scope.push();
}
if (types.isForStatement(node)) {
scope = scope.push();
}
this.setNextScope(node, scope);
if (types.isVariableDeclarator(node)) {
const {id} = node;
const init = this.evaluate(node.init);
if (types.isIdentifier(id)) {
if (init.async) {
yield {
async: true,
value: Promise.resolve(init.value).then((value) => {
scope.allocate(id.name, value);
}),
};
}
else {
scope.allocate(id.name, init.value);
}
}
else if (types.isArrayPattern(id)) {
const promiseOrVoid = init.async
? Promise.resolve(init.value).then((init) => this.destructureArray(id, init, scope))
: this.destructureArray(id, init.value, scope);
if (promiseOrVoid) {
yield {
async: true,
value: promiseOrVoid,
};
}
}
else if (types.isObjectPattern(id)) {
const promiseOrVoid = init.async
? Promise.resolve(init.value).then((init) => this.destructureObject(id, init, scope))
: this.destructureObject(id, init.value, scope);
if (promiseOrVoid) {
yield {
async: true,
value: promiseOrVoid,
};
}
}
}
// Blocks...
if (types.isIfStatement(node)) {
const {async, value} = this.evaluate(node.test);
const branch = (value) => {
keys = [value ? 'consequent' : 'alternate'];
};
if (async) {
yield {
async: true,
value: Promise.resolve(value).then(branch),
};
}
else {
branch(value);
}
}
// Loops...
let loop = false;
if (types.isForStatement(node)) {
const {value} = this.traverse(node.init).next();
if (value?.async) {
yield value;
}
}
do {
if (
types.isForStatement(node)
|| types.isWhileStatement(node)
) {
const {async, value} = this.evaluate(node.test);
if (async) {
yield {
async: true,
// eslint-disable-next-line no-loop-func
value: Promise.resolve(value).then((value) => {
keys = value ? ['body'] : [];
}),
};
}
else {
keys = value ? ['body'] : [];
}
loop = keys.length > 0;
}
// Recur...
const nodes = this.nextNodes(node, keys);
for (let i = 0; i < nodes.length; i++) {
const r = yield* this.traverse(nodes[i]);
if (r) {
// eslint-disable-next-line consistent-return
return r;
}
}
// Loops...
if (types.isForStatement(node)) {
const {value} = this.traverse(node.update).next();
if (value?.async) {
yield value;
}
if (loop) {
yield {loop: 'for', value: undefined};
}
}
if (types.isDoWhileStatement(node)) {
const test = this.evaluate(node.test);
if (test.async) {
yield {
async: true,
// eslint-disable-next-line no-loop-func
value: Promise.resolve(test.value).then((value) => {
loop = value;
}),
};
}
else {
loop = test.value;
}
yield {loop: 'doWhile', value: undefined};
}
if (types.isWhileStatement(node) && loop) {
yield {loop: 'while', value: undefined};
}
} while (loop);
// Scope...
if (types.isBlockStatement(node)) {
scope = scope.pop();
}
if (types.isForStatement(node)) {
scope = scope.pop();
}
// Evaluate...
if (types.isReturnStatement(node)) {
// eslint-disable-next-line consistent-return
return !node.argument ? {value: undefined} : this.evaluate(node.argument);
}
if (types.isDirective(node)) {
yield this.evaluate(node.value);
}
if (types.isExpressionStatement(node)) {
yield this.evaluate(node.expression);
}
// Pass through ForStatement update expressions.
if (types.isUpdateExpression(node) && types.isForStatement(this.nodeParent(node))) {
yield this.evaluate(node);
}
}
}

56
src/scope.js Normal file
View File

@ -0,0 +1,56 @@
export default class Scope {
parent = null;
context = {};
constructor(context = {}) {
this.context = context;
}
allocate(key, value) {
this.context[key] = value;
}
get(key) {
let walk = this;
while (walk) {
if (key in walk.context) {
return walk.context[key];
}
walk = walk.parent;
}
return undefined;
}
pop() {
const {parent} = this;
this.parent = null;
return parent;
}
push() {
const scope = new Scope();
scope.parent = this;
return scope;
}
set(key, value) {
let walk = this;
// eslint-disable-next-line no-constant-condition
while (walk) {
if (key in walk.context) {
walk.context[key] = value;
return value;
}
// TODO: disallow global set?
if (!walk.parent) {
walk.context[key] = value;
return value;
}
walk = walk.parent;
}
return undefined;
}
}

18
test/array-expression.js Normal file
View File

@ -0,0 +1,18 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates ArrayExpression', () => {
expect(new Sandbox(parse('[1, 2, 3]')).next().value)
.to.deep.include({value: [1, 2, 3]});
});
it('evaluates async ArrayExpression', async () => {
const o = {allowAwaitOutsideFunction: true};
const {async, value} = new Sandbox(parse('[await 1, 2, 3]', o)).next().value;
expect(async)
.to.be.true;
expect(await value)
.to.deep.equal([1, 2, 3]);
});

View File

@ -0,0 +1,54 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates async AssignmentExpression', async () => {
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('let foo = await 69; foo + 351', o));
const {async, value} = sandbox.next().value;
expect(async)
.to.be.true;
await value;
expect(sandbox.next().value)
.to.deep.include({value: 420});
});
it('evaluates AssignmentExpression', () => {
expect(new Sandbox(parse('let foo = 69; foo + 351')).next().value)
.to.deep.include({value: 420});
});
it('destructures', () => {
const sandbox = new Sandbox(parse('const {a, b} = {a: 1, b: 2}; [a, b];'));
expect(sandbox.next().value)
.to.deep.include({value: [1, 2]});
});
it('destructures async key', async () => {
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('const k = "a"; const {[await k]: a, b} = {a: 1, b: 2}; [a, b];', o));
const {async, value} = sandbox.next().value;
expect(async)
.to.be.true;
await value;
expect(sandbox.next().value)
.to.deep.include({value: [1, 2]});
});
it('destructures async value', async () => {
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('const {a, b} = await {a: 1, b: 2}; [a, b];', o));
const {async, value} = sandbox.next().value;
expect(async)
.to.be.true;
await value;
expect(sandbox.next().value)
.to.deep.include({value: [1, 2]});
});
it('nested destructures', () => {
const sandbox = new Sandbox(parse('const {a, b: {c}} = {a: 1, b: {c: 2}}; [a, c];'));
expect(sandbox.next().value)
.to.deep.include({value: [1, 2]});
});

18
test/await-expression.js Normal file
View File

@ -0,0 +1,18 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates AwaitExpression', async () => {
const context = {
wait: () => new Promise((resolve) => setTimeout(() => resolve(420), 0)),
};
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('const test = await wait(); test', o), context);
const {value} = sandbox.next();
expect(value.async)
.to.be.true;
await value.value;
expect(sandbox.next().value.value)
.to.equal(420);
});

51
test/binary-expression.js Normal file
View File

@ -0,0 +1,51 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates BinaryExpression', () => {
expect(new Sandbox(parse('1 + 2')).next().value)
.to.deep.include({value: 3});
expect(new Sandbox(parse('1 - 2')).next().value)
.to.deep.include({value: -1});
expect(new Sandbox(parse('4 / 2')).next().value)
.to.deep.include({value: 2});
expect(new Sandbox(parse('10 % 3')).next().value)
.to.deep.include({value: 1});
expect(new Sandbox(parse('1 * 2')).next().value)
.to.deep.include({value: 2});
expect(new Sandbox(parse('1 > 2')).next().value)
.to.deep.include({value: false});
expect(new Sandbox(parse('1 < 2')).next().value)
.to.deep.include({value: true});
expect(new Sandbox(parse('const foo = {a: 69}; "a" in foo')).next().value)
.to.deep.include({value: true});
expect(new Sandbox(parse('const foo = {a: 69}; "b" in foo')).next().value)
.to.deep.include({value: false});
expect(new Sandbox(parse('1 >= 2')).next().value)
.to.deep.include({value: false});
expect(new Sandbox(parse('1 <= 2')).next().value)
.to.deep.include({value: true});
expect(new Sandbox(parse('2 ** 3')).next().value)
.to.deep.include({value: 8});
expect(new Sandbox(parse('1 === 2')).next().value)
.to.deep.include({value: false});
expect(new Sandbox(parse('1 !== 2')).next().value)
.to.deep.include({value: true});
expect(new Sandbox(parse('7 & 3')).next().value)
.to.deep.include({value: 3});
expect(new Sandbox(parse('1 | 2')).next().value)
.to.deep.include({value: 3});
expect(new Sandbox(parse('16 >> 2')).next().value)
.to.deep.include({value: 4});
expect(new Sandbox(parse('16 >>> 5')).next().value)
.to.deep.include({value: 0});
expect(new Sandbox(parse('1 << 2')).next().value)
.to.deep.include({value: 4});
expect(new Sandbox(parse('1 ^ 2')).next().value)
.to.deep.include({value: 3});
expect(new Sandbox(parse('1 == 2')).next().value)
.to.deep.include({value: false});
expect(new Sandbox(parse('1 != 2')).next().value)
.to.deep.include({value: true});
});

9
test/call-expression.js Normal file
View File

@ -0,0 +1,9 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates CallExpression', () => {
expect(new Sandbox(parse('test()'), {test: () => 69}).next().value)
.to.deep.include({value: 69});
});

13
test/context.js Normal file
View File

@ -0,0 +1,13 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('can update context', () => {
const sandbox = new Sandbox(parse('foo = 420'), {foo: 69});
expect(sandbox.context)
.to.deep.equal({foo: 69});
sandbox.run();
expect(sandbox.context)
.to.deep.equal({foo: 420});
});

View File

@ -0,0 +1,11 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates DoWhileStatement', () => {
const sandbox = new Sandbox(parse('let i = 0; do { i++; } while (false); i'));
expect(sandbox.next().value).to.deep.include({value: 0});
expect(sandbox.next().value).to.deep.include({value: undefined});
expect(sandbox.next().value).to.deep.include({value: 1});
});

13
test/for-statement.js Normal file
View File

@ -0,0 +1,13 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates ForStatement', () => {
const sandbox = new Sandbox(parse('for (let i = 0; i < 5; ++i) { i; }'));
for (let i = 0; i < 5; ++i) {
expect(sandbox.next().value).to.deep.include({value: i});
// Loop...
sandbox.next();
}
});

9
test/identifier.js Normal file
View File

@ -0,0 +1,9 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates Identifier', () => {
expect(new Sandbox(parse('const test = 69; test')).next().value)
.to.deep.include({value: 69});
});

11
test/if-statement.js Normal file
View File

@ -0,0 +1,11 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates IfStatement', () => {
expect(new Sandbox(parse('if (false) { 69; }')).next())
.to.deep.include({done: true});
expect(new Sandbox(parse('if (true) { 69; }')).next().value)
.to.deep.include({value: 69});
});

13
test/literal.js Normal file
View File

@ -0,0 +1,13 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates Literal', () => {
expect(new Sandbox(parse('69')).next().value)
.to.deep.include({value: 69});
expect(new Sandbox(parse('"420"')).next().value)
.to.deep.include({value: '420'});
expect(new Sandbox(parse('420.69')).next().value)
.to.deep.include({value: 420.69});
});

24
test/member-expression.js Normal file
View File

@ -0,0 +1,24 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates MemberExpression', () => {
expect(new Sandbox(parse('a.b.c'), {a: {b: {c: 69}}}).next().value)
.to.deep.include({value: 69});
});
it('evaluates async MemberExpression', async () => {
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('const aa = await a; aa.b.c', o), {a: {b: {c: 69}}});
let {async, value} = sandbox.next().value;
expect(async)
.to.be.true;
expect(await value)
.to.equal(undefined);
({async, value} = sandbox.next().value);
expect(async)
.to.be.undefined;
expect(value)
.to.equal(69);
});

42
test/object-expression.js Normal file
View File

@ -0,0 +1,42 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates async ObjectExpression', async () => {
const context = {
b: () => Promise.resolve(2),
};
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('({a: 1, b: await b()})', o), context);
const {async, value} = sandbox.next().value;
expect(async).to.be.true;
expect(await value)
.to.deep.include({a: 1, b: 2});
});
it('evaluates ObjectExpression', () => {
expect(new Sandbox(parse('({a: 1, b: 2})')).next().value)
.to.deep.include({value: {a: 1, b: 2}});
expect(new Sandbox(parse('({"a": 1, "b": 2})')).next().value)
.to.deep.include({value: {a: 1, b: 2}});
});
it('evaluates async SpreadElement', async () => {
const context = {
b: () => Promise.resolve({a: 2}),
};
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('({a: 1, ...(await b())})', o), context);
const {async, value} = sandbox.next().value;
expect(async).to.be.true;
expect(await value)
.to.deep.include({a: 2});
});
it('evaluates SpreadElement', () => {
expect(new Sandbox(parse('({...({a: 1}), a: 2})')).next().value)
.to.deep.include({value: {a: 2}});
expect(new Sandbox(parse('({a: 0, ...({a: 1})})')).next().value)
.to.deep.include({value: {a: 1}});
});

14
test/return.js Normal file
View File

@ -0,0 +1,14 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates ReturnStatement', async () => {
const o = {allowReturnOutsideFunction: true};
const sandbox = new Sandbox(parse('return 69', o));
const {done, value} = sandbox.next();
expect(done)
.to.be.true;
expect(value.value)
.to.deep.equal(69);
});

12
test/run.js Normal file
View File

@ -0,0 +1,12 @@
import {parse} from '@babel/parser';
import Sandbox from '../src/sandbox';
it('runs', async () => {
const context = {
wait: () => new Promise((resolve) => setTimeout(() => resolve(), 0)),
};
const o = {allowAwaitOutsideFunction: true};
const sandbox = new Sandbox(parse('await wait(); 1 + 3 * 2;', o), context);
await sandbox.run();
});

17
test/scope.js Normal file
View File

@ -0,0 +1,17 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('scopes BlockStatement', () => {
const sandbox = new Sandbox(parse('if (true) { const foo = 69; foo; } foo;'));
expect(sandbox.next().value)
.to.deep.include({value: 69});
expect(sandbox.next().value)
.to.deep.include({value: undefined});
});
it('scopes ForStatement', () => {
expect(new Sandbox(parse('for (let i = 0; i < 5; ++i) {} i;')).next().value)
.to.deep.include({value: undefined});
});

15
test/update-expression.js Normal file
View File

@ -0,0 +1,15 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates UpdateExpression', () => {
expect(new Sandbox(parse('a++'), {a: 1}).next().value)
.to.deep.include({value: 1});
expect(new Sandbox(parse('++a'), {a: 1}).next().value)
.to.deep.include({value: 2});
expect(new Sandbox(parse('a--'), {a: 1}).next().value)
.to.deep.include({value: 1});
expect(new Sandbox(parse('--a'), {a: 1}).next().value)
.to.deep.include({value: 0});
});

13
test/while-statement.js Normal file
View File

@ -0,0 +1,13 @@
import {parse} from '@babel/parser';
import {expect} from 'chai';
import Sandbox from '../src/sandbox';
it('evaluates WhileStatement', () => {
const sandbox = new Sandbox(parse('let i = 0; while (i < 5) { i++; }'));
for (let i = 0; i < 5; ++i) {
expect(sandbox.next().value).to.deep.include({value: i});
// Loop...
sandbox.next();
}
});

5962
yarn.lock Normal file

File diff suppressed because it is too large Load Diff