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