@weborigami/language 0.3.1 → 0.3.3-jse.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/main.js +1 -1
- package/package.json +3 -3
- package/src/compiler/compile.js +2 -0
- package/src/compiler/origami.pegjs +118 -28
- package/src/compiler/parse.js +1506 -822
- package/src/compiler/parserHelpers.js +13 -12
- package/src/runtime/expressionObject.js +2 -6
- package/src/runtime/handlers.js +6 -3
- package/src/runtime/ops.js +64 -23
- package/src/runtime/{taggedTemplateIndent.js → templateIndent.js} +2 -2
- package/src/runtime/templateStandard.js +13 -0
- package/test/compiler/parse.test.js +142 -24
- package/test/runtime/ops.test.js +11 -2
- package/test/runtime/taggedTemplateIndent.test.js +1 -1
- package/test/runtime/templateText.test.js +18 -0
|
@@ -204,7 +204,8 @@ export function makeCall(target, args) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
let fnCall;
|
|
207
|
-
|
|
207
|
+
const op = args[0];
|
|
208
|
+
if (op === ops.traverse || op === ops.optionalTraverse) {
|
|
208
209
|
let tree = target;
|
|
209
210
|
|
|
210
211
|
if (tree[0] === undetermined) {
|
|
@@ -219,12 +220,12 @@ export function makeCall(target, args) {
|
|
|
219
220
|
if (args.length > 1) {
|
|
220
221
|
// Regular traverse
|
|
221
222
|
const keys = args.slice(1);
|
|
222
|
-
fnCall = [
|
|
223
|
+
fnCall = [op, tree, ...keys];
|
|
223
224
|
} else {
|
|
224
225
|
// Traverse without arguments equates to unpack
|
|
225
226
|
fnCall = [ops.unpack, tree];
|
|
226
227
|
}
|
|
227
|
-
} else if (
|
|
228
|
+
} else if (op === ops.templateStandard || op === ops.templateTree) {
|
|
228
229
|
// Tagged template
|
|
229
230
|
const strings = args[1];
|
|
230
231
|
const values = args.slice(2);
|
|
@@ -275,6 +276,15 @@ export function makeDeferredArguments(args) {
|
|
|
275
276
|
});
|
|
276
277
|
}
|
|
277
278
|
|
|
279
|
+
export function makeJsPropertyAccess(expression, property) {
|
|
280
|
+
const location = {
|
|
281
|
+
source: expression.location.source,
|
|
282
|
+
start: expression.location.start,
|
|
283
|
+
end: property.location.end,
|
|
284
|
+
};
|
|
285
|
+
return annotate([expression, property], location);
|
|
286
|
+
}
|
|
287
|
+
|
|
278
288
|
/**
|
|
279
289
|
* Make an object.
|
|
280
290
|
*
|
|
@@ -364,15 +374,6 @@ export function makeProperty(key, value) {
|
|
|
364
374
|
return [key, modified];
|
|
365
375
|
}
|
|
366
376
|
|
|
367
|
-
export function makeJsPropertyAccess(expression, property) {
|
|
368
|
-
const location = {
|
|
369
|
-
source: expression.location.source,
|
|
370
|
-
start: expression.location.start,
|
|
371
|
-
end: property.location.end,
|
|
372
|
-
};
|
|
373
|
-
return annotate([expression, property], location);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
377
|
export function makeReference(identifier) {
|
|
377
378
|
// We can't know for sure that an identifier is a builtin reference until we
|
|
378
379
|
// see whether it's being called as a function.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
extension,
|
|
3
3
|
ObjectTree,
|
|
4
|
+
setParent,
|
|
4
5
|
symbols,
|
|
5
6
|
trailingSlash,
|
|
6
7
|
Tree,
|
|
@@ -116,12 +117,7 @@ export default async function expressionObject(entries, parent) {
|
|
|
116
117
|
});
|
|
117
118
|
|
|
118
119
|
// Attach the parent
|
|
119
|
-
|
|
120
|
-
configurable: true,
|
|
121
|
-
enumerable: false,
|
|
122
|
-
value: parent,
|
|
123
|
-
writable: true,
|
|
124
|
-
});
|
|
120
|
+
setParent(object, parent);
|
|
125
121
|
|
|
126
122
|
// Evaluate any properties that were declared as immediate: get their value
|
|
127
123
|
// and overwrite the property getter with the actual value.
|
package/src/runtime/handlers.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isStringLike,
|
|
6
6
|
isUnpackable,
|
|
7
7
|
scope,
|
|
8
|
-
|
|
8
|
+
setParent,
|
|
9
9
|
trailingSlash,
|
|
10
10
|
} from "@weborigami/async-tree";
|
|
11
11
|
|
|
@@ -74,9 +74,12 @@ export async function handleExtension(parent, value, key) {
|
|
|
74
74
|
key = trailingSlash.remove(key);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Special
|
|
77
|
+
// Special cases: `.ori.<ext>` extensions are Origami documents,
|
|
78
|
+
// `.jse.<ext>` are JSE documents.
|
|
78
79
|
const extname = key.match(/\.ori\.\S+$/)
|
|
79
80
|
? ".oridocument"
|
|
81
|
+
: key.match(/\.jse\.\S+$/)
|
|
82
|
+
? ".jsedocument"
|
|
80
83
|
: extension.extname(key);
|
|
81
84
|
if (extname) {
|
|
82
85
|
const handler = await getExtensionHandler(parent, extname);
|
|
@@ -92,7 +95,7 @@ export async function handleExtension(parent, value, key) {
|
|
|
92
95
|
if (handler.mediaType) {
|
|
93
96
|
value.mediaType = handler.mediaType;
|
|
94
97
|
}
|
|
95
|
-
value
|
|
98
|
+
setParent(value, parent);
|
|
96
99
|
|
|
97
100
|
const unpack = handler.unpack;
|
|
98
101
|
if (unpack) {
|
package/src/runtime/ops.js
CHANGED
|
@@ -11,17 +11,18 @@ import {
|
|
|
11
11
|
deepText,
|
|
12
12
|
isUnpackable,
|
|
13
13
|
scope as scopeFn,
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
setParent,
|
|
15
|
+
text as templateFunctionTree,
|
|
16
16
|
} from "@weborigami/async-tree";
|
|
17
17
|
import os from "node:os";
|
|
18
|
-
import taggedTemplateIndent from "../../src/runtime/taggedTemplateIndent.js";
|
|
19
18
|
import { builtinReferenceError, scopeReferenceError } from "./errors.js";
|
|
20
19
|
import expressionObject from "./expressionObject.js";
|
|
21
20
|
import { evaluate } from "./internal.js";
|
|
22
21
|
import mergeTrees from "./mergeTrees.js";
|
|
23
22
|
import OrigamiFiles from "./OrigamiFiles.js";
|
|
24
23
|
import { codeSymbol } from "./symbols.js";
|
|
24
|
+
import templateFunctionIndent from "./templateIndent.js";
|
|
25
|
+
import templateFunctionStandard from "./templateStandard.js";
|
|
25
26
|
|
|
26
27
|
function addOpLabel(op, label) {
|
|
27
28
|
Object.defineProperty(op, "toString", {
|
|
@@ -105,7 +106,7 @@ addOpLabel(comma, "«ops.comma»");
|
|
|
105
106
|
* @param {any[]} args
|
|
106
107
|
*/
|
|
107
108
|
export async function concat(...args) {
|
|
108
|
-
return
|
|
109
|
+
return deepText.call(this, args);
|
|
109
110
|
}
|
|
110
111
|
addOpLabel(concat, "«ops.concat»");
|
|
111
112
|
|
|
@@ -114,6 +115,13 @@ export async function conditional(condition, truthy, falsy) {
|
|
|
114
115
|
return value instanceof Function ? await value() : value;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
export async function construct(constructor, ...args) {
|
|
119
|
+
if (isUnpackable(constructor)) {
|
|
120
|
+
constructor = await constructor.unpack();
|
|
121
|
+
}
|
|
122
|
+
return Reflect.construct(constructor, args);
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
/**
|
|
118
126
|
* Construct a document object by invoking the body code (a lambda) and adding
|
|
119
127
|
* the resulting text to the front data.
|
|
@@ -131,7 +139,7 @@ export async function document(frontData, bodyCode) {
|
|
|
131
139
|
...frontData,
|
|
132
140
|
"@text": body,
|
|
133
141
|
};
|
|
134
|
-
object
|
|
142
|
+
setParent(object, this);
|
|
135
143
|
return object;
|
|
136
144
|
}
|
|
137
145
|
addOpLabel(document, "«ops.document");
|
|
@@ -224,7 +232,8 @@ export async function inherited(key) {
|
|
|
224
232
|
return undefined;
|
|
225
233
|
}
|
|
226
234
|
const parentScope = scopeFn(this.parent);
|
|
227
|
-
|
|
235
|
+
const value = await parentScope.get(key);
|
|
236
|
+
return value;
|
|
228
237
|
}
|
|
229
238
|
addOpLabel(inherited, "«ops.inherited»");
|
|
230
239
|
|
|
@@ -388,17 +397,33 @@ export async function merge(...codes) {
|
|
|
388
397
|
return directObject;
|
|
389
398
|
}
|
|
390
399
|
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
+
// If we have direct property entries, create a context for them. The
|
|
401
|
+
// `expressionObject` function will set the object's parent symbol to `this`.
|
|
402
|
+
// Tree.from will call the ObjectTree constructor, which will use that symbol
|
|
403
|
+
// to set the parent for the new tree to `this`.
|
|
404
|
+
const context = directObject ? Tree.from(directObject) : this;
|
|
405
|
+
|
|
406
|
+
// Second pass: evaluate the trees. For the trees which are direct property
|
|
407
|
+
// entries, we'll copy over the values we've already calculated. We can't
|
|
408
|
+
// reuse the `directObject` as is because in a merge we need to respect the
|
|
409
|
+
// order in which the properties are defined. Trees that aren't direct
|
|
410
|
+
// property entries are evaluated with the direct property entries in scope.
|
|
400
411
|
const trees = await Promise.all(
|
|
401
|
-
codes.map(async (code) =>
|
|
412
|
+
codes.map(async (code) => {
|
|
413
|
+
if (code[0] === object) {
|
|
414
|
+
// Using the code as reference, create a new object with the direct
|
|
415
|
+
// property values we've already calculated.
|
|
416
|
+
const object = {};
|
|
417
|
+
for (const [key] of code.slice(1)) {
|
|
418
|
+
// @ts-ignore directObject will always be defined
|
|
419
|
+
object[key] = directObject[key];
|
|
420
|
+
}
|
|
421
|
+
setParent(object, this);
|
|
422
|
+
return object;
|
|
423
|
+
} else {
|
|
424
|
+
return evaluate.call(context, code);
|
|
425
|
+
}
|
|
426
|
+
})
|
|
402
427
|
);
|
|
403
428
|
|
|
404
429
|
return mergeTrees.call(this, ...trees);
|
|
@@ -455,6 +480,14 @@ export async function object(...entries) {
|
|
|
455
480
|
addOpLabel(object, "«ops.object»");
|
|
456
481
|
object.unevaluatedArgs = true;
|
|
457
482
|
|
|
483
|
+
export function optionalTraverse(treelike, key) {
|
|
484
|
+
if (!treelike) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
return Tree.traverseOrThrow(treelike, key);
|
|
488
|
+
}
|
|
489
|
+
addOpLabel(optionalTraverse, "«ops.optionalTraverse");
|
|
490
|
+
|
|
458
491
|
export function remainder(a, b) {
|
|
459
492
|
return a % b;
|
|
460
493
|
}
|
|
@@ -529,21 +562,29 @@ export function subtraction(a, b) {
|
|
|
529
562
|
}
|
|
530
563
|
addOpLabel(subtraction, "«ops.subtraction»");
|
|
531
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Apply the tree indent tagged template function.
|
|
567
|
+
*/
|
|
568
|
+
export async function templateIndent(strings, ...values) {
|
|
569
|
+
return templateFunctionIndent(strings, ...values);
|
|
570
|
+
}
|
|
571
|
+
addOpLabel(templateIndent, "«ops.templateIndent»");
|
|
572
|
+
|
|
532
573
|
/**
|
|
533
574
|
* Apply the default tagged template function.
|
|
534
575
|
*/
|
|
535
|
-
export
|
|
536
|
-
return
|
|
576
|
+
export function templateStandard(strings, ...values) {
|
|
577
|
+
return templateFunctionStandard(strings, ...values);
|
|
537
578
|
}
|
|
538
|
-
addOpLabel(
|
|
579
|
+
addOpLabel(templateStandard, "«ops.templateStandard»");
|
|
539
580
|
|
|
540
581
|
/**
|
|
541
|
-
* Apply the tagged template
|
|
582
|
+
* Apply the tree tagged template function.
|
|
542
583
|
*/
|
|
543
|
-
export async function
|
|
544
|
-
return
|
|
584
|
+
export async function templateTree(strings, ...values) {
|
|
585
|
+
return templateFunctionTree(strings, ...values);
|
|
545
586
|
}
|
|
546
|
-
addOpLabel(
|
|
587
|
+
addOpLabel(templateTree, "«ops.templateTree»");
|
|
547
588
|
|
|
548
589
|
/**
|
|
549
590
|
* Traverse a path of keys through a tree.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { text, toString, Tree } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
3
|
const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
|
|
4
4
|
|
|
@@ -19,7 +19,7 @@ export default async function indent(strings, ...values) {
|
|
|
19
19
|
}
|
|
20
20
|
const { blockIndentations, strings: modifiedStrings } = modified;
|
|
21
21
|
const valueTexts = await Promise.all(
|
|
22
|
-
values.map((value) => (Tree.isTreelike(value) ?
|
|
22
|
+
values.map((value) => (Tree.isTreelike(value) ? text(value) : value))
|
|
23
23
|
);
|
|
24
24
|
return joinBlocks(modifiedStrings, valueTexts, blockIndentations);
|
|
25
25
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concatenate the strings with a standard tagged template function that works
|
|
3
|
+
* just like normal JavaScript templates. This is: a) synchronous, b) does not
|
|
4
|
+
* convert treelike objects to strings.
|
|
5
|
+
*/
|
|
6
|
+
export default function standardTemplate(strings, ...values) {
|
|
7
|
+
let result = strings[0];
|
|
8
|
+
for (let i = 0; i < values.length; i++) {
|
|
9
|
+
result += values[i];
|
|
10
|
+
result += strings[i + 1];
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
@@ -19,6 +19,46 @@ describe("Origami parser", () => {
|
|
|
19
19
|
]);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
test("angleBracketLiteral", () => {
|
|
23
|
+
assertParse(
|
|
24
|
+
"angleBracketLiteral",
|
|
25
|
+
"<index.html>",
|
|
26
|
+
[ops.scope, "index.html"],
|
|
27
|
+
"jse",
|
|
28
|
+
false
|
|
29
|
+
);
|
|
30
|
+
assertParse(
|
|
31
|
+
"angleBracketLiteral",
|
|
32
|
+
"<foo/bar/baz>",
|
|
33
|
+
[
|
|
34
|
+
ops.traverse,
|
|
35
|
+
[ops.scope, "foo/"],
|
|
36
|
+
[ops.literal, "bar/"],
|
|
37
|
+
[ops.literal, "baz"],
|
|
38
|
+
],
|
|
39
|
+
"jse",
|
|
40
|
+
false
|
|
41
|
+
);
|
|
42
|
+
assertParse("angleBracketLiteral", "<files:src/assets>", [
|
|
43
|
+
ops.traverse,
|
|
44
|
+
[
|
|
45
|
+
[ops.builtin, "files:"],
|
|
46
|
+
[ops.literal, "src/"],
|
|
47
|
+
],
|
|
48
|
+
[ops.literal, "assets"],
|
|
49
|
+
]);
|
|
50
|
+
assertParse(
|
|
51
|
+
"angleBracketLiteral",
|
|
52
|
+
"<https://example.com/>",
|
|
53
|
+
[
|
|
54
|
+
[ops.builtin, "https:"],
|
|
55
|
+
[ops.literal, "example.com/"],
|
|
56
|
+
],
|
|
57
|
+
"jse",
|
|
58
|
+
false
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
22
62
|
test("arrayLiteral", () => {
|
|
23
63
|
assertParse("arrayLiteral", "[]", [ops.array]);
|
|
24
64
|
assertParse("arrayLiteral", "[1, 2, 3]", [
|
|
@@ -399,7 +439,8 @@ Body`,
|
|
|
399
439
|
]);
|
|
400
440
|
|
|
401
441
|
// Consecutive slashes at start of something = comment
|
|
402
|
-
assertParse("expression", "
|
|
442
|
+
assertParse("expression", "x //comment", [ops.scope, "x"], "jse", false);
|
|
443
|
+
|
|
403
444
|
assertParse("expression", "page.ori(mdHtml:(about.md))", [
|
|
404
445
|
[ops.scope, "page.ori"],
|
|
405
446
|
[
|
|
@@ -446,7 +487,11 @@ Body`,
|
|
|
446
487
|
]);
|
|
447
488
|
assertParse("expression", "fn =`x`", [
|
|
448
489
|
[ops.builtin, "fn"],
|
|
449
|
-
[
|
|
490
|
+
[
|
|
491
|
+
ops.lambda,
|
|
492
|
+
[[ops.literal, "_"]],
|
|
493
|
+
[ops.templateTree, [ops.literal, ["x"]]],
|
|
494
|
+
],
|
|
450
495
|
]);
|
|
451
496
|
assertParse("expression", "copy app.js(formulas), files:snapshot", [
|
|
452
497
|
[ops.builtin, "copy"],
|
|
@@ -464,7 +509,7 @@ Body`,
|
|
|
464
509
|
[
|
|
465
510
|
ops.lambda,
|
|
466
511
|
[[ops.literal, "_"]],
|
|
467
|
-
[ops.
|
|
512
|
+
[ops.templateTree, [ops.literal, ["<li>", "</li>"]], [ops.scope, "_"]],
|
|
468
513
|
],
|
|
469
514
|
]);
|
|
470
515
|
assertParse("expression", `https://example.com/about/`, [
|
|
@@ -523,6 +568,23 @@ Body`,
|
|
|
523
568
|
]);
|
|
524
569
|
});
|
|
525
570
|
|
|
571
|
+
test("frontMatterExpression", () => {
|
|
572
|
+
assertParse(
|
|
573
|
+
"frontMatterExpression",
|
|
574
|
+
`---
|
|
575
|
+
(name) => _template()
|
|
576
|
+
---
|
|
577
|
+
`,
|
|
578
|
+
[
|
|
579
|
+
ops.lambda,
|
|
580
|
+
[[ops.literal, "name"]],
|
|
581
|
+
[[ops.scope, "_template"], undefined],
|
|
582
|
+
],
|
|
583
|
+
"jse",
|
|
584
|
+
false
|
|
585
|
+
);
|
|
586
|
+
});
|
|
587
|
+
|
|
526
588
|
test("group", () => {
|
|
527
589
|
assertParse("group", "(hello)", [ops.scope, "hello"]);
|
|
528
590
|
assertParse("group", "(((nested)))", [ops.scope, "nested"]);
|
|
@@ -544,10 +606,10 @@ Body`,
|
|
|
544
606
|
});
|
|
545
607
|
|
|
546
608
|
test("identifier", () => {
|
|
547
|
-
assertParse("identifier", "abc", "abc", false);
|
|
548
|
-
assertParse("identifier", "index.html", "index.html", false);
|
|
549
|
-
assertParse("identifier", "foo\\ bar", "foo bar", false);
|
|
550
|
-
assertParse("identifier", "x-y-z", "x-y-z", false);
|
|
609
|
+
assertParse("identifier", "abc", "abc", "shell", false);
|
|
610
|
+
assertParse("identifier", "index.html", "index.html", "shell", false);
|
|
611
|
+
assertParse("identifier", "foo\\ bar", "foo bar", "shell", false);
|
|
612
|
+
assertParse("identifier", "x-y-z", "x-y-z", "shell", false);
|
|
551
613
|
});
|
|
552
614
|
|
|
553
615
|
test("implicitParenthesesCallExpression", () => {
|
|
@@ -586,8 +648,8 @@ Body`,
|
|
|
586
648
|
});
|
|
587
649
|
|
|
588
650
|
test("jsIdentifier", () => {
|
|
589
|
-
assertParse("jsIdentifier", "foo", "foo", false);
|
|
590
|
-
assertParse("jsIdentifier", "$Δelta", "$Δelta", false);
|
|
651
|
+
assertParse("jsIdentifier", "foo", "foo", "jse", false);
|
|
652
|
+
assertParse("jsIdentifier", "$Δelta", "$Δelta", "jse", false);
|
|
591
653
|
assertThrows(
|
|
592
654
|
"jsIdentifier",
|
|
593
655
|
"1stCharacterIsNumber",
|
|
@@ -627,6 +689,7 @@ Body`,
|
|
|
627
689
|
[ops.literal, 2],
|
|
628
690
|
[ops.literal, 3],
|
|
629
691
|
]);
|
|
692
|
+
assertThrows("list", "1\n2\n3", `but "\\n" found`, 0, "jse");
|
|
630
693
|
assertParse("list", "'a' , 'b' , 'c'", [
|
|
631
694
|
[ops.literal, "a"],
|
|
632
695
|
[ops.literal, "b"],
|
|
@@ -662,7 +725,13 @@ Body`,
|
|
|
662
725
|
});
|
|
663
726
|
|
|
664
727
|
test("multiLineComment", () => {
|
|
665
|
-
assertParse(
|
|
728
|
+
assertParse(
|
|
729
|
+
"multiLineComment",
|
|
730
|
+
"/*\nHello, world!\n*/",
|
|
731
|
+
null,
|
|
732
|
+
"jse",
|
|
733
|
+
false
|
|
734
|
+
);
|
|
666
735
|
});
|
|
667
736
|
|
|
668
737
|
test("multiplicativeExpression", () => {
|
|
@@ -687,6 +756,13 @@ Body`,
|
|
|
687
756
|
assertParse("namespace", "js:", [ops.builtin, "js:"]);
|
|
688
757
|
});
|
|
689
758
|
|
|
759
|
+
test("newExpression", () => {
|
|
760
|
+
assertParse("newExpression", "new Foo()", [
|
|
761
|
+
ops.construct,
|
|
762
|
+
[ops.scope, "Foo"],
|
|
763
|
+
]);
|
|
764
|
+
});
|
|
765
|
+
|
|
690
766
|
test("nullishCoalescingExpression", () => {
|
|
691
767
|
assertParse("nullishCoalescingExpression", "a ?? b", [
|
|
692
768
|
ops.nullishCoalescing,
|
|
@@ -839,9 +915,16 @@ Body`,
|
|
|
839
915
|
});
|
|
840
916
|
|
|
841
917
|
test("objectPublicKey", () => {
|
|
842
|
-
assertParse("objectPublicKey", "a", "a", false);
|
|
843
|
-
assertParse("objectPublicKey", "markdown/", "markdown/", false);
|
|
844
|
-
assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
|
|
918
|
+
assertParse("objectPublicKey", "a", "a", "jse", false);
|
|
919
|
+
assertParse("objectPublicKey", "markdown/", "markdown/", "jse", false);
|
|
920
|
+
assertParse("objectPublicKey", "foo\\ bar", "foo bar", "jse", false);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test("optionalChaining", () => {
|
|
924
|
+
assertParse("optionalChaining", "?.key", [
|
|
925
|
+
ops.optionalTraverse,
|
|
926
|
+
[ops.literal, "key"],
|
|
927
|
+
]);
|
|
845
928
|
});
|
|
846
929
|
|
|
847
930
|
test("parenthesesArguments", () => {
|
|
@@ -911,6 +994,15 @@ Body`,
|
|
|
911
994
|
[ops.literal, 1],
|
|
912
995
|
[ops.literal, 2],
|
|
913
996
|
]);
|
|
997
|
+
// Only in JSE
|
|
998
|
+
assertParse(
|
|
999
|
+
"primary",
|
|
1000
|
+
"<index.html>",
|
|
1001
|
+
[ops.scope, "index.html"],
|
|
1002
|
+
"jse",
|
|
1003
|
+
false
|
|
1004
|
+
);
|
|
1005
|
+
assertThrows("primary", "<index.html>", `but "<" found`, 0, "shell");
|
|
914
1006
|
});
|
|
915
1007
|
|
|
916
1008
|
test("program", () => {
|
|
@@ -920,6 +1012,7 @@ Body`,
|
|
|
920
1012
|
'Hello'
|
|
921
1013
|
`,
|
|
922
1014
|
[ops.literal, "Hello"],
|
|
1015
|
+
"jse",
|
|
923
1016
|
false
|
|
924
1017
|
);
|
|
925
1018
|
});
|
|
@@ -964,6 +1057,10 @@ Body`,
|
|
|
964
1057
|
]);
|
|
965
1058
|
});
|
|
966
1059
|
|
|
1060
|
+
test("regexLiteral", () => {
|
|
1061
|
+
assertParse("regexLiteral", "/abc+/g", [ops.literal, /abc+/g]);
|
|
1062
|
+
});
|
|
1063
|
+
|
|
967
1064
|
test("relationalExpression", () => {
|
|
968
1065
|
assertParse("relationalExpression", "1 < 2", [
|
|
969
1066
|
ops.lessThan,
|
|
@@ -1025,7 +1122,7 @@ Body`,
|
|
|
1025
1122
|
assertParse("shorthandFunction", "=`Hello, ${name}.`", [
|
|
1026
1123
|
ops.lambda,
|
|
1027
1124
|
[[ops.literal, "_"]],
|
|
1028
|
-
[ops.
|
|
1125
|
+
[ops.templateTree, [ops.literal, ["Hello, ", "."]], [ops.scope, "name"]],
|
|
1029
1126
|
]);
|
|
1030
1127
|
assertParse("shorthandFunction", "=indent`hello`", [
|
|
1031
1128
|
ops.lambda,
|
|
@@ -1038,7 +1135,7 @@ Body`,
|
|
|
1038
1135
|
});
|
|
1039
1136
|
|
|
1040
1137
|
test("singleLineComment", () => {
|
|
1041
|
-
assertParse("singleLineComment", "// Hello, world!", null, false);
|
|
1138
|
+
assertParse("singleLineComment", "// Hello, world!", null, "jse", false);
|
|
1042
1139
|
});
|
|
1043
1140
|
|
|
1044
1141
|
test("spreadElement", () => {
|
|
@@ -1142,21 +1239,27 @@ Body text`,
|
|
|
1142
1239
|
|
|
1143
1240
|
test("templateLiteral", () => {
|
|
1144
1241
|
assertParse("templateLiteral", "`Hello, world.`", [
|
|
1145
|
-
ops.
|
|
1242
|
+
ops.templateTree,
|
|
1146
1243
|
[ops.literal, ["Hello, world."]],
|
|
1147
1244
|
]);
|
|
1245
|
+
assertParse(
|
|
1246
|
+
"templateLiteral",
|
|
1247
|
+
"`Hello, world.`",
|
|
1248
|
+
[ops.templateStandard, [ops.literal, ["Hello, world."]]],
|
|
1249
|
+
"jse"
|
|
1250
|
+
);
|
|
1148
1251
|
assertParse("templateLiteral", "`foo ${x} bar`", [
|
|
1149
|
-
ops.
|
|
1252
|
+
ops.templateTree,
|
|
1150
1253
|
[ops.literal, ["foo ", " bar"]],
|
|
1151
1254
|
[ops.scope, "x"],
|
|
1152
1255
|
]);
|
|
1153
1256
|
assertParse("templateLiteral", "`${`nested`}`", [
|
|
1154
|
-
ops.
|
|
1257
|
+
ops.templateTree,
|
|
1155
1258
|
[ops.literal, ["", ""]],
|
|
1156
|
-
[ops.
|
|
1259
|
+
[ops.templateTree, [ops.literal, ["nested"]]],
|
|
1157
1260
|
]);
|
|
1158
1261
|
assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
|
|
1159
|
-
ops.
|
|
1262
|
+
ops.templateTree,
|
|
1160
1263
|
[ops.literal, ["", ""]],
|
|
1161
1264
|
[
|
|
1162
1265
|
[ops.builtin, "map:"],
|
|
@@ -1164,14 +1267,20 @@ Body text`,
|
|
|
1164
1267
|
[
|
|
1165
1268
|
ops.lambda,
|
|
1166
1269
|
[[ops.literal, "_"]],
|
|
1167
|
-
[ops.
|
|
1270
|
+
[ops.templateTree, [ops.literal, ["", ""]], [ops.scope, "name"]],
|
|
1168
1271
|
],
|
|
1169
1272
|
],
|
|
1170
1273
|
]);
|
|
1171
1274
|
});
|
|
1172
1275
|
|
|
1173
1276
|
test("templateSubtitution", () => {
|
|
1174
|
-
assertParse(
|
|
1277
|
+
assertParse(
|
|
1278
|
+
"templateSubstitution",
|
|
1279
|
+
"${foo}",
|
|
1280
|
+
[ops.scope, "foo"],
|
|
1281
|
+
"shell",
|
|
1282
|
+
false
|
|
1283
|
+
);
|
|
1175
1284
|
});
|
|
1176
1285
|
|
|
1177
1286
|
test("whitespace block", () => {
|
|
@@ -1182,6 +1291,7 @@ Body text`,
|
|
|
1182
1291
|
// Second comment
|
|
1183
1292
|
`,
|
|
1184
1293
|
null,
|
|
1294
|
+
"jse",
|
|
1185
1295
|
false
|
|
1186
1296
|
);
|
|
1187
1297
|
});
|
|
@@ -1197,9 +1307,16 @@ Body text`,
|
|
|
1197
1307
|
});
|
|
1198
1308
|
});
|
|
1199
1309
|
|
|
1200
|
-
function assertParse(
|
|
1310
|
+
function assertParse(
|
|
1311
|
+
startRule,
|
|
1312
|
+
source,
|
|
1313
|
+
expected,
|
|
1314
|
+
mode = "shell",
|
|
1315
|
+
checkLocation = true
|
|
1316
|
+
) {
|
|
1201
1317
|
const code = parse(source, {
|
|
1202
1318
|
grammarSource: { text: source },
|
|
1319
|
+
mode,
|
|
1203
1320
|
startRule,
|
|
1204
1321
|
});
|
|
1205
1322
|
|
|
@@ -1227,10 +1344,11 @@ function assertCodeLocations(code) {
|
|
|
1227
1344
|
}
|
|
1228
1345
|
}
|
|
1229
1346
|
|
|
1230
|
-
function assertThrows(startRule, source, message, position) {
|
|
1347
|
+
function assertThrows(startRule, source, message, position, mode = "shell") {
|
|
1231
1348
|
try {
|
|
1232
1349
|
parse(source, {
|
|
1233
1350
|
grammarSource: { text: source },
|
|
1351
|
+
mode,
|
|
1234
1352
|
startRule,
|
|
1235
1353
|
});
|
|
1236
1354
|
} catch (/** @type {any} */ error) {
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -80,7 +80,11 @@ describe("ops", () => {
|
|
|
80
80
|
assert.strictEqual(await ops.conditional(false, errorFn, trueFn), true);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test("ops.
|
|
83
|
+
test("ops.construct", async () => {
|
|
84
|
+
assert.equal(await ops.construct(String, "hello"), "hello");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("ops.document", async () => {
|
|
84
88
|
const code = createCode([
|
|
85
89
|
ops.document,
|
|
86
90
|
{
|
|
@@ -90,7 +94,7 @@ describe("ops", () => {
|
|
|
90
94
|
ops.lambda,
|
|
91
95
|
[["_"]],
|
|
92
96
|
[
|
|
93
|
-
ops.
|
|
97
|
+
ops.templateIndent,
|
|
94
98
|
[ops.literal, ["a = ", ""]],
|
|
95
99
|
[ops.concat, [ops.scope, "a"]],
|
|
96
100
|
],
|
|
@@ -293,6 +297,11 @@ describe("ops", () => {
|
|
|
293
297
|
assert.strictEqual(ops.multiplication("foo", 2), NaN);
|
|
294
298
|
});
|
|
295
299
|
|
|
300
|
+
test("ops.optionalTraverse", async () => {
|
|
301
|
+
assert.equal(await ops.optionalTraverse(null, "a"), undefined);
|
|
302
|
+
assert.equal(await ops.optionalTraverse({ a: 1 }, "a"), 1);
|
|
303
|
+
});
|
|
304
|
+
|
|
296
305
|
test("ops.notEqual", () => {
|
|
297
306
|
assert(!ops.notEqual(1, 1));
|
|
298
307
|
assert(ops.notEqual(1, 2));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import indent from "../../src/runtime/
|
|
3
|
+
import indent from "../../src/runtime/templateIndent.js";
|
|
4
4
|
|
|
5
5
|
describe("taggedTemplateIndent", () => {
|
|
6
6
|
test("joins strings and values together if template isn't a block template", async () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import templateText from "../../src/runtime/templateStandard.js";
|
|
4
|
+
|
|
5
|
+
describe("templateText", () => {
|
|
6
|
+
test("joins strings and values together like JavaScript", async () => {
|
|
7
|
+
const a = 1;
|
|
8
|
+
const b = 2;
|
|
9
|
+
const result = await templateText`-${a} ${b}-`;
|
|
10
|
+
assert.equal(result, "-1 2-");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("renders an object like JavaScript", async () => {
|
|
14
|
+
const object = { a: 1 };
|
|
15
|
+
const result = await templateText`-${object}-`;
|
|
16
|
+
assert.equal(result, "-[object Object]-");
|
|
17
|
+
});
|
|
18
|
+
});
|