Files
sashinexists/node_modules/@weborigami/language/test/compiler/parse.test.js
2024-12-07 13:18:31 +11:00

1070 lines
29 KiB
JavaScript

import assert from "node:assert";
import { describe, test } from "node:test";
import { parse } from "../../src/compiler/parse.js";
import { undetermined } from "../../src/compiler/parserHelpers.js";
import * as ops from "../../src/runtime/ops.js";
import { stripCodeLocations } from "./stripCodeLocations.js";
describe("Origami parser", () => {
test("additiveExpression", () => {
assertParse("additiveExpression", "1 + 2", [
ops.addition,
[ops.literal, 1],
[ops.literal, 2],
]);
assertParse("additiveExpression", "5 - 4", [
ops.subtraction,
[ops.literal, 5],
[ops.literal, 4],
]);
});
test("arrayLiteral", () => {
assertParse("arrayLiteral", "[]", [ops.array]);
assertParse("arrayLiteral", "[1, 2, 3]", [
ops.array,
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("arrayLiteral", "[ 1 , 2 , 3 ]", [
ops.array,
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("arrayLiteral", "[1,,,4]", [
ops.array,
[ops.literal, 1],
[ops.literal, undefined],
[ops.literal, undefined],
[ops.literal, 4],
]);
assertParse("arrayLiteral", "[ 1, ...[2, 3]]", [
ops.merge,
[ops.array, [ops.literal, 1]],
[ops.array, [ops.literal, 2], [ops.literal, 3]],
]);
assertParse(
"arrayLiteral",
`[
1
2
3
]`,
[ops.array, [ops.literal, 1], [ops.literal, 2], [ops.literal, 3]]
);
});
test("arrowFunction", () => {
assertParse("arrowFunction", "() => foo", [
ops.lambda,
[],
[ops.scope, "foo"],
]);
assertParse("arrowFunction", "x => y", [
ops.lambda,
["x"],
[ops.scope, "y"],
]);
assertParse("arrowFunction", "(a, b, c) ⇒ fn(a, b, c)", [
ops.lambda,
["a", "b", "c"],
[
[ops.builtin, "fn"],
[ops.scope, "a"],
[ops.scope, "b"],
[ops.scope, "c"],
],
]);
assertParse("arrowFunction", "a => b => fn(a, b)", [
ops.lambda,
["a"],
[
ops.lambda,
["b"],
[
[ops.builtin, "fn"],
[ops.scope, "a"],
[ops.scope, "b"],
],
],
]);
});
test("bitwiseAndExpression", () => {
assertParse("bitwiseAndExpression", "5 & 3", [
ops.bitwiseAnd,
[ops.literal, 5],
[ops.literal, 3],
]);
});
test("bitwiseOrExpression", () => {
assertParse("bitwiseOrExpression", "5 | 3", [
ops.bitwiseOr,
[ops.literal, 5],
[ops.literal, 3],
]);
});
test("bitwiseXorExpression", () => {
assertParse("bitwiseXorExpression", "5 ^ 3", [
ops.bitwiseXor,
[ops.literal, 5],
[ops.literal, 3],
]);
});
test("callExpression", () => {
assertParse("callExpression", "fn()", [[ops.builtin, "fn"], undefined]);
assertParse("callExpression", "foo.js(arg)", [
[ops.scope, "foo.js"],
[ops.scope, "arg"],
]);
assertParse("callExpression", "fn(a, b)", [
[ops.builtin, "fn"],
[ops.scope, "a"],
[ops.scope, "b"],
]);
assertParse("callExpression", "foo.js( a , b )", [
[ops.scope, "foo.js"],
[ops.scope, "a"],
[ops.scope, "b"],
]);
assertParse("callExpression", "fn()(arg)", [
[[ops.builtin, "fn"], undefined],
[ops.scope, "arg"],
]);
assertParse("callExpression", "tree/", [ops.unpack, [ops.scope, "tree/"]]);
assertParse("callExpression", "tree/foo/bar", [
ops.traverse,
[ops.scope, "tree/"],
[ops.literal, "foo/"],
[ops.literal, "bar"],
]);
assertParse("callExpression", "tree/foo/bar/", [
ops.traverse,
[ops.scope, "tree/"],
[ops.literal, "foo/"],
[ops.literal, "bar/"],
]);
assertParse("callExpression", "/foo/bar", [
ops.traverse,
[ops.rootDirectory, [ops.literal, "foo/"]],
[ops.literal, "bar"],
]);
assertParse("callExpression", "foo.js()/key", [
ops.traverse,
[[ops.scope, "foo.js"], undefined],
[ops.literal, "key"],
]);
assertParse("callExpression", "tree/key()", [
[ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
undefined,
]);
assertParse("callExpression", "(tree)/", [ops.unpack, [ops.scope, "tree"]]);
assertParse("callExpression", "fn()/key()", [
[ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
undefined,
]);
assertParse("callExpression", "(foo.js())('arg')", [
[[ops.scope, "foo.js"], undefined],
[ops.literal, "arg"],
]);
assertParse("callExpression", "fn('a')('b')", [
[
[ops.builtin, "fn"],
[ops.literal, "a"],
],
[ops.literal, "b"],
]);
assertParse("callExpression", "(foo.js())(a, b)", [
[[ops.scope, "foo.js"], undefined],
[ops.scope, "a"],
[ops.scope, "b"],
]);
assertParse("callExpression", "{ a: 1, b: 2}/b", [
ops.traverse,
[ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
[ops.literal, "b"],
]);
assertParse("callExpression", "indent`hello`", [
[ops.builtin, "indent"],
[ops.literal, ["hello"]],
]);
assertParse("callExpression", "fn.js`Hello, world.`", [
[ops.scope, "fn.js"],
[ops.literal, ["Hello, world."]],
]);
assertParse("callExpression", "files:src/assets", [
ops.traverse,
[
[ops.builtin, "files:"],
[ops.literal, "src/"],
],
[ops.literal, "assets"],
]);
assertParse("callExpression", "new:(js:Date, '2025-01-01')", [
[ops.builtin, "new:"],
[
[ops.builtin, "js:"],
[ops.literal, "Date"],
],
[ops.literal, "2025-01-01"],
]);
assertParse("callExpression", "map(markdown, mdHtml)", [
[ops.builtin, "map"],
[ops.scope, "markdown"],
[ops.scope, "mdHtml"],
]);
assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
[
ops.traverse,
[
[ops.builtin, "package:"],
[ops.literal, "@weborigami/"],
],
[ops.literal, "dropbox/"],
[ops.literal, "auth"],
],
[ops.scope, "creds"],
]);
});
test("commaExpression", () => {
assertParse("commaExpression", "1", [ops.literal, 1]);
assertParse("commaExpression", "a, b, c", [
ops.comma,
[ops.scope, "a"],
[ops.scope, "b"],
[ops.scope, "c"],
]);
});
test("conditionalExpression", () => {
assertParse("conditionalExpression", "1", [ops.literal, 1]);
assertParse("conditionalExpression", "true ? 1 : 0", [
ops.conditional,
[ops.scope, "true"],
[ops.lambda, [], [ops.literal, "1"]],
[ops.lambda, [], [ops.literal, "0"]],
]);
assertParse("conditionalExpression", "false ? () => 1 : 0", [
ops.conditional,
[ops.scope, "false"],
[ops.lambda, [], [ops.lambda, [], [ops.literal, "1"]]],
[ops.lambda, [], [ops.literal, "0"]],
]);
assertParse("conditionalExpression", "false ? =1 : 0", [
ops.conditional,
[ops.scope, "false"],
[ops.lambda, [], [ops.lambda, ["_"], [ops.literal, "1"]]],
[ops.lambda, [], [ops.literal, "0"]],
]);
});
test("equalityExpression", () => {
assertParse("equalityExpression", "1 === 1", [
ops.strictEqual,
[ops.literal, 1],
[ops.literal, 1],
]);
assertParse("equalityExpression", "a === b === c", [
ops.strictEqual,
[ops.strictEqual, [undetermined, "a"], [undetermined, "b"]],
[undetermined, "c"],
]);
assertParse("equalityExpression", "1 !== 1", [
ops.notStrictEqual,
[ops.literal, 1],
[ops.literal, 1],
]);
});
test("exponentiationExpression", () => {
assertParse("exponentiationExpression", "2 ** 2 ** 3", [
ops.exponentiation,
[ops.literal, 2],
[ops.exponentiation, [ops.literal, 2], [ops.literal, 3]],
]);
});
test("expression", () => {
assertParse(
"expression",
`
{
index.html = index.ori(teamData.yaml)
thumbnails = map(images, { value: thumbnail.js })
}
`,
[
ops.object,
[
"index.html",
[
ops.getter,
[
[ops.scope, "index.ori"],
[ops.scope, "teamData.yaml"],
],
],
],
[
"thumbnails",
[
ops.getter,
[
[ops.builtin, "map"],
[ops.scope, "images"],
[ops.object, ["value", [ops.scope, "thumbnail.js"]]],
],
],
],
]
);
// Builtin on its own is the function itself, not a function call
assertParse("expression", "mdHtml:", [ops.builtin, "mdHtml:"]);
// Consecutive slahes in a path are removed
assertParse("expression", "path//key", [
ops.traverse,
[ops.scope, "path/"],
[ops.literal, "key"],
]);
// Single slash at start of something = absolute file path
assertParse("expression", "/path", [
ops.rootDirectory,
[ops.literal, "path"],
]);
// Consecutive slashes at start of something = comment
assertParse("expression", "path //comment", [ops.scope, "path"], false);
assertParse("expression", "page.ori(mdHtml:(about.md))", [
[ops.scope, "page.ori"],
[
[ops.builtin, "mdHtml:"],
[ops.scope, "about.md"],
],
]);
// Slash on its own is the root folder
assertParse("expression", "keys /", [
[ops.builtin, "keys"],
[ops.rootDirectory],
]);
assertParse("expression", "'Hello' -> test.orit", [
[ops.scope, "test.orit"],
[ops.literal, "Hello"],
]);
assertParse("expression", "obj.json", [ops.scope, "obj.json"]);
assertParse("expression", "(fn a, b, c)", [
[ops.builtin, "fn"],
[undetermined, "a"],
[undetermined, "b"],
[undetermined, "c"],
]);
assertParse("expression", "foo.bar('hello', 'world')", [
[ops.scope, "foo.bar"],
[ops.literal, "hello"],
[ops.literal, "world"],
]);
assertParse("expression", "(key)('a')", [
[ops.scope, "key"],
[ops.literal, "a"],
]);
assertParse("expression", "1", [ops.literal, 1]);
assertParse("expression", "{ a: 1, b: 2 }", [
ops.object,
["a", [ops.literal, 1]],
["b", [ops.literal, 2]],
]);
assertParse("expression", "serve { index.html: 'hello' }", [
[ops.builtin, "serve"],
[ops.object, ["index.html", [ops.literal, "hello"]]],
]);
assertParse("expression", "fn =`x`", [
[ops.builtin, "fn"],
[ops.lambda, ["_"], [ops.template, [ops.literal, ["x"]]]],
]);
assertParse("expression", "copy app.js(formulas), files:snapshot", [
[ops.builtin, "copy"],
[
[ops.scope, "app.js"],
[ops.scope, "formulas"],
],
[
[ops.builtin, "files:"],
[ops.literal, "snapshot"],
],
]);
assertParse("expression", "map =`<li>${_}</li>`", [
[ops.builtin, "map"],
[
ops.lambda,
["_"],
[
ops.template,
[ops.literal, ["<li>", "</li>"]],
[ops.concat, [ops.scope, "_"]],
],
],
]);
assertParse("expression", `https://example.com/about/`, [
[ops.builtin, "https:"],
[ops.literal, "example.com/"],
[ops.literal, "about/"],
]);
assertParse("expression", "tag`Hello, ${name}!`", [
[ops.builtin, "tag"],
[ops.literal, ["Hello, ", "!"]],
[ops.concat, [ops.scope, "name"]],
]);
assertParse("expression", "(post, slug) => fn.js(post, slug)", [
ops.lambda,
["post", "slug"],
[
[ops.scope, "fn.js"],
[ops.scope, "post"],
[ops.scope, "slug"],
],
]);
// Verify parser treatment of identifiers containing operators
assertParse("expression", "a + b", [
ops.addition,
[undetermined, "a"],
[undetermined, "b"],
]);
assertParse("expression", "a+b", [ops.scope, "a+b"]);
assertParse("expression", "a - b", [
ops.subtraction,
[undetermined, "a"],
[undetermined, "b"],
]);
assertParse("expression", "a-b", [ops.scope, "a-b"]);
assertParse("expression", "a&b", [ops.scope, "a&b"]);
assertParse("expression", "a & b", [
ops.bitwiseAnd,
[undetermined, "a"],
[undetermined, "b"],
]);
});
test("group", () => {
assertParse("group", "(hello)", [ops.scope, "hello"]);
assertParse("group", "(((nested)))", [ops.scope, "nested"]);
assertParse("group", "(fn())", [[ops.builtin, "fn"], undefined]);
assertParse("group", "(a -> b)", [
[ops.builtin, "b"],
[ops.scope, "a"],
]);
});
test("homeDirectory", () => {
assertParse("homeDirectory", "~", [ops.homeDirectory]);
});
test("host", () => {
assertParse("host", "abc", [ops.literal, "abc"]);
assertParse("host", "abc:123", [ops.literal, "abc:123"]);
assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
});
test("identifier", () => {
assertParse("identifier", "abc", "abc", false);
assertParse("identifier", "index.html", "index.html", false);
assertParse("identifier", "foo\\ bar", "foo bar", false);
assertParse("identifier", "x-y-z", "x-y-z", false);
});
test("implicitParenthesesCallExpression", () => {
assertParse("implicitParenthesesCallExpression", "fn arg", [
[ops.builtin, "fn"],
[undetermined, "arg"],
]);
assertParse("implicitParenthesesCallExpression", "page.ori 'a', 'b'", [
[ops.scope, "page.ori"],
[ops.literal, "a"],
[ops.literal, "b"],
]);
assertParse("implicitParenthesesCallExpression", "fn a(b), c", [
[ops.builtin, "fn"],
[
[ops.builtin, "a"],
[ops.scope, "b"],
],
[undetermined, "c"],
]);
assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
[[ops.builtin, "fn"], undefined],
[ops.literal, "arg"],
]);
assertParse("implicitParenthesesCallExpression", "tree/key arg", [
[ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
[undetermined, "arg"],
]);
assertParse("implicitParenthesesCallExpression", "foo.js bar.ori 'arg'", [
[ops.scope, "foo.js"],
[
[ops.scope, "bar.ori"],
[ops.literal, "arg"],
],
]);
});
test("list", () => {
assertParse("list", "1", [[ops.literal, 1]]);
assertParse("list", "1,2,3", [
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("list", "1, 2, 3,", [
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("list", "1 , 2 , 3", [
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("list", "1\n2\n3", [
[ops.literal, 1],
[ops.literal, 2],
[ops.literal, 3],
]);
assertParse("list", "'a' , 'b' , 'c'", [
[ops.literal, "a"],
[ops.literal, "b"],
[ops.literal, "c"],
]);
});
test("logicalAndExpression", () => {
assertParse("logicalAndExpression", "true && false", [
ops.logicalAnd,
[ops.scope, "true"],
[ops.lambda, [], [undetermined, "false"]],
]);
});
test("logicalOrExpression", () => {
assertParse("logicalOrExpression", "1 || 0", [
ops.logicalOr,
[ops.literal, 1],
[ops.literal, 0],
]);
assertParse("logicalOrExpression", "false || false || true", [
ops.logicalOr,
[ops.scope, "false"],
[ops.lambda, [], [undetermined, "false"]],
[ops.lambda, [], [undetermined, "true"]],
]);
assertParse("logicalOrExpression", "1 || 2 && 0", [
ops.logicalOr,
[ops.literal, 1],
[ops.lambda, [], [ops.logicalAnd, [ops.literal, 2], [ops.literal, 0]]],
]);
});
test("multiLineComment", () => {
assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
});
test("multiplicativeExpression", () => {
assertParse("multiplicativeExpression", "3 * 4", [
ops.multiplication,
[ops.literal, 3],
[ops.literal, 4],
]);
assertParse("multiplicativeExpression", "5 / 2", [
ops.division,
[ops.literal, 5],
[ops.literal, 2],
]);
assertParse("multiplicativeExpression", "6 % 5", [
ops.remainder,
[ops.literal, 6],
[ops.literal, 5],
]);
});
test("namespace", () => {
assertParse("namespace", "js:", [ops.builtin, "js:"]);
});
test("nullishCoalescingExpression", () => {
assertParse("nullishCoalescingExpression", "a ?? b", [
ops.nullishCoalescing,
[ops.scope, "a"],
[ops.lambda, [], [undetermined, "b"]],
]);
assertParse("nullishCoalescingExpression", "a ?? b ?? c", [
ops.nullishCoalescing,
[ops.scope, "a"],
[ops.lambda, [], [undetermined, "b"]],
[ops.lambda, [], [undetermined, "c"]],
]);
});
test("numericLiteral", () => {
assertParse("numericLiteral", "123", [ops.literal, 123]);
assertParse("numericLiteral", ".5", [ops.literal, 0.5]);
assertParse("numericLiteral", "123.45", [ops.literal, 123.45]);
});
test("objectLiteral", () => {
assertParse("objectLiteral", "{}", [ops.object]);
assertParse("objectLiteral", "{ a: 1, b }", [
ops.object,
["a", [ops.literal, 1]],
["b", [ops.inherited, "b"]],
]);
assertParse("objectLiteral", "{ sub: { a: 1 } }", [
ops.object,
["sub", [ops.object, ["a", [ops.literal, 1]]]],
]);
assertParse("objectLiteral", "{ sub: { a/: 1 } }", [
ops.object,
["sub", [ops.object, ["a/", [ops.literal, 1]]]],
]);
assertParse("objectLiteral", `{ "a": 1, "b": 2 }`, [
ops.object,
["a", [ops.literal, 1]],
["b", [ops.literal, 2]],
]);
assertParse("objectLiteral", "{ a = b, b = 2 }", [
ops.object,
["a", [ops.getter, [ops.scope, "b"]]],
["b", [ops.literal, 2]],
]);
assertParse(
"objectLiteral",
`{
a = b
b = 2
}`,
[
ops.object,
["a", [ops.getter, [ops.scope, "b"]]],
["b", [ops.literal, 2]],
]
);
assertParse("objectLiteral", "{ a: { b: 1 } }", [
ops.object,
["a", [ops.object, ["b", [ops.literal, 1]]]],
]);
assertParse("objectLiteral", "{ a: { b = 1 } }", [
ops.object,
["a", [ops.object, ["b", [ops.literal, 1]]]],
]);
assertParse("objectLiteral", "{ a: { b = fn() } }", [
ops.object,
[
"a/",
[ops.object, ["b", [ops.getter, [[ops.builtin, "fn"], undefined]]]],
],
]);
assertParse("objectLiteral", "{ x = fn.js('a') }", [
ops.object,
[
"x",
[
ops.getter,
[
[ops.scope, "fn.js"],
[ops.literal, "a"],
],
],
],
]);
assertParse("objectLiteral", "{ a: 1, ...b }", [
ops.merge,
[ops.object, ["a", [ops.literal, 1]]],
[undetermined, "b"],
]);
assertParse("objectLiteral", "{ (a): 1 }", [
ops.object,
["(a)", [ops.literal, 1]],
]);
});
test("objectEntry", () => {
assertParse("objectEntry", "foo", ["foo", [ops.inherited, "foo"]]);
assertParse("objectEntry", "x: y", ["x", [ops.scope, "y"]]);
assertParse("objectEntry", "a: a", ["a", [ops.inherited, "a"]]);
assertParse("objectEntry", "a: (a) => a", [
"a",
[ops.lambda, ["a"], [ops.scope, "a"]],
]);
assertParse("objectEntry", "posts/: map(posts, post.ori)", [
"posts/",
[
[ops.builtin, "map"],
[ops.inherited, "posts"],
[ops.scope, "post.ori"],
],
]);
});
test("objectGetter", () => {
assertParse("objectGetter", "data = obj.json", [
"data",
[ops.getter, [ops.scope, "obj.json"]],
]);
assertParse("objectGetter", "foo = page.ori 'bar'", [
"foo",
[
ops.getter,
[
[ops.scope, "page.ori"],
[ops.literal, "bar"],
],
],
]);
});
test("objectProperty", () => {
assertParse("objectProperty", "a: 1", ["a", [ops.literal, 1]]);
assertParse("objectProperty", "name: 'Alice'", [
"name",
[ops.literal, "Alice"],
]);
assertParse("objectProperty", "x: fn('a')", [
"x",
[
[ops.builtin, "fn"],
[ops.literal, "a"],
],
]);
});
test("objectPublicKey", () => {
assertParse("objectPublicKey", "a", "a", false);
assertParse("objectPublicKey", "markdown/", "markdown/", false);
assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
});
test("parenthesesArguments", () => {
assertParse("parenthesesArguments", "()", [undefined]);
assertParse("parenthesesArguments", "(a, b, c)", [
[ops.scope, "a"],
[ops.scope, "b"],
[ops.scope, "c"],
]);
});
test("path", () => {
assertParse("path", "/tree/", [[ops.literal, "tree/"]]);
assertParse("path", "/month/12", [
[ops.literal, "month/"],
[ops.literal, "12"],
]);
assertParse("path", "/tree/foo/bar", [
[ops.literal, "tree/"],
[ops.literal, "foo/"],
[ops.literal, "bar"],
]);
assertParse("path", "/a///b", [
[ops.literal, "a/"],
[ops.literal, "b"],
]);
});
test("pathArguments", () => {
assertParse("pathArguments", "/", [ops.traverse]);
assertParse("pathArguments", "/tree", [
ops.traverse,
[ops.literal, "tree"],
]);
assertParse("pathArguments", "/tree/", [
ops.traverse,
[ops.literal, "tree/"],
]);
});
test("pipelineExpression", () => {
assertParse("pipelineExpression", "foo", [ops.scope, "foo"]);
assertParse("pipelineExpression", "a -> b", [
[ops.builtin, "b"],
[ops.scope, "a"],
]);
assertParse("pipelineExpression", "input → one.js → two.js", [
[ops.scope, "two.js"],
[
[ops.scope, "one.js"],
[ops.scope, "input"],
],
]);
assertParse("pipelineExpression", "fn a -> b", [
[ops.builtin, "b"],
[
[ops.builtin, "fn"],
[undetermined, "a"],
],
]);
});
test("primary", () => {
assertParse("primary", "foo.js", [ops.scope, "foo.js"]);
assertParse("primary", "[1, 2]", [
ops.array,
[ops.literal, 1],
[ops.literal, 2],
]);
});
test("program", () => {
assertParse(
"program",
`#!/usr/bin/env ori invoke
'Hello'
`,
[ops.literal, "Hello"],
false
);
});
test("protocolExpression", () => {
assertParse("protocolExpression", "foo://bar", [
[ops.builtin, "foo:"],
[ops.literal, "bar"],
]);
assertParse("protocolExpression", "http://example.com", [
[ops.builtin, "http:"],
[ops.literal, "example.com"],
]);
assertParse("protocolExpression", "https://example.com/about/", [
[ops.builtin, "https:"],
[ops.literal, "example.com/"],
[ops.literal, "about/"],
]);
assertParse("protocolExpression", "https://example.com/about/index.html", [
[ops.builtin, "https:"],
[ops.literal, "example.com/"],
[ops.literal, "about/"],
[ops.literal, "index.html"],
]);
assertParse("protocolExpression", "http://localhost:5000/foo", [
[ops.builtin, "http:"],
[ops.literal, "localhost:5000/"],
[ops.literal, "foo"],
]);
});
test("qualifiedReference", () => {
assertParse("qualifiedReference", "js:Date", [
[ops.builtin, "js:"],
[ops.literal, "Date"],
]);
});
test("relationalExpression", () => {
assertParse("relationalExpression", "1 < 2", [
ops.lessThan,
[ops.literal, 1],
[ops.literal, 2],
]);
assertParse("relationalExpression", "3 > 4", [
ops.greaterThan,
[ops.literal, 3],
[ops.literal, 4],
]);
assertParse("relationalExpression", "5 <= 6", [
ops.lessThanOrEqual,
[ops.literal, 5],
[ops.literal, 6],
]);
assertParse("relationalExpression", "7 >= 8", [
ops.greaterThanOrEqual,
[ops.literal, 7],
[ops.literal, 8],
]);
});
test("rootDirectory", () => {
assertParse("rootDirectory", "/", [ops.rootDirectory]);
});
test("scopeReference", () => {
assertParse("scopeReference", "keys", [undetermined, "keys"]);
assertParse("scopeReference", "greet.js", [ops.scope, "greet.js"]);
// scopeReference checks whether a slash follows; hard to test in isolation
// assertParse("scopeReference", "markdown/", [ops.scope, "markdown"]);
});
test("shiftExpression", () => {
assertParse("shiftExpression", "1 << 2", [
ops.shiftLeft,
[ops.literal, 1],
[ops.literal, 2],
]);
assertParse("shiftExpression", "3 >> 4", [
ops.shiftRightSigned,
[ops.literal, 3],
[ops.literal, 4],
]);
assertParse("shiftExpression", "5 >>> 6", [
ops.shiftRightUnsigned,
[ops.literal, 5],
[ops.literal, 6],
]);
});
test("shorthandFunction", () => {
assertParse("shorthandFunction", "=message", [
ops.lambda,
["_"],
[undetermined, "message"],
]);
assertParse("shorthandFunction", "=`Hello, ${name}.`", [
ops.lambda,
["_"],
[
ops.template,
[ops.literal, ["Hello, ", "."]],
[ops.concat, [ops.scope, "name"]],
],
]);
assertParse("shorthandFunction", "=indent`hello`", [
ops.lambda,
["_"],
[
[ops.builtin, "indent"],
[ops.literal, ["hello"]],
],
]);
});
test("singleLineComment", () => {
assertParse("singleLineComment", "// Hello, world!", null, false);
});
test("spread", () => {
assertParse("spread", "...a", [ops.spread, [undetermined, "a"]]);
assertParse("spread", "…a", [ops.spread, [undetermined, "a"]]);
});
test("stringLiteral", () => {
assertParse("stringLiteral", '"foo"', [ops.literal, "foo"]);
assertParse("stringLiteral", "'bar'", [ops.literal, "bar"]);
assertParse("stringLiteral", '"foo bar"', [ops.literal, "foo bar"]);
assertParse("stringLiteral", "'bar baz'", [ops.literal, "bar baz"]);
assertParse("stringLiteral", `"foo\\"s bar"`, [ops.literal, `foo"s bar`]);
assertParse("stringLiteral", `'bar\\'s baz'`, [ops.literal, `bar's baz`]);
assertParse("stringLiteral", `«string»`, [ops.literal, "string"]);
assertParse("stringLiteral", `"\\0\\b\\f\\n\\r\\t\\v"`, [
ops.literal,
"\0\b\f\n\r\t\v",
]);
});
test("templateDocument", () => {
assertParse("templateDocument", "hello${foo}world", [
ops.lambda,
["_"],
[
ops.template,
[ops.literal, ["hello", "world"]],
[ops.concat, [ops.scope, "foo"]],
],
]);
assertParse("templateDocument", "Documents can contain ` backticks", [
ops.lambda,
["_"],
[ops.template, [ops.literal, ["Documents can contain ` backticks"]]],
]);
});
test("templateLiteral", () => {
assertParse("templateLiteral", "`Hello, world.`", [
ops.template,
[ops.literal, ["Hello, world."]],
]);
assertParse("templateLiteral", "`foo ${x} bar`", [
ops.template,
[ops.literal, ["foo ", " bar"]],
[ops.concat, [ops.scope, "x"]],
]);
assertParse("templateLiteral", "`${`nested`}`", [
ops.template,
[ops.literal, ["", ""]],
[ops.concat, [ops.template, [ops.literal, ["nested"]]]],
]);
assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
ops.template,
[ops.literal, ["", ""]],
[
ops.concat,
[
[ops.builtin, "map:"],
[ops.scope, "people"],
[
ops.lambda,
["_"],
[
ops.template,
[ops.literal, ["", ""]],
[ops.concat, [ops.scope, "name"]],
],
],
],
],
]);
});
test("templateSubtitution", () => {
assertParse("templateSubstitution", "${foo}", [ops.scope, "foo"], false);
});
test("whitespace block", () => {
assertParse(
"__",
`
// First comment
// Second comment
`,
null,
false
);
});
test("unaryExpression", () => {
assertParse("unaryExpression", "!true", [
ops.logicalNot,
[undetermined, "true"],
]);
assertParse("unaryExpression", "+1", [ops.unaryPlus, [ops.literal, 1]]);
assertParse("unaryExpression", "-2", [ops.unaryMinus, [ops.literal, 2]]);
assertParse("unaryExpression", "~3", [ops.bitwiseNot, [ops.literal, 3]]);
});
});
function assertParse(startRule, source, expected, checkLocation = true) {
const code = parse(source, {
grammarSource: { text: source },
startRule,
});
// Verify that the parser returned a `location` property and that it spans the
// entire source. We skip this check in cases where the source starts or ends
// with a comment; the parser will strip those.
if (checkLocation) {
assert(code.location, "no location");
const resultSource = code.location.source.text.slice(
code.location.start.offset,
code.location.end.offset
);
assert.equal(resultSource, source.trim());
}
const actual = stripCodeLocations(code);
assert.deepEqual(actual, expected);
}