2024-06-22 10:47:17 -05:00
|
|
|
import {parse as acornParse} from 'acorn';
|
2024-06-16 08:01:01 -05:00
|
|
|
import {expect, test} from 'vitest';
|
|
|
|
|
2024-06-22 08:02:23 -05:00
|
|
|
import Sandbox from '@/astride/sandbox.js';
|
2024-06-16 08:01:01 -05:00
|
|
|
|
2024-06-22 10:47:17 -05:00
|
|
|
function parse(code, options = {}) {
|
|
|
|
return acornParse(code, {
|
|
|
|
ecmaVersion: 'latest',
|
|
|
|
sourceType: 'module',
|
|
|
|
...options,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-30 10:03:50 -05:00
|
|
|
async function finish(sandbox) {
|
|
|
|
let result;
|
|
|
|
let i = 0;
|
|
|
|
do {
|
|
|
|
result = sandbox.step();
|
|
|
|
if (result.async) {
|
|
|
|
await result.value;
|
|
|
|
}
|
|
|
|
} while (!result.done && ++i < 1000);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-06-16 08:01:01 -05:00
|
|
|
test('declares variables', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
2024-06-22 10:47:17 -05:00
|
|
|
const scalar = true ? +1 : 32;
|
2024-06-16 08:01:01 -05:00
|
|
|
const asyncScalar = await 2;
|
|
|
|
const array = [3, 4, 5];
|
|
|
|
const asyncArray = await [6, 7, 8];
|
|
|
|
const object = {9: '10'};
|
|
|
|
const asyncObject = await {11: '12'};
|
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
await finish(sandbox);
|
2024-06-16 08:01:01 -05:00
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
scalar: 1,
|
|
|
|
asyncScalar: 2,
|
|
|
|
array: [3, 4, 5],
|
|
|
|
asyncArray: [6, 7, 8],
|
|
|
|
object: {9: '10'},
|
|
|
|
asyncObject: {11: '12'},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-06-22 04:05:18 -05:00
|
|
|
test('scopes variables', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const result = [];
|
|
|
|
const scalar = 1;
|
|
|
|
result.push(scalar);
|
|
|
|
{
|
|
|
|
const scalar = 2;
|
|
|
|
result.push(scalar);
|
|
|
|
}
|
|
|
|
result.push(scalar);
|
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
await finish(sandbox);
|
2024-06-22 04:05:18 -05:00
|
|
|
expect(sandbox.context.result)
|
|
|
|
.to.deep.equal([1, 2, 1]);
|
|
|
|
});
|
|
|
|
|
2024-06-30 10:03:50 -05:00
|
|
|
test('returns last', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
foo = 32;
|
|
|
|
{
|
|
|
|
bar = 64;
|
|
|
|
}
|
|
|
|
`),
|
|
|
|
);
|
|
|
|
expect(await finish(sandbox))
|
|
|
|
.to.deep.include({done: true, value: 64});
|
|
|
|
});
|
|
|
|
|
2024-06-16 08:01:01 -05:00
|
|
|
test('destructures variables', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const [a, , c] = [1, 2, 3];
|
|
|
|
const {x: x1, y, z: {zz}} = {x: 4, y: 5, z: {zz: 6}};
|
|
|
|
const [d, e] = await [7, 8];
|
|
|
|
const {t, u: {uu}} = {t: 9, u: {uu: await 10}};
|
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
await finish(sandbox);
|
2024-06-16 08:01:01 -05:00
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
a: 1,
|
|
|
|
c: 3,
|
|
|
|
x1: 4,
|
|
|
|
y: 5,
|
|
|
|
zz: 6,
|
|
|
|
d: 7,
|
|
|
|
e: 8,
|
|
|
|
t: 9,
|
|
|
|
uu: 10,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('runs arbitrary number of ops', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const foo = [];
|
2024-06-30 10:03:50 -05:00
|
|
|
for (let i = 0; i < 150; ++i) {
|
2024-06-16 08:01:01 -05:00
|
|
|
foo.push(i);
|
|
|
|
}
|
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
sandbox.run(100);
|
2024-06-16 08:01:01 -05:00
|
|
|
expect(sandbox.context.foo.length)
|
2024-06-30 10:03:50 -05:00
|
|
|
.to.equal(100);
|
|
|
|
sandbox.run(100);
|
|
|
|
expect(sandbox.context.foo.length)
|
|
|
|
.to.equal(150);
|
2024-06-16 08:01:01 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
test('evaluates conditional branches', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
let foo, bar;
|
|
|
|
if (true) {
|
|
|
|
foo = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
foo = 2;
|
|
|
|
}
|
|
|
|
if (await false) {
|
|
|
|
bar = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
bar = 2;
|
|
|
|
}
|
2024-06-30 11:27:42 -05:00
|
|
|
if (await false) {
|
|
|
|
bar = 1;
|
|
|
|
}
|
2024-06-16 08:01:01 -05:00
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
await finish(sandbox);
|
2024-06-16 08:01:01 -05:00
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
foo: 1,
|
|
|
|
bar: 2,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-06-30 14:20:37 -05:00
|
|
|
test('evaluates conditional expressions', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const x = true || false ? 1 : 2
|
|
|
|
const y = false && true ? 1 : 2
|
|
|
|
const a = (await true) ? await 1 : await 2
|
|
|
|
const b = (await false) ? await 1 : await 2
|
|
|
|
xx = true || false ? 1 : 2
|
|
|
|
yy = false && true ? 1 : 2
|
|
|
|
aa = (await true) ? await 1 : await 2
|
|
|
|
bb = (await false) ? await 1 : await 2
|
|
|
|
`),
|
|
|
|
);
|
|
|
|
await finish(sandbox);
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
x: 1,
|
|
|
|
y: 2,
|
|
|
|
a: 1,
|
|
|
|
b: 2,
|
|
|
|
xx: 1,
|
|
|
|
yy: 2,
|
|
|
|
aa: 1,
|
|
|
|
bb: 2,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-06-16 08:01:01 -05:00
|
|
|
test('evaluates loops', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
2024-06-30 10:03:50 -05:00
|
|
|
x = 0
|
|
|
|
y = 0
|
|
|
|
a = 0
|
|
|
|
b = 0
|
|
|
|
c = 0
|
2024-06-30 14:20:37 -05:00
|
|
|
d = 0
|
2024-06-16 08:01:01 -05:00
|
|
|
for (let i = 0; i < 3; ++i) {
|
|
|
|
x += 1;
|
|
|
|
}
|
|
|
|
for (let i = await 0; i < await 3; i = 1 + await i) {
|
|
|
|
y += 1;
|
|
|
|
}
|
|
|
|
do {
|
|
|
|
a += 1;
|
|
|
|
} while (a < 3);
|
|
|
|
do {
|
2024-06-30 14:20:37 -05:00
|
|
|
b += await 1;
|
2024-06-16 08:01:01 -05:00
|
|
|
} while (await b < 3);
|
|
|
|
while (c < 3) {
|
|
|
|
c += 1;
|
|
|
|
}
|
2024-06-30 14:20:37 -05:00
|
|
|
while (await d < 3) {
|
|
|
|
d += await 1;
|
|
|
|
}
|
2024-06-16 08:01:01 -05:00
|
|
|
`),
|
|
|
|
);
|
2024-06-30 10:03:50 -05:00
|
|
|
await finish(sandbox);
|
2024-06-16 08:01:01 -05:00
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
a: 3,
|
|
|
|
b: 3,
|
|
|
|
c: 3,
|
2024-06-30 14:20:37 -05:00
|
|
|
d: 3,
|
2024-06-16 08:01:01 -05:00
|
|
|
x: 3,
|
|
|
|
y: 3,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-06-18 22:29:40 -05:00
|
|
|
test('evaluates undefined for nonexistent variables in scope', async () => {
|
2024-06-16 08:01:01 -05:00
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const x = y
|
|
|
|
`),
|
|
|
|
);
|
|
|
|
sandbox.run();
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
x: undefined,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-06-18 22:29:40 -05:00
|
|
|
test('returns values at the top level', async () => {
|
|
|
|
let sandbox;
|
|
|
|
sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
x = 16
|
|
|
|
y = 4
|
|
|
|
return [x * 3, y * 3]
|
|
|
|
x = 32
|
|
|
|
y = 8
|
|
|
|
`, {allowReturnOutsideFunction: true}),
|
|
|
|
);
|
2024-06-30 14:20:37 -05:00
|
|
|
expect(sandbox.run().value)
|
|
|
|
.to.deep.equal([48, 12]);
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({x: 16, y: 4});
|
|
|
|
sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
x = 16
|
|
|
|
y = 4
|
|
|
|
return await [x * 3, y * 3]
|
|
|
|
x = 32
|
|
|
|
y = 8
|
|
|
|
`, {allowReturnOutsideFunction: true}),
|
|
|
|
);
|
|
|
|
expect((await finish(sandbox)).value)
|
2024-06-18 22:29:40 -05:00
|
|
|
.to.deep.equal([48, 12]);
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({x: 16, y: 4});
|
|
|
|
sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
x = 16
|
|
|
|
y = 4
|
|
|
|
return
|
|
|
|
x = 32
|
|
|
|
y = 8
|
|
|
|
`, {allowReturnOutsideFunction: true}),
|
|
|
|
);
|
|
|
|
sandbox.run();
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({x: 16, y: 4});
|
|
|
|
});
|
|
|
|
|
2024-06-30 13:05:53 -05:00
|
|
|
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});
|
|
|
|
});
|
|
|
|
|
2024-06-16 08:01:01 -05:00
|
|
|
test('sets variables in global scope', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
x = y
|
|
|
|
`),
|
|
|
|
);
|
|
|
|
sandbox.run();
|
|
|
|
expect(sandbox.context)
|
|
|
|
.to.deep.equal({
|
|
|
|
x: undefined,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('runs arbitrary number of ops', async () => {
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
const foo = [];
|
|
|
|
for (let i = 0; i < 1500; ++i) {
|
|
|
|
foo.push(i);
|
|
|
|
}
|
|
|
|
`),
|
|
|
|
);
|
|
|
|
sandbox.run(1000);
|
|
|
|
expect(sandbox.context.foo.length)
|
|
|
|
.to.equal(1000);
|
|
|
|
sandbox.run(1000);
|
2024-06-28 07:03:07 -05:00
|
|
|
expect(sandbox.context.foo.length)
|
2024-06-16 08:01:01 -05:00
|
|
|
.to.equal(1500);
|
|
|
|
expect(true)
|
|
|
|
.to.be.true;
|
|
|
|
});
|
2024-06-30 10:03:50 -05:00
|
|
|
|
|
|
|
test('clears for loop deferred context', async () => {
|
|
|
|
const context = {x: 10};
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
let l = 0
|
|
|
|
for (let i = 0; i < x; ++i) {
|
|
|
|
l += 1
|
|
|
|
}
|
|
|
|
`),
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
expect(sandbox.run())
|
|
|
|
.to.deep.include({
|
|
|
|
value: 10,
|
|
|
|
});
|
|
|
|
context.x = 0
|
|
|
|
expect(sandbox.run())
|
|
|
|
.to.deep.include({
|
|
|
|
value: undefined,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
test('clears while loop deferred context', async () => {
|
|
|
|
const context = {x: 10};
|
|
|
|
const sandbox = new Sandbox(
|
|
|
|
await parse(`
|
|
|
|
let l = 0
|
|
|
|
while (l < x) {
|
|
|
|
l += 1
|
|
|
|
}
|
|
|
|
`),
|
|
|
|
context,
|
|
|
|
);
|
|
|
|
expect(sandbox.run())
|
|
|
|
.to.deep.include({
|
|
|
|
value: 10,
|
|
|
|
});
|
|
|
|
context.x = 0
|
|
|
|
expect(sandbox.run())
|
|
|
|
.to.deep.include({
|
|
|
|
value: undefined,
|
|
|
|
});
|
|
|
|
});
|
2024-06-30 13:05:53 -05:00
|
|
|
|
|
|
|
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});
|
|
|
|
});
|