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