diff --git a/app/astride/evaluate.js b/app/astride/evaluate.js index 6e3f6fe..2538de4 100644 --- a/app/astride/evaluate.js +++ b/app/astride/evaluate.js @@ -35,6 +35,8 @@ export default function evaluate(node, {scope} = {}) { return evaluators.binary(node, {evaluate, scope}); case 'MemberExpression': return evaluators.member(node, {evaluate, scope}); + case 'NewExpression': + return evaluators.new(node, {evaluate, scope}); case 'ObjectExpression': return evaluators.object(node, {evaluate, scope}); case 'UnaryExpression': diff --git a/app/astride/evaluators/new.js b/app/astride/evaluators/new.js new file mode 100644 index 0000000..2ed05b2 --- /dev/null +++ b/app/astride/evaluators/new.js @@ -0,0 +1,21 @@ +export default function(node, {evaluate, scope}) { + let asyncArgs = false; + const args = []; + for (let i = 0; i < node.arguments.length; i++) { + const arg = node.arguments[i]; + const {async, value} = evaluate(arg, {scope}); + asyncArgs ||= async; + args.push(value); + } + const {callee} = node; + const {async, value} = evaluate(callee, {scope}); + if (asyncArgs || async) { + return { + async: true, + value: Promise + .all([value, Promise.all(args)]) + .then(([callee, args]) => new callee(...args)), + }; + } + return {value: new value(...args)}; +} diff --git a/app/astride/evaluators/new.test.js b/app/astride/evaluators/new.test.js new file mode 100644 index 0000000..2b7170f --- /dev/null +++ b/app/astride/evaluators/new.test.js @@ -0,0 +1,48 @@ +import {expect, test} from 'vitest'; + +import evaluate from '@/astride/evaluate.js'; +import expression from '@/astride/test/expression.js'; + +const scopeTest = test.extend({ + scope: async ({}, use) => { + await use({ + S: {O: {}}, + get(k) { return this.S[k]; }, + set(k, v) { return this.S[k] = v; } + }); + }, +}); + +class C { + foo = 'bar'; + constructor(a, b) { + this.a = a; + this.b = b; + } +} + +scopeTest('creates instances', async ({scope}) => { + scope.set('C', C); + const evaluated = evaluate(await expression('new C(1, 2)'), {scope}); + expect(evaluated.value) + .to.deep.include({ + a: 1, + b: 2, + foo: 'bar', + }); +}); + +scopeTest('creates instances with async dependencies', async ({scope}) => { + scope.set('C', C); + scope.set('a', Promise.resolve(1)); + scope.set('b', Promise.resolve(2)); + const evaluated = evaluate(await expression('new C(await a, await b)'), {scope}); + expect(evaluated.async) + .to.equal(true); + expect(await evaluated.value) + .to.deep.include({ + a: 1, + b: 2, + foo: 'bar', + }); +}); diff --git a/app/astride/sandbox.js b/app/astride/sandbox.js index 5a92f7f..b49d585 100644 --- a/app/astride/sandbox.js +++ b/app/astride/sandbox.js @@ -150,6 +150,7 @@ export default class Sandbox { case 'ObjectExpression': case 'Identifier': case 'MemberExpression': + case 'NewExpression': case 'UpdateExpression': { result = this.evaluateToResult(node); if (result.yield) { diff --git a/app/astride/sandbox.test.js b/app/astride/sandbox.test.js index de48b3d..47243d7 100644 --- a/app/astride/sandbox.test.js +++ b/app/astride/sandbox.test.js @@ -121,6 +121,39 @@ test('runs arbitrary number of ops', async () => { .to.equal(150); }); +test('instantiates', async () => { + const sandbox = new Sandbox( + await parse(` + const x = new C(1, 2); + const y = new C(await a, await b); + `), + { + a: Promise.resolve(1), + b: Promise.resolve(2), + C: class { + foo = 'bar'; + constructor(a, b) { + this.a = a; + this.b = b; + } + }, + }, + ); + await finish(sandbox); + expect(sandbox.context.x) + .to.deep.include({ + a: 1, + b: 2, + foo: 'bar', + }); + expect(sandbox.context.y) + .to.deep.include({ + a: 1, + b: 2, + foo: 'bar', + }); +}); + test('deletes', async () => { const sandbox = new Sandbox( await parse(` diff --git a/app/astride/traverse.js b/app/astride/traverse.js index 9bbe352..b31b330 100644 --- a/app/astride/traverse.js +++ b/app/astride/traverse.js @@ -16,6 +16,7 @@ export const TRAVERSAL_PATH = { Identifier: [], IfStatement: ['alternate', 'consequent', 'test'], MemberExpression: ['object', 'property'], + NewExpression: ['arguments', 'callee'], Literal: [], LogicalExpression: ['left', 'right'], ObjectExpression: ['properties'],