feat: new

This commit is contained in:
cha0s 2024-07-25 09:00:54 -05:00
parent ce802a8499
commit b9985a573d
6 changed files with 106 additions and 0 deletions

View File

@ -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':

View File

@ -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)};
}

View File

@ -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',
});
});

View File

@ -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) {

View File

@ -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(`

View File

@ -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'],