sandbox: more fixes and tests
This commit is contained in:
parent
00862f96bd
commit
d7a629db7a
|
@ -1,6 +1,6 @@
|
||||||
export default function(node, {evaluate, scope}) {
|
export default function(node, {evaluate, scope}) {
|
||||||
const {computed, object, property, wrapper} = node;
|
const {computed, object, optional, property} = node;
|
||||||
const member = (O, P) => (wrapper?.optional ? O?.[P] : O[P]);
|
const member = (O, P) => (optional ? O?.[P] : O[P]);
|
||||||
const O = evaluate(object, {scope});
|
const O = evaluate(object, {scope});
|
||||||
const P = computed
|
const P = computed
|
||||||
? evaluate(property, {scope})
|
? evaluate(property, {scope})
|
||||||
|
|
|
@ -106,8 +106,8 @@ export default class Sandbox {
|
||||||
evaluateToResult(node) {
|
evaluateToResult(node) {
|
||||||
const {async, value} = this.evaluate(node);
|
const {async, value} = this.evaluate(node);
|
||||||
return {
|
return {
|
||||||
yield: async ? YIELD_PROMISE : YIELD_NONE,
|
|
||||||
value,
|
value,
|
||||||
|
yield: async ? YIELD_PROMISE : YIELD_NONE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +139,10 @@ export default class Sandbox {
|
||||||
case 'AssignmentExpression':
|
case 'AssignmentExpression':
|
||||||
case 'BinaryExpression':
|
case 'BinaryExpression':
|
||||||
case 'CallExpression':
|
case 'CallExpression':
|
||||||
|
case 'ChainExpression':
|
||||||
case 'ObjectExpression':
|
case 'ObjectExpression':
|
||||||
case 'Identifier':
|
case 'Identifier':
|
||||||
|
case 'MemberExpression':
|
||||||
case 'UnaryExpression':
|
case 'UnaryExpression':
|
||||||
case 'UpdateExpression': {
|
case 'UpdateExpression': {
|
||||||
result = this.evaluateToResult(node);
|
result = this.evaluateToResult(node);
|
||||||
|
@ -154,17 +156,20 @@ export default class Sandbox {
|
||||||
result = this.executeSync(node.argument, depth + 1);
|
result = this.executeSync(node.argument, depth + 1);
|
||||||
if (coerce) {
|
if (coerce) {
|
||||||
result = {
|
result = {
|
||||||
yield: YIELD_PROMISE,
|
|
||||||
value: result.value,
|
value: result.value,
|
||||||
|
yield: YIELD_PROMISE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (result.yield) {
|
if (result.yield) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
this.$$execution.deferred.delete(node.argument);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'BlockStatement': {
|
case 'BlockStatement': {
|
||||||
|
result = {
|
||||||
|
value: undefined,
|
||||||
|
yield: YIELD_NONE,
|
||||||
|
};
|
||||||
let skipping = isReplaying;
|
let skipping = isReplaying;
|
||||||
for (const child of node.body) {
|
for (const child of node.body) {
|
||||||
if (skipping && child === this.$$execution.stack[depth + 1]) {
|
if (skipping && child === this.$$execution.stack[depth + 1]) {
|
||||||
|
@ -181,10 +186,19 @@ export default class Sandbox {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ConditionalExpression': {
|
case 'ConditionalExpression': {
|
||||||
|
const shouldVisitChild = (child) => (
|
||||||
|
isReplaying
|
||||||
|
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
if (shouldVisitChild(node.test)) {
|
||||||
const test = this.executeSync(node.test, depth + 1);
|
const test = this.executeSync(node.test, depth + 1);
|
||||||
if (test.yield) {
|
if (test.yield) {
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
|
this.$$execution.deferred.set(node.test, test);
|
||||||
|
}
|
||||||
|
const test = this.$$execution.deferred.get(node.test);
|
||||||
if (test.value) {
|
if (test.value) {
|
||||||
result = this.executeSync(node.consequent, depth + 1);
|
result = this.executeSync(node.consequent, depth + 1);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +216,7 @@ export default class Sandbox {
|
||||||
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
||||||
: true
|
: true
|
||||||
);
|
);
|
||||||
if (this.$$execution.stack[this.$$execution.stack.length - 1] === undefined) {
|
if (this.$$execution.stack[depth + 1] === node) {
|
||||||
this.$$execution.stack.pop();
|
this.$$execution.stack.pop();
|
||||||
}
|
}
|
||||||
result = this.$$execution.deferred.get(node.body);
|
result = this.$$execution.deferred.get(node.body);
|
||||||
|
@ -224,7 +238,7 @@ export default class Sandbox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Yield
|
// Yield
|
||||||
this.$$execution.stack.push(undefined);
|
this.$$execution.stack.push(node);
|
||||||
return {
|
return {
|
||||||
value: undefined,
|
value: undefined,
|
||||||
yield: YIELD_LOOP_UPDATE,
|
yield: YIELD_LOOP_UPDATE,
|
||||||
|
@ -249,7 +263,7 @@ export default class Sandbox {
|
||||||
return init;
|
return init;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.$$execution.stack[this.$$execution.stack.length - 1] === undefined) {
|
if (this.$$execution.stack[depth + 1] === node) {
|
||||||
this.$$execution.stack.pop();
|
this.$$execution.stack.pop();
|
||||||
}
|
}
|
||||||
result = this.$$execution.deferred.get(node.body) || {
|
result = this.$$execution.deferred.get(node.body) || {
|
||||||
|
@ -281,7 +295,7 @@ export default class Sandbox {
|
||||||
this.$$execution.deferred.set(node.update, update);
|
this.$$execution.deferred.set(node.update, update);
|
||||||
}
|
}
|
||||||
// Yield
|
// Yield
|
||||||
this.$$execution.stack.push(undefined);
|
this.$$execution.stack.push(node);
|
||||||
const update = this.$$execution.deferred.get(node.update);
|
const update = this.$$execution.deferred.get(node.update);
|
||||||
this.$$execution.deferred.delete(node.update);
|
this.$$execution.deferred.delete(node.update);
|
||||||
return {
|
return {
|
||||||
|
@ -290,14 +304,23 @@ export default class Sandbox {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'IfStatement': {
|
case 'IfStatement': {
|
||||||
|
const shouldVisitChild = (child) => (
|
||||||
|
isReplaying
|
||||||
|
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
||||||
|
: true
|
||||||
|
);
|
||||||
|
result = {
|
||||||
|
value: undefined,
|
||||||
|
yield: YIELD_NONE,
|
||||||
|
};
|
||||||
|
if (shouldVisitChild(node.test)) {
|
||||||
const test = this.executeSync(node.test, depth + 1);
|
const test = this.executeSync(node.test, depth + 1);
|
||||||
if (test.yield) {
|
if (test.yield) {
|
||||||
return test;
|
return test;
|
||||||
}
|
}
|
||||||
result = {
|
this.$$execution.deferred.set(node.test, test);
|
||||||
yield: YIELD_NONE,
|
}
|
||||||
value: undefined,
|
const test = this.$$execution.deferred.get(node.test);
|
||||||
};
|
|
||||||
if (test.value) {
|
if (test.value) {
|
||||||
result = this.executeSync(node.consequent, depth + 1);
|
result = this.executeSync(node.consequent, depth + 1);
|
||||||
}
|
}
|
||||||
|
@ -307,6 +330,7 @@ export default class Sandbox {
|
||||||
if (result.yield) {
|
if (result.yield) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
this.$$execution.deferred.delete(node.test);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Literal': {
|
case 'Literal': {
|
||||||
|
@ -327,7 +351,7 @@ export default class Sandbox {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = {yield: YIELD_RETURN, value: result.value};
|
result = {value: result.value, yield: YIELD_RETURN};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ReturnStatement': {
|
case 'ReturnStatement': {
|
||||||
|
@ -341,10 +365,11 @@ export default class Sandbox {
|
||||||
if (argument.yield) {
|
if (argument.yield) {
|
||||||
return argument;
|
return argument;
|
||||||
}
|
}
|
||||||
return {
|
result = {
|
||||||
value: argument.value,
|
value: argument.value,
|
||||||
yield: YIELD_RETURN,
|
yield: YIELD_RETURN,
|
||||||
};
|
};
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case 'VariableDeclaration': {
|
case 'VariableDeclaration': {
|
||||||
let skipping = isReplaying;
|
let skipping = isReplaying;
|
||||||
|
@ -426,7 +451,7 @@ export default class Sandbox {
|
||||||
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
? (!this.$$execution.stack[depth + 1] || this.$$execution.stack[depth + 1] === child)
|
||||||
: true
|
: true
|
||||||
);
|
);
|
||||||
if (this.$$execution.stack[this.$$execution.stack.length - 1] === undefined) {
|
if (this.$$execution.stack[depth + 1] === node) {
|
||||||
this.$$execution.stack.pop();
|
this.$$execution.stack.pop();
|
||||||
}
|
}
|
||||||
result = this.$$execution.deferred.get(node.body) || {
|
result = this.$$execution.deferred.get(node.body) || {
|
||||||
|
@ -451,7 +476,7 @@ export default class Sandbox {
|
||||||
this.$$execution.deferred.set(node.body, body);
|
this.$$execution.deferred.set(node.body, body);
|
||||||
}
|
}
|
||||||
// Yield
|
// Yield
|
||||||
this.$$execution.stack.push(undefined);
|
this.$$execution.stack.push(node);
|
||||||
return {
|
return {
|
||||||
value: undefined,
|
value: undefined,
|
||||||
yield: YIELD_LOOP_UPDATE,
|
yield: YIELD_LOOP_UPDATE,
|
||||||
|
@ -470,7 +495,7 @@ export default class Sandbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.stack = [];
|
this.$$execution = undefined;
|
||||||
for (const scope of this.scopes.values()) {
|
for (const scope of this.scopes.values()) {
|
||||||
scope.context = {};
|
scope.context = {};
|
||||||
}
|
}
|
||||||
|
@ -481,7 +506,7 @@ export default class Sandbox {
|
||||||
let result;
|
let result;
|
||||||
for (let i = 0; i < ops; ++i) {
|
for (let i = 0; i < ops; ++i) {
|
||||||
result = this.step();
|
result = this.step();
|
||||||
if (result.done || result.value?.async) {
|
if (result.done || result.async) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,7 +525,8 @@ export default class Sandbox {
|
||||||
switch (result.yield) {
|
switch (result.yield) {
|
||||||
case YIELD_PROMISE: {
|
case YIELD_PROMISE: {
|
||||||
stepResult.async = true;
|
stepResult.async = true;
|
||||||
stepResult.value = Promise.resolve(result.value).then((value) => {
|
stepResult.value = Promise.resolve(result.value)
|
||||||
|
.then((value) => {
|
||||||
const top = this.$$execution.stack[this.$$execution.stack.length - 1];
|
const top = this.$$execution.stack[this.$$execution.stack.length - 1];
|
||||||
this.$$execution.deferred.set(top, {
|
this.$$execution.deferred.set(top, {
|
||||||
value,
|
value,
|
||||||
|
|
|
@ -73,7 +73,6 @@ test('returns last', async () => {
|
||||||
}
|
}
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
;
|
|
||||||
expect(await finish(sandbox))
|
expect(await finish(sandbox))
|
||||||
.to.deep.include({done: true, value: 64});
|
.to.deep.include({done: true, value: 64});
|
||||||
});
|
});
|
||||||
|
@ -227,6 +226,24 @@ test('returns values at the top level', async () => {
|
||||||
.to.deep.equal({x: 16, y: 4});
|
.to.deep.equal({x: 16, y: 4});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('returns arbitrarily', async () => {
|
||||||
|
let sandbox;
|
||||||
|
sandbox = new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
x = 16
|
||||||
|
y = 4
|
||||||
|
if (x === 16) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
`, {allowReturnOutsideFunction: true}),
|
||||||
|
);
|
||||||
|
const {value} = sandbox.run();
|
||||||
|
expect(value)
|
||||||
|
.to.deep.equal(false);
|
||||||
|
expect(sandbox.context)
|
||||||
|
.to.deep.equal({x: 16, y: 4});
|
||||||
|
});
|
||||||
|
|
||||||
test('sets variables in global scope', async () => {
|
test('sets variables in global scope', async () => {
|
||||||
const sandbox = new Sandbox(
|
const sandbox = new Sandbox(
|
||||||
await parse(`
|
await parse(`
|
||||||
|
@ -302,3 +319,89 @@ test('clears while loop deferred context', async () => {
|
||||||
value: undefined,
|
value: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('executes member expressions', async () => {
|
||||||
|
const sandbox = new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
let l = {}
|
||||||
|
l.f = 54
|
||||||
|
if (l.f) {
|
||||||
|
l.g = 65
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
expect(sandbox.run())
|
||||||
|
.to.deep.include({
|
||||||
|
value: 65,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles nested yields', async () => {
|
||||||
|
expect(
|
||||||
|
await finish(new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
for (let i = 0; i < 1; ++i) {
|
||||||
|
for (let j = 0; j < 1; ++j) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
.to.deep.include({value: undefined});
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await finish(new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
for (let i = 0; i < 1; ++i) {
|
||||||
|
for (let j = 0; j < 1; ++j) {
|
||||||
|
await 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.to.deep.include({value: 1});
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await finish(new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
if (true) {
|
||||||
|
for (let i = 0; i < 1; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.to.deep.include({value: undefined});
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
await finish(new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
if (1 ? await 2 : 3) {
|
||||||
|
for (let i = 0; i < 1; ++i) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.to.deep.include({value: undefined});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles optional expressions', async () => {
|
||||||
|
const context = {
|
||||||
|
console,
|
||||||
|
projected: undefined,
|
||||||
|
}
|
||||||
|
const sandbox = new Sandbox(
|
||||||
|
await parse(`
|
||||||
|
if (projected?.length > 0) {
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
expect(sandbox.run())
|
||||||
|
.to.deep.include({value: undefined});
|
||||||
|
});
|
||||||
|
|
|
@ -77,7 +77,7 @@ export default class Script {
|
||||||
|
|
||||||
evaluateSync() {
|
evaluateSync() {
|
||||||
this.sandbox.reset();
|
this.sandbox.reset();
|
||||||
const {value: {value}} = this.sandbox.run();
|
const {value} = this.sandbox.run();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,12 +113,10 @@ export default class Script {
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
this.sandbox.context.elapsed = elapsed;
|
this.sandbox.context.elapsed = elapsed;
|
||||||
const {done, value} = this.sandbox.step();
|
const {async, done, value} = this.sandbox.step();
|
||||||
if (value) {
|
|
||||||
const {async, value: result} = value;
|
|
||||||
if (async) {
|
if (async) {
|
||||||
this.promise = result;
|
this.promise = value;
|
||||||
result
|
value
|
||||||
.catch(reject)
|
.catch(reject)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (done) {
|
if (done) {
|
||||||
|
@ -130,7 +128,6 @@ export default class Script {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (done) {
|
if (done) {
|
||||||
resolve();
|
resolve();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -118,9 +118,6 @@ if (projected?.length > 0) {
|
||||||
if ([1, 2, 3, 4].includes(layer.tile(projected[i]))) {
|
if ([1, 2, 3, 4].includes(layer.tile(projected[i]))) {
|
||||||
layer.stamp(projected[i], [[7]])
|
layer.stamp(projected[i], [[7]])
|
||||||
}
|
}
|
||||||
// else if ([6].includes(layer.tile(projected[i]))) {
|
|
||||||
// layer.stamp(projected[i], [[7]])
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Controlled.locked = 0;
|
Controlled.locked = 0;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user