118 lines
3.6 KiB
JavaScript
118 lines
3.6 KiB
JavaScript
import { ObjectTree, symbols, Tree } from "@weborigami/async-tree";
|
|
import assert from "node:assert";
|
|
import { describe, test } from "node:test";
|
|
import * as compile from "../../src/compiler/compile.js";
|
|
import { ops } from "../../src/runtime/internal.js";
|
|
import { stripCodeLocations } from "./stripCodeLocations.js";
|
|
|
|
const shared = new ObjectTree({
|
|
greet: (name) => `Hello, ${name}!`,
|
|
name: "Alice",
|
|
});
|
|
|
|
describe.only("compile", () => {
|
|
test("array", async () => {
|
|
await assertCompile("[]", []);
|
|
await assertCompile("[ 1, 2, 3, ]", [1, 2, 3]);
|
|
await assertCompile("[\n'a'\n'b'\n'c'\n]", ["a", "b", "c"]);
|
|
});
|
|
|
|
test("functionComposition", async () => {
|
|
await assertCompile("greet()", "Hello, undefined!");
|
|
await assertCompile("greet(name)", "Hello, Alice!");
|
|
await assertCompile("greet 'world'", "Hello, world!");
|
|
});
|
|
|
|
test("tree", async () => {
|
|
const fn = compile.expression("{ message = greet(name) }");
|
|
const tree = await fn.call(null);
|
|
tree[symbols.parent] = shared;
|
|
assert.deepEqual(await Tree.plain(tree), {
|
|
message: "Hello, Alice!",
|
|
});
|
|
});
|
|
|
|
test("number", async () => {
|
|
await assertCompile("1", 1);
|
|
await assertCompile("3.14159", 3.14159);
|
|
await assertCompile("-1", -1);
|
|
});
|
|
|
|
test("sync object", async () => {
|
|
await assertCompile("{a:1, b:2}", { a: 1, b: 2 });
|
|
await assertCompile("{ a: { b: { c: 0 } } }", { a: { b: { c: 0 } } });
|
|
});
|
|
|
|
test("async object", async () => {
|
|
const fn = compile.expression("{ a: { b = name }}");
|
|
const object = await fn.call(shared);
|
|
assert.deepEqual(await object["a/"].b, "Alice");
|
|
});
|
|
|
|
test("templateDocument", async () => {
|
|
const fn = compile.templateDocument("Documents can contain ` backticks");
|
|
const templateFn = await fn.call(shared);
|
|
const value = await templateFn.call(null);
|
|
assert.deepEqual(value, "Documents can contain ` backticks");
|
|
});
|
|
|
|
test("templateLiteral", async () => {
|
|
await assertCompile("`Hello, ${name}!`", "Hello, Alice!");
|
|
await assertCompile(
|
|
"`escape characters with \\`backslash\\``",
|
|
"escape characters with `backslash`"
|
|
);
|
|
});
|
|
|
|
test("tagged template string array is identical across calls", async () => {
|
|
let saved;
|
|
const scope = new ObjectTree({
|
|
tag: (strings, ...values) => {
|
|
assert.deepEqual(strings, ["Hello, ", "!"]);
|
|
if (saved) {
|
|
assert.equal(strings, saved);
|
|
} else {
|
|
saved = strings;
|
|
}
|
|
return strings[0] + values[0] + strings[1];
|
|
},
|
|
});
|
|
const program = compile.expression("=tag`Hello, ${_}!`");
|
|
const lambda = await program.call(scope);
|
|
const alice = await lambda("Alice");
|
|
assert.equal(alice, "Hello, Alice!");
|
|
const bob = await lambda("Bob");
|
|
assert.equal(bob, "Hello, Bob!");
|
|
});
|
|
|
|
test.only("converts non-local ops.scope calls to ops.external", async () => {
|
|
const expression = `
|
|
(name) => {
|
|
a: 1
|
|
b: a // local, should be left as ops.scope
|
|
c: nonLocal // non-local, should be converted to ops.cache
|
|
d: name // local, should be left as ops.scope
|
|
}
|
|
`;
|
|
const fn = compile.expression(expression);
|
|
const code = fn.code;
|
|
assert.deepEqual(stripCodeLocations(code), [
|
|
ops.lambda,
|
|
["name"],
|
|
[
|
|
ops.object,
|
|
["a", [ops.literal, 1]],
|
|
["b", [ops.scope, "a"]],
|
|
["c", [ops.external, "nonLocal", {}]],
|
|
["d", [ops.scope, "name"]],
|
|
],
|
|
]);
|
|
});
|
|
});
|
|
|
|
async function assertCompile(text, expected) {
|
|
const fn = compile.expression(text);
|
|
const result = await fn.call(shared);
|
|
assert.deepEqual(result, expected);
|
|
}
|