From 5365608f9f657d8abfce0289e95f26df9feaf0fd Mon Sep 17 00:00:00 2001 From: cha0s Date: Fri, 12 Jul 2024 18:32:55 -0500 Subject: [PATCH] feat: break --- app/astride/sandbox.js | 29 +++++++ app/astride/sandbox.test.js | 78 +++++++++++++++++++ app/astride/traverse.js | 1 + .../assets/tomato-seeds/projection-check.js | 1 + 4 files changed, 109 insertions(+) diff --git a/app/astride/sandbox.js b/app/astride/sandbox.js index 8e39a62..fe51520 100644 --- a/app/astride/sandbox.js +++ b/app/astride/sandbox.js @@ -6,6 +6,7 @@ const YIELD_NONE = 0; const YIELD_PROMISE = 1; const YIELD_LOOP_UPDATE = 2; const YIELD_RETURN = 3; +const YIELD_BREAK = 4; export default class Sandbox { @@ -194,6 +195,24 @@ export default class Sandbox { } break; } + case 'BreakStatement': { + walkUp: while (this.$$execution.stack.length > 0) { + const frame = this.$$execution.stack[this.$$execution.stack.length - 1]; + switch (frame.type) { + case 'ForStatement': { + break walkUp; + } + case 'ForOfStatement': { + break walkUp; + } + default: this.$$execution.stack.pop(); + } + } + return { + value: undefined, + yield: YIELD_BREAK, + }; + } case 'ConditionalExpression': { const shouldVisitChild = (child) => ( isReplaying @@ -292,6 +311,10 @@ export default class Sandbox { if (shouldVisitChild(node.body)) { const body = this.executeSync(node.body, depth + 1); if (body.yield) { + if (YIELD_BREAK === body.yield) { + this.$$execution.deferred.delete(node.body); + break; + } return body; } this.$$execution.deferred.set(node.body, body); @@ -338,6 +361,7 @@ export default class Sandbox { } const {done, value} = this.$$execution.deferred.get(node.left); if (done) { + this.$$execution.deferred.delete(node.left); this.$$execution.deferred.delete(node.body); break; } @@ -376,6 +400,11 @@ export default class Sandbox { if (shouldVisitChild(node.body)) { const body = this.executeSync(node.body, depth + 1); if (body.yield) { + if (YIELD_BREAK === body.yield) { + this.$$execution.deferred.delete(node.left); + this.$$execution.deferred.delete(node.body); + break; + } return body; } this.$$execution.deferred.set(node.body, body); diff --git a/app/astride/sandbox.test.js b/app/astride/sandbox.test.js index 2622561..4269b4b 100644 --- a/app/astride/sandbox.test.js +++ b/app/astride/sandbox.test.js @@ -553,3 +553,81 @@ test('implements for...of', async () => { ) .to.deep.include({value: [2, 12, 30]}); }); + +test('breaks loops', async () => { + expect( + (new Sandbox( + await parse(` + const out = []; + for (let i = 0; i < 3; ++i) { + out.push(i); + break; + } + out + `), + )).run() + ) + .to.deep.include({value: [0]}); + expect( + (new Sandbox( + await parse(` + const out = []; + for (let i = 0; i < 3; ++i) { + out.push(i); + if (i > 0) { + break; + } + } + out + `), + )).run() + ) + .to.deep.include({value: [0, 1]}); + expect( + (new Sandbox( + await parse(` + const out = []; + for (const x of [1, 2, 3]) { + out.push(x); + break; + } + out + `), + )).run() + ) + .to.deep.include({value: [1]}); + expect( + (new Sandbox( + await parse(` + const out = []; + for (const x of [1, 2, 3]) { + for (const y of [4, 5, 6]) { + out.push(x); + if (y > 4) { + break; + } + } + } + out + `), + )).run() + ) + .to.deep.include({value: [1, 1, 2, 2, 3, 3]}); + expect( + (new Sandbox( + await parse(` + const out = []; + for (let x = 1; x < 4; ++x) { + for (let y = 4; y < 7; ++y) { + out.push(x); + if (y > 4) { + break; + } + } + } + out + `), + )).run() + ) + .to.deep.include({value: [1, 1, 2, 2, 3, 3]}); +}); diff --git a/app/astride/traverse.js b/app/astride/traverse.js index 8735426..448659c 100644 --- a/app/astride/traverse.js +++ b/app/astride/traverse.js @@ -5,6 +5,7 @@ export const TRAVERSAL_PATH = { AwaitExpression: ['argument'], BinaryExpression: ['left', 'right'], BlockStatement: ['body'], + BreakStatement: [], CallExpression: ['arguments', 'callee'], ChainExpression: ['expression'], ConditionalExpression: ['alternate', 'consequent', 'test'], diff --git a/public/assets/tomato-seeds/projection-check.js b/public/assets/tomato-seeds/projection-check.js index 00422ae..09311de 100644 --- a/public/assets/tomato-seeds/projection-check.js +++ b/public/assets/tomato-seeds/projection-check.js @@ -15,6 +15,7 @@ for (const position of projected) { for (const {Plant} of entities) { if (Plant) { hasPlant = true + break; } } if (!hasPlant) {