@weborigami/language 0.3.3 → 0.3.4-jse.4

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.
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { parse } from "../../src/compiler/parse.js";
4
- import { undetermined } from "../../src/compiler/parserHelpers.js";
4
+ import { markers } from "../../src/compiler/parserHelpers.js";
5
5
  import * as ops from "../../src/runtime/ops.js";
6
6
  import { assertCodeEqual } from "./codeHelpers.js";
7
7
 
@@ -19,6 +19,72 @@ describe("Origami parser", () => {
19
19
  ]);
20
20
  });
21
21
 
22
+ describe("angleBracketLiteral", () => {
23
+ test("with path", () => {
24
+ assertParse("angleBracketLiteral", "<index.html>", [
25
+ markers.traverse,
26
+ [markers.external, "index.html"],
27
+ ]);
28
+ assertParse(
29
+ "angleBracketLiteral",
30
+ "<Path with spaces (and parens).html>",
31
+ [
32
+ markers.traverse,
33
+ [markers.external, "Path with spaces (and parens).html"],
34
+ ]
35
+ );
36
+ assertParse("angleBracketLiteral", "<foo/bar/baz>", [
37
+ markers.traverse,
38
+ [markers.external, "foo/"],
39
+ [ops.literal, "bar/"],
40
+ [ops.literal, "baz"],
41
+ ]);
42
+ });
43
+
44
+ test("root directory", () => {
45
+ assertParse("angleBracketLiteral", "</>", [
46
+ markers.traverse,
47
+ [markers.external, "/"],
48
+ ]);
49
+ assertParse("angleBracketLiteral", "</etc/passwd>", [
50
+ markers.traverse,
51
+ [markers.external, "/"],
52
+ [ops.literal, "etc/"],
53
+ [ops.literal, "passwd"],
54
+ ]);
55
+ });
56
+
57
+ test("home directory", () => {
58
+ assertParse("angleBracketLiteral", "<~>", [
59
+ markers.traverse,
60
+ [markers.external, "~"],
61
+ ]);
62
+ assertParse("angleBracketLiteral", "<~/.bash_profile>", [
63
+ markers.traverse,
64
+ [markers.external, "~/"],
65
+ [ops.literal, ".bash_profile"],
66
+ ]);
67
+ });
68
+
69
+ test("with protocol URL", () => {
70
+ assertParse("angleBracketLiteral", "<files:src/assets>", [
71
+ [markers.global, "files:"],
72
+ [ops.literal, "src/"],
73
+ [ops.literal, "assets"],
74
+ ]);
75
+ assertParse(
76
+ "angleBracketLiteral",
77
+ "<https://example.com/data.yaml>",
78
+ [
79
+ [markers.global, "https:"],
80
+ [ops.literal, "example.com/"],
81
+ [ops.literal, "data.yaml"],
82
+ ],
83
+ "jse"
84
+ );
85
+ });
86
+ });
87
+
22
88
  test("arrayLiteral", () => {
23
89
  assertParse("arrayLiteral", "[]", [ops.array]);
24
90
  assertParse("arrayLiteral", "[1, 2, 3]", [
@@ -41,7 +107,7 @@ describe("Origami parser", () => {
41
107
  [ops.literal, 4],
42
108
  ]);
43
109
  assertParse("arrayLiteral", "[ 1, ...[2, 3]]", [
44
- ops.merge,
110
+ ops.flat,
45
111
  [ops.array, [ops.literal, 1]],
46
112
  [ops.array, [ops.literal, 2], [ops.literal, 3]],
47
113
  ]);
@@ -60,12 +126,12 @@ describe("Origami parser", () => {
60
126
  assertParse("arrowFunction", "() => foo", [
61
127
  ops.lambda,
62
128
  [],
63
- [ops.scope, "foo"],
129
+ [markers.traverse, [markers.reference, "foo"]],
64
130
  ]);
65
131
  assertParse("arrowFunction", "x => y", [
66
132
  ops.lambda,
67
133
  [[ops.literal, "x"]],
68
- [ops.scope, "y"],
134
+ [markers.traverse, [markers.reference, "y"]],
69
135
  ]);
70
136
  assertParse("arrowFunction", "(a, b, c) ⇒ fn(a, b, c)", [
71
137
  ops.lambda,
@@ -75,10 +141,10 @@ describe("Origami parser", () => {
75
141
  [ops.literal, "c"],
76
142
  ],
77
143
  [
78
- [ops.builtin, "fn"],
79
- [ops.scope, "a"],
80
- [ops.scope, "b"],
81
- [ops.scope, "c"],
144
+ [markers.traverse, [markers.reference, "fn"]],
145
+ [markers.traverse, [markers.reference, "a"]],
146
+ [markers.traverse, [markers.reference, "b"]],
147
+ [markers.traverse, [markers.reference, "c"]],
82
148
  ],
83
149
  ]);
84
150
  assertParse("arrowFunction", "a => b => fn(a, b)", [
@@ -88,9 +154,9 @@ describe("Origami parser", () => {
88
154
  ops.lambda,
89
155
  [[ops.literal, "b"]],
90
156
  [
91
- [ops.builtin, "fn"],
92
- [ops.scope, "a"],
93
- [ops.scope, "b"],
157
+ [markers.traverse, [markers.reference, "fn"]],
158
+ [markers.traverse, [markers.reference, "a"]],
159
+ [markers.traverse, [markers.reference, "b"]],
94
160
  ],
95
161
  ],
96
162
  ]);
@@ -120,147 +186,149 @@ describe("Origami parser", () => {
120
186
  ]);
121
187
  });
122
188
 
123
- test("callExpression", () => {
124
- assertParse("callExpression", "fn()", [[ops.builtin, "fn"], undefined]);
125
- assertParse("callExpression", "foo.js(arg)", [
126
- [ops.scope, "foo.js"],
127
- [ops.scope, "arg"],
128
- ]);
129
- assertParse("callExpression", "fn(a, b)", [
130
- [ops.builtin, "fn"],
131
- [ops.scope, "a"],
132
- [ops.scope, "b"],
133
- ]);
134
- assertParse("callExpression", "foo.js( a , b )", [
135
- [ops.scope, "foo.js"],
136
- [ops.scope, "a"],
137
- [ops.scope, "b"],
138
- ]);
139
- assertParse("callExpression", "fn()(arg)", [
140
- [[ops.builtin, "fn"], undefined],
141
- [ops.scope, "arg"],
142
- ]);
143
- assertParse("callExpression", "tree/", [ops.unpack, [ops.scope, "tree/"]]);
144
- assertParse("callExpression", "tree/foo/bar", [
145
- ops.traverse,
146
- [ops.scope, "tree/"],
147
- [ops.literal, "foo/"],
148
- [ops.literal, "bar"],
149
- ]);
150
- assertParse("callExpression", "tree/foo/bar/", [
151
- ops.traverse,
152
- [ops.scope, "tree/"],
153
- [ops.literal, "foo/"],
154
- [ops.literal, "bar/"],
155
- ]);
156
- assertParse("callExpression", "/foo/bar", [
157
- ops.traverse,
158
- [ops.rootDirectory, [ops.literal, "foo/"]],
159
- [ops.literal, "bar"],
160
- ]);
161
- assertParse("callExpression", "foo.js()/key", [
162
- ops.traverse,
163
- [[ops.scope, "foo.js"], undefined],
164
- [ops.literal, "key"],
165
- ]);
166
- assertParse("callExpression", "tree/key()", [
167
- [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
168
- undefined,
169
- ]);
170
- assertParse("callExpression", "(tree)/", [ops.unpack, [ops.scope, "tree"]]);
171
- assertParse("callExpression", "fn()/key()", [
172
- [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
173
- undefined,
174
- ]);
175
- assertParse("callExpression", "(foo.js())('arg')", [
176
- [[ops.scope, "foo.js"], undefined],
177
- [ops.literal, "arg"],
178
- ]);
179
- assertParse("callExpression", "fn('a')('b')", [
180
- [
181
- [ops.builtin, "fn"],
182
- [ops.literal, "a"],
183
- ],
184
- [ops.literal, "b"],
185
- ]);
186
- assertParse("callExpression", "(foo.js())(a, b)", [
187
- [[ops.scope, "foo.js"], undefined],
188
- [ops.scope, "a"],
189
- [ops.scope, "b"],
190
- ]);
191
- assertParse("callExpression", "{ a: 1, b: 2}/b", [
192
- ops.traverse,
193
- [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
194
- [ops.literal, "b"],
195
- ]);
196
- assertParse("callExpression", "indent`hello`", [
197
- [ops.builtin, "indent"],
198
- [ops.literal, ["hello"]],
199
- ]);
200
- assertParse("callExpression", "fn.js`Hello, world.`", [
201
- [ops.scope, "fn.js"],
202
- [ops.literal, ["Hello, world."]],
203
- ]);
204
- assertParse("callExpression", "files:src/assets", [
205
- ops.traverse,
206
- [
207
- [ops.builtin, "files:"],
208
- [ops.literal, "src/"],
209
- ],
210
- [ops.literal, "assets"],
211
- ]);
212
- assertParse("callExpression", "new:(js:Date, '2025-01-01')", [
213
- [ops.builtin, "new:"],
214
- [
215
- [ops.builtin, "js:"],
216
- [ops.literal, "Date"],
217
- ],
218
- [ops.literal, "2025-01-01"],
219
- ]);
220
- assertParse("callExpression", "map(markdown, mdHtml)", [
221
- [ops.builtin, "map"],
222
- [ops.scope, "markdown"],
223
- [ops.scope, "mdHtml"],
224
- ]);
225
- assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
226
- [
227
- ops.traverse,
189
+ describe("callExpression", () => {
190
+ test("with parentheses arguments", () => {
191
+ assertParse("callExpression", "fn()", [
192
+ [markers.traverse, [markers.reference, "fn"]],
193
+ undefined,
194
+ ]);
195
+ assertParse("callExpression", "foo.js(arg)", [
196
+ [markers.traverse, [markers.reference, "foo.js"]],
197
+ [markers.traverse, [markers.reference, "arg"]],
198
+ ]);
199
+ assertParse("callExpression", "fn(a, b)", [
200
+ [markers.traverse, [markers.reference, "fn"]],
201
+ [markers.traverse, [markers.reference, "a"]],
202
+ [markers.traverse, [markers.reference, "b"]],
203
+ ]);
204
+ assertParse("callExpression", "foo.js( a , b )", [
205
+ [markers.traverse, [markers.reference, "foo.js"]],
206
+ [markers.traverse, [markers.reference, "a"]],
207
+ [markers.traverse, [markers.reference, "b"]],
208
+ ]);
209
+ assertParse("callExpression", "fn()(arg)", [
210
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
211
+ [markers.traverse, [markers.reference, "arg"]],
212
+ ]);
213
+ });
214
+
215
+ test("call chains", () => {
216
+ assertParse("callExpression", "(foo.js())('arg')", [
217
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
218
+ [ops.literal, "arg"],
219
+ ]);
220
+ assertParse("callExpression", "fn('a')('b')", [
228
221
  [
229
- [ops.builtin, "package:"],
222
+ [markers.traverse, [markers.reference, "fn"]],
223
+ [ops.literal, "a"],
224
+ ],
225
+ [ops.literal, "b"],
226
+ ]);
227
+ assertParse("callExpression", "(foo.js())(a, b)", [
228
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
229
+ [markers.traverse, [markers.reference, "a"]],
230
+ [markers.traverse, [markers.reference, "b"]],
231
+ ]);
232
+ });
233
+
234
+ test("with paths", () => {
235
+ assertParse("callExpression", "tree/", [
236
+ ops.unpack,
237
+ [markers.traverse, [markers.reference, "tree/"]],
238
+ ]);
239
+ assertParse("callExpression", "tree/foo/bar", [
240
+ markers.traverse,
241
+ [markers.reference, "tree/"],
242
+ [ops.literal, "foo/"],
243
+ [ops.literal, "bar"],
244
+ ]);
245
+ assertParse("callExpression", "tree/foo/bar/", [
246
+ ops.unpack,
247
+ [
248
+ markers.traverse,
249
+ [markers.reference, "tree/"],
250
+ [ops.literal, "foo/"],
251
+ [ops.literal, "bar/"],
252
+ ],
253
+ ]);
254
+ // Consecutive slahes in a path are removed
255
+ assertParse("callExpression", "tree//key", [
256
+ markers.traverse,
257
+ [markers.reference, "tree/"],
258
+ [ops.literal, "key"],
259
+ ]);
260
+ assertParse("callExpression", "{ a: 1, b: 2}/b", [
261
+ [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
262
+ [ops.literal, "b"],
263
+ ]);
264
+ assertParse("callExpression", "files:foo/bar", [
265
+ [markers.global, "files:"],
266
+ [ops.literal, "foo/"],
267
+ [ops.literal, "bar"],
268
+ ]);
269
+ });
270
+
271
+ test("path and parentheses chains", () => {
272
+ assertParse("callExpression", "foo.js()/key", [
273
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
274
+ [ops.literal, "key"],
275
+ ]);
276
+ assertParse("callExpression", "tree/key()", [
277
+ [markers.traverse, [markers.reference, "tree/"], [ops.literal, "key"]],
278
+ undefined,
279
+ ]);
280
+ assertParse("callExpression", "fn()/key()", [
281
+ [
282
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
283
+ [ops.literal, "key"],
284
+ ],
285
+ undefined,
286
+ ]);
287
+ assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
288
+ [
289
+ [markers.global, "package:"],
230
290
  [ops.literal, "@weborigami/"],
291
+ [ops.literal, "dropbox/"],
292
+ [ops.literal, "auth"],
231
293
  ],
232
- [ops.literal, "dropbox/"],
233
- [ops.literal, "auth"],
234
- ],
235
- [ops.scope, "creds"],
236
- ]);
237
- });
294
+ [markers.traverse, [markers.reference, "creds"]],
295
+ ]);
296
+ });
238
297
 
239
- test("callExpression using property acccess", () => {
240
- assertParse("callExpression", "(foo).bar", [
241
- ops.traverse,
242
- [ops.scope, "foo"],
243
- [ops.literal, "bar"],
244
- ]);
245
- assertParse("callExpression", "(foo).bar.baz", [
246
- ops.traverse,
247
- [ops.traverse, [ops.scope, "foo"], [ops.literal, "bar"]],
248
- [ops.literal, "baz"],
249
- ]);
250
- assertParse("callExpression", "foo[bar]", [
251
- ops.traverse,
252
- [ops.scope, "foo/"],
253
- [ops.scope, "bar"],
254
- ]);
298
+ test("tagged templates", () => {
299
+ assertParse("callExpression", "indent`hello`", [
300
+ [markers.traverse, [markers.reference, "indent"]],
301
+ [ops.literal, ["hello"]],
302
+ ]);
303
+ assertParse("callExpression", "fn.js`Hello, world.`", [
304
+ [markers.traverse, [markers.reference, "fn.js"]],
305
+ [ops.literal, ["Hello, world."]],
306
+ ]);
307
+ });
308
+
309
+ test("protocols", () => {
310
+ assertParse("callExpression", "files:src/assets", [
311
+ [markers.global, "files:"],
312
+ [ops.literal, "src/"],
313
+ [ops.literal, "assets"],
314
+ ]);
315
+ assertParse("callExpression", "<node:process>.env", [
316
+ [
317
+ [markers.global, "node:"],
318
+ [ops.literal, "process"],
319
+ ],
320
+ [ops.literal, "env"],
321
+ ]);
322
+ });
255
323
  });
256
324
 
257
325
  test("commaExpression", () => {
258
326
  assertParse("commaExpression", "1", [ops.literal, 1]);
259
327
  assertParse("commaExpression", "a, b, c", [
260
328
  ops.comma,
261
- [ops.scope, "a"],
262
- [ops.scope, "b"],
263
- [ops.scope, "c"],
329
+ [markers.traverse, [markers.reference, "a"]],
330
+ [markers.traverse, [markers.reference, "b"]],
331
+ [markers.traverse, [markers.reference, "c"]],
264
332
  ]);
265
333
  });
266
334
 
@@ -268,19 +336,19 @@ describe("Origami parser", () => {
268
336
  assertParse("conditionalExpression", "1", [ops.literal, 1]);
269
337
  assertParse("conditionalExpression", "true ? 1 : 0", [
270
338
  ops.conditional,
271
- [ops.scope, "true"],
339
+ [markers.traverse, [markers.reference, "true"]],
272
340
  [ops.literal, 1],
273
341
  [ops.literal, 0],
274
342
  ]);
275
343
  assertParse("conditionalExpression", "false ? () => 1 : 0", [
276
344
  ops.conditional,
277
- [ops.scope, "false"],
345
+ [markers.traverse, [markers.reference, "false"]],
278
346
  [ops.lambda, [], [ops.lambda, [], [ops.literal, 1]]],
279
347
  [ops.literal, 0],
280
348
  ]);
281
349
  assertParse("conditionalExpression", "false ? =1 : 0", [
282
350
  ops.conditional,
283
- [ops.scope, "false"],
351
+ [markers.traverse, [markers.reference, "false"]],
284
352
  [ops.lambda, [], [ops.lambda, [[ops.literal, "_"]], [ops.literal, 1]]],
285
353
  [ops.literal, 0],
286
354
  ]);
@@ -294,8 +362,12 @@ describe("Origami parser", () => {
294
362
  ]);
295
363
  assertParse("equalityExpression", "a === b === c", [
296
364
  ops.strictEqual,
297
- [ops.strictEqual, [undetermined, "a"], [undetermined, "b"]],
298
- [undetermined, "c"],
365
+ [
366
+ ops.strictEqual,
367
+ [markers.traverse, [markers.reference, "a"]],
368
+ [markers.traverse, [markers.reference, "b"]],
369
+ ],
370
+ [markers.traverse, [markers.reference, "c"]],
299
371
  ]);
300
372
  assertParse("equalityExpression", "1 !== 1", [
301
373
  ops.notStrictEqual,
@@ -349,260 +421,357 @@ Body`,
349
421
  ]);
350
422
  });
351
423
 
352
- test("expression", () => {
353
- assertParse(
354
- "expression",
355
- `{
424
+ describe("expression", () => {
425
+ test("slash with and without spaces", () => {
426
+ assertParse("expression", "x/y", [
427
+ markers.traverse,
428
+ [markers.reference, "x/"],
429
+ [ops.literal, "y"],
430
+ ]);
431
+ assertParse("expression", "x / y", [
432
+ ops.division,
433
+ [markers.traverse, [markers.reference, "x"]],
434
+ [markers.traverse, [markers.reference, "y"]],
435
+ ]);
436
+ // Parses as a call, not a path
437
+ assertParse("expression", "(x)/y", [
438
+ [markers.traverse, [markers.reference, "x"]],
439
+ [ops.literal, "y"],
440
+ ]);
441
+ });
442
+
443
+ test("operators with spaces = math", () => {
444
+ assertParse("expression", "a + b", [
445
+ ops.addition,
446
+ [markers.traverse, [markers.reference, "a"]],
447
+ [markers.traverse, [markers.reference, "b"]],
448
+ ]);
449
+ assertParse("expression", "a - b", [
450
+ ops.subtraction,
451
+ [markers.traverse, [markers.reference, "a"]],
452
+ [markers.traverse, [markers.reference, "b"]],
453
+ ]);
454
+ assertParse("expression", "a & b", [
455
+ ops.bitwiseAnd,
456
+ [markers.traverse, [markers.reference, "a"]],
457
+ [markers.traverse, [markers.reference, "b"]],
458
+ ]);
459
+ });
460
+
461
+ test("operators without spaces = reference", () => {
462
+ assertParse("expression", "a+b", [
463
+ markers.traverse,
464
+ [markers.reference, "a+b"],
465
+ ]);
466
+ assertParse("expression", "a-b", [
467
+ markers.traverse,
468
+ [markers.reference, "a-b"],
469
+ ]);
470
+ assertParse("expression", "a&b", [
471
+ markers.traverse,
472
+ [markers.reference, "a&b"],
473
+ ]);
474
+ });
475
+
476
+ test("property acccess", () => {
477
+ assertParse("expression", "(foo).bar", [
478
+ [markers.traverse, [markers.reference, "foo"]],
479
+ [ops.literal, "bar"],
480
+ ]);
481
+ assertParse("expression", "(foo).bar.baz", [
482
+ [
483
+ [markers.traverse, [markers.reference, "foo"]],
484
+ [ops.literal, "bar"],
485
+ ],
486
+ [ops.literal, "baz"],
487
+ ]);
488
+ assertParse("expression", "foo[bar]", [
489
+ [markers.traverse, [markers.reference, "foo"]],
490
+ [markers.traverse, [markers.reference, "bar"]],
491
+ ]);
492
+ assertParse("expression", "Tree.map", [
493
+ markers.traverse,
494
+ [markers.reference, "Tree.map"],
495
+ ]);
496
+ });
497
+
498
+ test("consecutive slashes at start of something = comment", () => {
499
+ assertParse(
500
+ "expression",
501
+ "x //comment",
502
+ [markers.traverse, [markers.reference, "x"]],
503
+ "jse",
504
+ false
505
+ );
506
+ });
507
+
508
+ test("complex expressions", () => {
509
+ assertParse("expression", "page.ori(mdHtml(about.md))", [
510
+ [markers.traverse, [markers.reference, "page.ori"]],
511
+ [
512
+ [markers.traverse, [markers.reference, "mdHtml"]],
513
+ [markers.traverse, [markers.reference, "about.md"]],
514
+ ],
515
+ ]);
516
+
517
+ assertParse("expression", "keys(</>)", [
518
+ [markers.traverse, [markers.reference, "keys"]],
519
+ [markers.traverse, [markers.external, "/"]],
520
+ ]);
521
+
522
+ assertParse("expression", "'Hello' -> test.ori.html", [
523
+ [markers.traverse, [markers.reference, "test.ori.html"]],
524
+ [ops.literal, "Hello"],
525
+ ]);
526
+ assertParse("expression", "obj.json", [
527
+ markers.traverse,
528
+ [markers.reference, "obj.json"],
529
+ ]);
530
+ assertParse("expression", "(fn a, b, c)", [
531
+ [markers.traverse, [markers.reference, "fn"]],
532
+ [markers.traverse, [markers.reference, "a"]],
533
+ [markers.traverse, [markers.reference, "b"]],
534
+ [markers.traverse, [markers.reference, "c"]],
535
+ ]);
536
+ assertParse("expression", "foo.bar('hello', 'world')", [
537
+ [markers.traverse, [markers.reference, "foo.bar"]],
538
+ [ops.literal, "hello"],
539
+ [ops.literal, "world"],
540
+ ]);
541
+ assertParse("expression", "(key)('a')", [
542
+ [markers.traverse, [markers.reference, "key"]],
543
+ [ops.literal, "a"],
544
+ ]);
545
+ assertParse("expression", "1", [ops.literal, 1]);
546
+ assertParse("expression", "{ a: 1, b: 2 }", [
547
+ ops.object,
548
+ ["a", [ops.literal, 1]],
549
+ ["b", [ops.literal, 2]],
550
+ ]);
551
+ assertParse("expression", "serve { index.html: 'hello' }", [
552
+ [markers.traverse, [markers.reference, "serve"]],
553
+ [ops.object, ["index.html", [ops.literal, "hello"]]],
554
+ ]);
555
+ assertParse("expression", "fn =`x`", [
556
+ [markers.traverse, [markers.reference, "fn"]],
557
+ [
558
+ ops.lambda,
559
+ [[ops.literal, "_"]],
560
+ [ops.templateTree, [ops.literal, ["x"]]],
561
+ ],
562
+ ]);
563
+ assertParse("expression", "copy app.js(formulas), files:snapshot", [
564
+ [markers.traverse, [markers.reference, "copy"]],
565
+ [
566
+ [markers.traverse, [markers.reference, "app.js"]],
567
+ [markers.traverse, [markers.reference, "formulas"]],
568
+ ],
569
+ [
570
+ [markers.global, "files:"],
571
+ [ops.literal, "snapshot"],
572
+ ],
573
+ ]);
574
+ assertParse("expression", "map =`<li>${_}</li>`", [
575
+ [markers.traverse, [markers.reference, "map"]],
576
+ [
577
+ ops.lambda,
578
+ [[ops.literal, "_"]],
579
+ [
580
+ ops.templateTree,
581
+ [ops.literal, ["<li>", "</li>"]],
582
+ [markers.traverse, [markers.reference, "_"]],
583
+ ],
584
+ ],
585
+ ]);
586
+ assertParse("expression", `https://example.com/about/`, [
587
+ [markers.global, "https:"],
588
+ [ops.literal, "example.com/"],
589
+ [ops.literal, "about/"],
590
+ ]);
591
+ assertParse("expression", "tag`Hello, ${name}!`", [
592
+ [markers.traverse, [markers.reference, "tag"]],
593
+ [ops.literal, ["Hello, ", "!"]],
594
+ [ops.concat, [markers.traverse, [markers.reference, "name"]]],
595
+ ]);
596
+ assertParse("expression", "=tag`Hello, ${_}!`", [
597
+ ops.lambda,
598
+ [[ops.literal, "_"]],
599
+ [
600
+ [markers.traverse, [markers.reference, "tag"]],
601
+ [ops.literal, ["Hello, ", "!"]],
602
+ [ops.concat, [markers.traverse, [markers.reference, "_"]]],
603
+ ],
604
+ ]);
605
+ assertParse("expression", "(post, slug) => fn.js(post, slug)", [
606
+ ops.lambda,
607
+ [
608
+ [ops.literal, "post"],
609
+ [ops.literal, "slug"],
610
+ ],
611
+ [
612
+ [markers.traverse, [markers.reference, "fn.js"]],
613
+ [markers.traverse, [markers.reference, "post"]],
614
+ [markers.traverse, [markers.reference, "slug"]],
615
+ ],
616
+ ]);
617
+ assertParse("expression", "keys(<~>)", [
618
+ [markers.traverse, [markers.reference, "keys"]],
619
+ [markers.traverse, [markers.external, "~"]],
620
+ ]);
621
+ });
622
+
623
+ test("complex object", () => {
624
+ assertParse(
625
+ "expression",
626
+ `{
356
627
  index.html = index.ori(teamData.yaml)
357
628
  thumbnails = map(images, { value: thumbnail.js })
358
629
  }`,
359
- [
360
- ops.object,
361
630
  [
362
- "index.html",
631
+ ops.object,
363
632
  [
364
- ops.getter,
633
+ "index.html",
365
634
  [
366
- [ops.scope, "index.ori"],
367
- [ops.scope, "teamData.yaml"],
635
+ ops.getter,
636
+ [
637
+ [markers.traverse, [markers.reference, "index.ori"]],
638
+ [markers.traverse, [markers.reference, "teamData.yaml"]],
639
+ ],
368
640
  ],
369
641
  ],
370
- ],
371
- [
372
- "thumbnails",
373
642
  [
374
- ops.getter,
643
+ "thumbnails",
375
644
  [
376
- [ops.builtin, "map"],
377
- [ops.scope, "images"],
378
- [ops.object, ["value", [ops.scope, "thumbnail.js"]]],
645
+ ops.getter,
646
+ [
647
+ [markers.traverse, [markers.reference, "map"]],
648
+ [markers.traverse, [markers.reference, "images"]],
649
+ [
650
+ ops.object,
651
+ [
652
+ "value",
653
+ [markers.traverse, [markers.reference, "thumbnail.js"]],
654
+ ],
655
+ ],
656
+ ],
379
657
  ],
380
658
  ],
381
- ],
382
- ]
383
- );
384
-
385
- // Builtin on its own is the function itself, not a function call
386
- assertParse("expression", "mdHtml:", [ops.builtin, "mdHtml:"]);
387
-
388
- // Consecutive slahes in a path are removed
389
- assertParse("expression", "path//key", [
390
- ops.traverse,
391
- [ops.scope, "path/"],
392
- [ops.literal, "key"],
393
- ]);
394
-
395
- // Single slash at start of something = absolute file path
396
- assertParse("expression", "/path", [
397
- ops.rootDirectory,
398
- [ops.literal, "path"],
399
- ]);
400
-
401
- // Consecutive slashes at start of something = comment
402
- assertParse("expression", "path //comment", [ops.scope, "path"], false);
403
- assertParse("expression", "page.ori(mdHtml:(about.md))", [
404
- [ops.scope, "page.ori"],
405
- [
406
- [ops.builtin, "mdHtml:"],
407
- [ops.scope, "about.md"],
408
- ],
409
- ]);
410
-
411
- // Slash on its own is the root folder
412
- assertParse("expression", "keys /", [
413
- [ops.builtin, "keys"],
414
- [ops.rootDirectory],
415
- ]);
659
+ ]
660
+ );
661
+ });
662
+ });
416
663
 
417
- assertParse("expression", "'Hello' -> test.orit", [
418
- [ops.scope, "test.orit"],
419
- [ops.literal, "Hello"],
420
- ]);
421
- assertParse("expression", "obj.json", [ops.scope, "obj.json"]);
422
- assertParse("expression", "(fn a, b, c)", [
423
- [ops.builtin, "fn"],
424
- [undetermined, "a"],
425
- [undetermined, "b"],
426
- [undetermined, "c"],
427
- ]);
428
- assertParse("expression", "foo.bar('hello', 'world')", [
429
- [ops.scope, "foo.bar"],
430
- [ops.literal, "hello"],
431
- [ops.literal, "world"],
432
- ]);
433
- assertParse("expression", "(key)('a')", [
434
- [ops.scope, "key"],
435
- [ops.literal, "a"],
436
- ]);
437
- assertParse("expression", "1", [ops.literal, 1]);
438
- assertParse("expression", "{ a: 1, b: 2 }", [
439
- ops.object,
440
- ["a", [ops.literal, 1]],
441
- ["b", [ops.literal, 2]],
442
- ]);
443
- assertParse("expression", "serve { index.html: 'hello' }", [
444
- [ops.builtin, "serve"],
445
- [ops.object, ["index.html", [ops.literal, "hello"]]],
446
- ]);
447
- assertParse("expression", "fn =`x`", [
448
- [ops.builtin, "fn"],
449
- [ops.lambda, [[ops.literal, "_"]], [ops.template, [ops.literal, ["x"]]]],
450
- ]);
451
- assertParse("expression", "copy app.js(formulas), files:snapshot", [
452
- [ops.builtin, "copy"],
453
- [
454
- [ops.scope, "app.js"],
455
- [ops.scope, "formulas"],
456
- ],
457
- [
458
- [ops.builtin, "files:"],
459
- [ops.literal, "snapshot"],
460
- ],
461
- ]);
462
- assertParse("expression", "map =`<li>${_}</li>`", [
463
- [ops.builtin, "map"],
664
+ test("frontMatterExpression", () => {
665
+ assertParse(
666
+ "frontMatterExpression",
667
+ `---
668
+ (name) => _template()
669
+ ---
670
+ `,
464
671
  [
465
672
  ops.lambda,
466
- [[ops.literal, "_"]],
467
- [ops.template, [ops.literal, ["<li>", "</li>"]], [ops.scope, "_"]],
673
+ [[ops.literal, "name"]],
674
+ [[markers.traverse, [markers.reference, "_template"]], undefined],
468
675
  ],
469
- ]);
470
- assertParse("expression", `https://example.com/about/`, [
471
- [ops.builtin, "https:"],
472
- [ops.literal, "example.com/"],
473
- [ops.literal, "about/"],
474
- ]);
475
- assertParse("expression", "tag`Hello, ${name}!`", [
476
- [ops.builtin, "tag"],
477
- [ops.literal, ["Hello, ", "!"]],
478
- [ops.concat, [ops.scope, "name"]],
479
- ]);
480
- assertParse("expression", "(post, slug) => fn.js(post, slug)", [
481
- ops.lambda,
482
- [
483
- [ops.literal, "post"],
484
- [ops.literal, "slug"],
485
- ],
486
- [
487
- [ops.scope, "fn.js"],
488
- [ops.scope, "post"],
489
- [ops.scope, "slug"],
490
- ],
491
- ]);
492
- assertParse("expression", "keys ~", [
493
- [ops.builtin, "keys"],
494
- [ops.homeDirectory],
495
- ]);
496
- assertParse("expression", "keys /Users/alice", [
497
- [ops.builtin, "keys"],
498
- [
499
- ops.traverse,
500
- [ops.rootDirectory, [ops.literal, "Users/"]],
501
- [ops.literal, "alice"],
502
- ],
503
- ]);
676
+ "jse",
677
+ false
678
+ );
679
+ });
504
680
 
505
- // Verify parser treatment of identifiers containing operators
506
- assertParse("expression", "a + b", [
507
- ops.addition,
508
- [undetermined, "a"],
509
- [undetermined, "b"],
681
+ test("group", () => {
682
+ assertParse("group", "(hello)", [
683
+ markers.traverse,
684
+ [markers.reference, "hello"],
510
685
  ]);
511
- assertParse("expression", "a+b", [ops.scope, "a+b"]);
512
- assertParse("expression", "a - b", [
513
- ops.subtraction,
514
- [undetermined, "a"],
515
- [undetermined, "b"],
686
+ assertParse("group", "(((nested)))", [
687
+ markers.traverse,
688
+ [markers.reference, "nested"],
516
689
  ]);
517
- assertParse("expression", "a-b", [ops.scope, "a-b"]);
518
- assertParse("expression", "a&b", [ops.scope, "a&b"]);
519
- assertParse("expression", "a & b", [
520
- ops.bitwiseAnd,
521
- [undetermined, "a"],
522
- [undetermined, "b"],
690
+ assertParse("group", "(fn())", [
691
+ [markers.traverse, [markers.reference, "fn"]],
692
+ undefined,
523
693
  ]);
524
- });
525
-
526
- test("group", () => {
527
- assertParse("group", "(hello)", [ops.scope, "hello"]);
528
- assertParse("group", "(((nested)))", [ops.scope, "nested"]);
529
- assertParse("group", "(fn())", [[ops.builtin, "fn"], undefined]);
530
694
  assertParse("group", "(a -> b)", [
531
- [ops.builtin, "b"],
532
- [ops.scope, "a"],
695
+ [markers.traverse, [markers.reference, "b"]],
696
+ [markers.traverse, [markers.reference, "a"]],
533
697
  ]);
534
698
  });
535
699
 
536
- test("homeDirectory", () => {
537
- assertParse("homeDirectory", "~", [ops.homeDirectory]);
538
- });
539
-
540
700
  test("host", () => {
541
701
  assertParse("host", "abc", [ops.literal, "abc"]);
542
702
  assertParse("host", "abc:123", [ops.literal, "abc:123"]);
543
- assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
544
703
  });
545
704
 
546
705
  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);
706
+ assertParse("identifier", "foo", "foo", "jse", false);
707
+ assertParse("identifier", "$Δelta", "$Δelta", "jse", false);
708
+ assertThrows(
709
+ "identifier",
710
+ "1stCharacterIsNumber",
711
+ "Expected JavaScript identifier start"
712
+ );
713
+ assertThrows(
714
+ "identifier",
715
+ "has space",
716
+ "Expected JavaScript identifier continuation"
717
+ );
718
+ assertThrows(
719
+ "identifier",
720
+ "foo.bar",
721
+ "Expected JavaScript identifier continuation"
722
+ );
551
723
  });
552
724
 
553
725
  test("implicitParenthesesCallExpression", () => {
554
726
  assertParse("implicitParenthesesCallExpression", "fn arg", [
555
- [ops.builtin, "fn"],
556
- [undetermined, "arg"],
727
+ [markers.traverse, [markers.reference, "fn"]],
728
+ [markers.traverse, [markers.reference, "arg"]],
557
729
  ]);
558
730
  assertParse("implicitParenthesesCallExpression", "page.ori 'a', 'b'", [
559
- [ops.scope, "page.ori"],
731
+ [markers.traverse, [markers.reference, "page.ori"]],
560
732
  [ops.literal, "a"],
561
733
  [ops.literal, "b"],
562
734
  ]);
563
735
  assertParse("implicitParenthesesCallExpression", "fn a(b), c", [
564
- [ops.builtin, "fn"],
736
+ [markers.traverse, [markers.reference, "fn"]],
565
737
  [
566
- [ops.builtin, "a"],
567
- [ops.scope, "b"],
738
+ [markers.traverse, [markers.reference, "a"]],
739
+ [markers.traverse, [markers.reference, "b"]],
568
740
  ],
569
- [undetermined, "c"],
741
+ [markers.traverse, [markers.reference, "c"]],
570
742
  ]);
571
743
  assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
572
- [[ops.builtin, "fn"], undefined],
744
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
573
745
  [ops.literal, "arg"],
574
746
  ]);
575
- assertParse("implicitParenthesesCallExpression", "tree/key arg", [
576
- [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
577
- [undetermined, "arg"],
578
- ]);
747
+ assertParse(
748
+ "implicitParenthesesCallExpression",
749
+ "tree/key arg",
750
+ [
751
+ [markers.traverse, [markers.reference, "tree/"], [ops.literal, "key"]],
752
+ [markers.traverse, [markers.reference, "arg"]],
753
+ ],
754
+ "shell"
755
+ );
579
756
  assertParse("implicitParenthesesCallExpression", "foo.js bar.ori 'arg'", [
580
- [ops.scope, "foo.js"],
757
+ [markers.traverse, [markers.reference, "foo.js"]],
581
758
  [
582
- [ops.scope, "bar.ori"],
759
+ [markers.traverse, [markers.reference, "bar.ori"]],
583
760
  [ops.literal, "arg"],
584
761
  ],
585
762
  ]);
586
763
  });
587
764
 
588
- test("jsIdentifier", () => {
589
- assertParse("jsIdentifier", "foo", "foo", false);
590
- assertParse("jsIdentifier", "$Δelta", "$Δelta", false);
591
- assertThrows(
592
- "jsIdentifier",
593
- "1stCharacterIsNumber",
594
- "Expected JavaScript identifier start"
595
- );
596
- assertThrows(
597
- "jsIdentifier",
598
- "has space",
599
- "Expected JavaScript identifier continuation"
600
- );
601
- assertThrows(
602
- "jsIdentifier",
603
- "foo.bar",
604
- "Expected JavaScript identifier continuation"
605
- );
765
+ test("key", () => {
766
+ assertParse("key", "a", "a");
767
+ assertParse("key", "_b", "_b");
768
+ assertParse("key", ".ssh", ".ssh");
769
+ assertParse("key", "index.html", "index.html");
770
+ assertParse("key", "404.html", "404.html");
771
+ assertParse("key", "1a2b3c", "1a2b3c");
772
+ assertParse("key", "a~b", "a~b");
773
+ assertParse("key", "foo-bar", "foo-bar");
774
+ assertParse("key", "package-lock.json", "package-lock.json");
606
775
  });
607
776
 
608
777
  test("list", () => {
@@ -637,8 +806,8 @@ Body`,
637
806
  test("logicalAndExpression", () => {
638
807
  assertParse("logicalAndExpression", "true && false", [
639
808
  ops.logicalAnd,
640
- [ops.scope, "true"],
641
- [ops.lambda, [], [undetermined, "false"]],
809
+ [markers.traverse, [markers.reference, "true"]],
810
+ [ops.lambda, [], [markers.traverse, [markers.reference, "false"]]],
642
811
  ]);
643
812
  });
644
813
 
@@ -650,9 +819,9 @@ Body`,
650
819
  ]);
651
820
  assertParse("logicalOrExpression", "false || false || true", [
652
821
  ops.logicalOr,
653
- [ops.scope, "false"],
654
- [ops.lambda, [], [undetermined, "false"]],
655
- [ops.lambda, [], [undetermined, "true"]],
822
+ [markers.traverse, [markers.reference, "false"]],
823
+ [ops.lambda, [], [markers.traverse, [markers.reference, "false"]]],
824
+ [ops.lambda, [], [markers.traverse, [markers.reference, "true"]]],
656
825
  ]);
657
826
  assertParse("logicalOrExpression", "1 || 2 && 0", [
658
827
  ops.logicalOr,
@@ -662,7 +831,13 @@ Body`,
662
831
  });
663
832
 
664
833
  test("multiLineComment", () => {
665
- assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
834
+ assertParse(
835
+ "multiLineComment",
836
+ "/*\nHello, world!\n*/",
837
+ null,
838
+ "jse",
839
+ false
840
+ );
666
841
  });
667
842
 
668
843
  test("multiplicativeExpression", () => {
@@ -683,21 +858,28 @@ Body`,
683
858
  ]);
684
859
  });
685
860
 
686
- test("namespace", () => {
687
- assertParse("namespace", "js:", [ops.builtin, "js:"]);
861
+ test("newExpression", () => {
862
+ assertParse("newExpression", "new Foo()", [
863
+ ops.construct,
864
+ [markers.traverse, [markers.reference, "Foo"]],
865
+ ]);
866
+ assertParse("newExpression", "new:Foo()", [
867
+ ops.construct,
868
+ [markers.traverse, [markers.reference, "Foo"]],
869
+ ]);
688
870
  });
689
871
 
690
872
  test("nullishCoalescingExpression", () => {
691
873
  assertParse("nullishCoalescingExpression", "a ?? b", [
692
874
  ops.nullishCoalescing,
693
- [ops.scope, "a"],
694
- [ops.lambda, [], [undetermined, "b"]],
875
+ [markers.traverse, [markers.reference, "a"]],
876
+ [ops.lambda, [], [markers.traverse, [markers.reference, "b"]]],
695
877
  ]);
696
878
  assertParse("nullishCoalescingExpression", "a ?? b ?? c", [
697
879
  ops.nullishCoalescing,
698
- [ops.scope, "a"],
699
- [ops.lambda, [], [undetermined, "b"]],
700
- [ops.lambda, [], [undetermined, "c"]],
880
+ [markers.traverse, [markers.reference, "a"]],
881
+ [ops.lambda, [], [markers.traverse, [markers.reference, "b"]]],
882
+ [ops.lambda, [], [markers.traverse, [markers.reference, "c"]]],
701
883
  ]);
702
884
  });
703
885
 
@@ -712,7 +894,7 @@ Body`,
712
894
  assertParse("objectLiteral", "{ a: 1, b }", [
713
895
  ops.object,
714
896
  ["a", [ops.literal, 1]],
715
- ["b", [ops.inherited, "b"]],
897
+ ["b", [markers.traverse, [markers.reference, "b"]]],
716
898
  ]);
717
899
  assertParse("objectLiteral", "{ sub: { a: 1 } }", [
718
900
  ops.object,
@@ -729,7 +911,7 @@ Body`,
729
911
  ]);
730
912
  assertParse("objectLiteral", "{ a = b, b = 2 }", [
731
913
  ops.object,
732
- ["a", [ops.getter, [ops.scope, "b"]]],
914
+ ["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
733
915
  ["b", [ops.literal, 2]],
734
916
  ]);
735
917
  assertParse(
@@ -740,7 +922,7 @@ Body`,
740
922
  }`,
741
923
  [
742
924
  ops.object,
743
- ["a", [ops.getter, [ops.scope, "b"]]],
925
+ ["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
744
926
  ["b", [ops.literal, 2]],
745
927
  ]
746
928
  );
@@ -756,7 +938,16 @@ Body`,
756
938
  ops.object,
757
939
  [
758
940
  "a",
759
- [ops.object, ["b", [ops.getter, [[ops.builtin, "fn"], undefined]]]],
941
+ [
942
+ ops.object,
943
+ [
944
+ "b",
945
+ [
946
+ ops.getter,
947
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
948
+ ],
949
+ ],
950
+ ],
760
951
  ],
761
952
  ]);
762
953
  assertParse("objectLiteral", "{ x = fn.js('a') }", [
@@ -766,16 +957,28 @@ Body`,
766
957
  [
767
958
  ops.getter,
768
959
  [
769
- [ops.scope, "fn.js"],
960
+ [markers.traverse, [markers.reference, "fn.js"]],
770
961
  [ops.literal, "a"],
771
962
  ],
772
963
  ],
773
964
  ],
774
965
  ]);
775
- assertParse("objectLiteral", "{ a: 1, ...b }", [
776
- ops.merge,
777
- [ops.object, ["a", [ops.literal, 1]]],
778
- [ops.scope, "b"],
966
+ assertParse("objectLiteral", "{ a: 1, ...more, c: a }", [
967
+ [
968
+ ops.object,
969
+ ["a", [ops.literal, 1]],
970
+ ["c", [markers.traverse, [markers.reference, "a"]]],
971
+ [
972
+ "_result",
973
+ [
974
+ ops.merge,
975
+ [ops.object, ["a", [ops.getter, [[ops.context, 1], "a"]]]],
976
+ [markers.traverse, [markers.reference, "more"]],
977
+ [ops.object, ["c", [ops.getter, [[ops.context, 1], "c"]]]],
978
+ ],
979
+ ],
980
+ ],
981
+ "_result",
779
982
  ]);
780
983
  assertParse("objectLiteral", "{ a: 1, ...{ b: 2 } }", [
781
984
  ops.object,
@@ -786,22 +989,66 @@ Body`,
786
989
  ops.object,
787
990
  ["(a)", [ops.literal, 1]],
788
991
  ]);
992
+ assertParse(
993
+ "objectLiteral",
994
+ "{ <path/to/file.txt> }",
995
+ [
996
+ ops.object,
997
+ [
998
+ "file.txt",
999
+ [
1000
+ markers.traverse,
1001
+ [markers.external, "path/"],
1002
+ [ops.literal, "to/"],
1003
+ [ops.literal, "file.txt"],
1004
+ ],
1005
+ ],
1006
+ ],
1007
+ "jse"
1008
+ );
789
1009
  });
790
1010
 
791
1011
  test("objectEntry", () => {
792
- assertParse("objectEntry", "foo", ["foo", [ops.inherited, "foo"]]);
793
- assertParse("objectEntry", "x: y", ["x", [ops.scope, "y"]]);
794
- assertParse("objectEntry", "a: a", ["a", [ops.inherited, "a"]]);
1012
+ assertParse("objectEntry", "foo", [
1013
+ "foo",
1014
+ [markers.traverse, [markers.reference, "foo"]],
1015
+ ]);
1016
+ assertParse("objectEntry", "index.html: x", [
1017
+ "index.html",
1018
+ [markers.traverse, [markers.reference, "x"]],
1019
+ ]);
1020
+ assertParse("objectEntry", "a: a", [
1021
+ "a",
1022
+ [markers.traverse, [markers.reference, "a"]],
1023
+ ]);
1024
+ assertParse(
1025
+ "objectEntry",
1026
+ "<path/to/file.txt>",
1027
+ [
1028
+ "file.txt",
1029
+ [
1030
+ markers.traverse,
1031
+ [markers.external, "path/"],
1032
+ [ops.literal, "to/"],
1033
+ [ops.literal, "file.txt"],
1034
+ ],
1035
+ ],
1036
+ "jse"
1037
+ );
795
1038
  assertParse("objectEntry", "a: (a) => a", [
796
1039
  "a",
797
- [ops.lambda, [[ops.literal, "a"]], [ops.scope, "a"]],
1040
+ [
1041
+ ops.lambda,
1042
+ [[ops.literal, "a"]],
1043
+ [markers.traverse, [markers.reference, "a"]],
1044
+ ],
798
1045
  ]);
799
1046
  assertParse("objectEntry", "posts/: map(posts, post.ori)", [
800
1047
  "posts/",
801
1048
  [
802
- [ops.builtin, "map"],
803
- [ops.inherited, "posts"],
804
- [ops.scope, "post.ori"],
1049
+ [markers.traverse, [markers.reference, "map"]],
1050
+ [markers.traverse, [markers.reference, "posts"]],
1051
+ [markers.traverse, [markers.reference, "post.ori"]],
805
1052
  ],
806
1053
  ]);
807
1054
  });
@@ -809,15 +1056,15 @@ Body`,
809
1056
  test("objectGetter", () => {
810
1057
  assertParse("objectGetter", "data = obj.json", [
811
1058
  "data",
812
- [ops.getter, [ops.scope, "obj.json"]],
1059
+ [ops.getter, [markers.traverse, [markers.reference, "obj.json"]]],
813
1060
  ]);
814
- assertParse("objectGetter", "foo = page.ori 'bar'", [
815
- "foo",
1061
+ assertParse("objectGetter", "index.html = index.ori(teamData.yaml)", [
1062
+ "index.html",
816
1063
  [
817
1064
  ops.getter,
818
1065
  [
819
- [ops.scope, "page.ori"],
820
- [ops.literal, "bar"],
1066
+ [markers.traverse, [markers.reference, "index.ori"]],
1067
+ [markers.traverse, [markers.reference, "teamData.yaml"]],
821
1068
  ],
822
1069
  ],
823
1070
  ]);
@@ -825,92 +1072,171 @@ Body`,
825
1072
 
826
1073
  test("objectProperty", () => {
827
1074
  assertParse("objectProperty", "a: 1", ["a", [ops.literal, 1]]);
828
- assertParse("objectProperty", "name: 'Alice'", [
829
- "name",
1075
+ assertParse("objectProperty", "data.json: 'Alice'", [
1076
+ "data.json",
830
1077
  [ops.literal, "Alice"],
831
1078
  ]);
832
1079
  assertParse("objectProperty", "x: fn('a')", [
833
1080
  "x",
834
1081
  [
835
- [ops.builtin, "fn"],
1082
+ [markers.traverse, [markers.reference, "fn"]],
836
1083
  [ops.literal, "a"],
837
1084
  ],
838
1085
  ]);
839
1086
  });
840
1087
 
841
1088
  test("objectPublicKey", () => {
842
- assertParse("objectPublicKey", "a", "a", false);
843
- assertParse("objectPublicKey", "markdown/", "markdown/", false);
844
- assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
1089
+ assertParse("objectPublicKey", "a", "a", "shell", false);
1090
+ assertParse("objectPublicKey", "index.html", "index.html", "shell", false);
1091
+ assertParse("objectPublicKey", "markdown/", "markdown/", "shell", false);
1092
+ assertParse("objectPublicKey", `"foo bar"`, "foo bar", "shell", false);
1093
+ });
1094
+
1095
+ test.skip("optionalChaining", () => {
1096
+ assertParse("optionalChaining", "?.key", [
1097
+ ops.optionalTraverse,
1098
+ [ops.literal, "key"],
1099
+ ]);
845
1100
  });
846
1101
 
847
1102
  test("parenthesesArguments", () => {
848
1103
  assertParse("parenthesesArguments", "()", [undefined]);
849
1104
  assertParse("parenthesesArguments", "(a, b, c)", [
850
- [ops.scope, "a"],
851
- [ops.scope, "b"],
852
- [ops.scope, "c"],
1105
+ [markers.traverse, [markers.reference, "a"]],
1106
+ [markers.traverse, [markers.reference, "b"]],
1107
+ [markers.traverse, [markers.reference, "c"]],
853
1108
  ]);
854
1109
  });
855
1110
 
856
- test("path", () => {
857
- assertParse("path", "/tree/", [[ops.literal, "tree/"]]);
858
- assertParse("path", "/month/12", [
859
- [ops.literal, "month/"],
860
- [ops.literal, "12"],
861
- ]);
862
- assertParse("path", "/tree/foo/bar", [
863
- [ops.literal, "tree/"],
864
- [ops.literal, "foo/"],
865
- [ops.literal, "bar"],
866
- ]);
867
- assertParse("path", "/a///b", [
868
- [ops.literal, "a/"],
869
- [ops.literal, "b"],
870
- ]);
1111
+ test("pathKeys", () => {
1112
+ assertParse(
1113
+ "pathKeys",
1114
+ "tree/",
1115
+ [[ops.literal, "tree/"]],
1116
+ undefined,
1117
+ false
1118
+ );
1119
+ assertParse(
1120
+ "pathKeys",
1121
+ "month/12",
1122
+ [
1123
+ [ops.literal, "month/"],
1124
+ [ops.literal, "12"],
1125
+ ],
1126
+ undefined,
1127
+ false
1128
+ );
1129
+ assertParse(
1130
+ "pathKeys",
1131
+ "tree/foo/bar",
1132
+ [
1133
+ [ops.literal, "tree/"],
1134
+ [ops.literal, "foo/"],
1135
+ [ops.literal, "bar"],
1136
+ ],
1137
+ undefined,
1138
+ false
1139
+ );
1140
+ assertParse(
1141
+ "pathKeys",
1142
+ "a///b",
1143
+ [
1144
+ [ops.literal, "a/"],
1145
+ [ops.literal, "/"],
1146
+ [ops.literal, "/"],
1147
+ [ops.literal, "b"],
1148
+ ],
1149
+ undefined,
1150
+ false
1151
+ );
871
1152
  });
872
1153
 
873
1154
  test("pathArguments", () => {
874
- assertParse("pathArguments", "/", [ops.traverse]);
1155
+ assertParse("pathArguments", "/", [markers.traverse]);
875
1156
  assertParse("pathArguments", "/tree", [
876
- ops.traverse,
1157
+ markers.traverse,
877
1158
  [ops.literal, "tree"],
878
1159
  ]);
879
1160
  assertParse("pathArguments", "/tree/", [
880
- ops.traverse,
1161
+ markers.traverse,
881
1162
  [ops.literal, "tree/"],
882
1163
  ]);
883
1164
  });
884
1165
 
1166
+ test("pathLiteral", () => {
1167
+ assertParse("pathLiteral", "tree", [
1168
+ markers.traverse,
1169
+ [markers.reference, "tree"],
1170
+ ]);
1171
+ assertParse("pathLiteral", "tree/", [
1172
+ ops.unpack,
1173
+ [markers.traverse, [markers.reference, "tree/"]],
1174
+ ]);
1175
+ assertParse("pathLiteral", "month/12", [
1176
+ markers.traverse,
1177
+ [markers.reference, "month/"],
1178
+ [ops.literal, "12"],
1179
+ ]);
1180
+ assertParse("pathLiteral", "a/b/c/", [
1181
+ ops.unpack,
1182
+ [
1183
+ markers.traverse,
1184
+ [markers.reference, "a/"],
1185
+ [ops.literal, "b/"],
1186
+ [ops.literal, "c/"],
1187
+ ],
1188
+ ]);
1189
+ assertParse("pathLiteral", "~/.cshrc", [
1190
+ markers.traverse,
1191
+ [markers.reference, "~/"],
1192
+ [ops.literal, ".cshrc"],
1193
+ ]);
1194
+ });
1195
+
885
1196
  test("pipelineExpression", () => {
886
- assertParse("pipelineExpression", "foo", [ops.scope, "foo"]);
1197
+ assertParse("pipelineExpression", "foo", [
1198
+ markers.traverse,
1199
+ [markers.reference, "foo"],
1200
+ ]);
887
1201
  assertParse("pipelineExpression", "a -> b", [
888
- [ops.builtin, "b"],
889
- [ops.scope, "a"],
1202
+ [markers.traverse, [markers.reference, "b"]],
1203
+ [markers.traverse, [markers.reference, "a"]],
890
1204
  ]);
891
1205
  assertParse("pipelineExpression", "input → one.js → two.js", [
892
- [ops.scope, "two.js"],
1206
+ [markers.traverse, [markers.reference, "two.js"]],
893
1207
  [
894
- [ops.scope, "one.js"],
895
- [ops.scope, "input"],
1208
+ [markers.traverse, [markers.reference, "one.js"]],
1209
+ [markers.traverse, [markers.reference, "input"]],
896
1210
  ],
897
1211
  ]);
898
1212
  assertParse("pipelineExpression", "fn a -> b", [
899
- [ops.builtin, "b"],
1213
+ [markers.traverse, [markers.reference, "b"]],
900
1214
  [
901
- [ops.builtin, "fn"],
902
- [undetermined, "a"],
1215
+ [markers.traverse, [markers.reference, "fn"]],
1216
+ [markers.traverse, [markers.reference, "a"]],
903
1217
  ],
904
1218
  ]);
905
1219
  });
906
1220
 
907
1221
  test("primary", () => {
908
- assertParse("primary", "foo.js", [ops.scope, "foo.js"]);
1222
+ assertParse("primary", "123", [ops.literal, 123]);
1223
+ assertParse("primary", "123.html", [
1224
+ markers.traverse,
1225
+ [markers.reference, "123.html"],
1226
+ ]);
1227
+ assertParse("primary", "foo.js", [
1228
+ markers.traverse,
1229
+ [markers.reference, "foo.js"],
1230
+ ]);
909
1231
  assertParse("primary", "[1, 2]", [
910
1232
  ops.array,
911
1233
  [ops.literal, 1],
912
1234
  [ops.literal, 2],
913
1235
  ]);
1236
+ assertParse("primary", "<index.html>", [
1237
+ markers.traverse,
1238
+ [markers.external, "index.html"],
1239
+ ]);
914
1240
  });
915
1241
 
916
1242
  test("program", () => {
@@ -920,48 +1246,13 @@ Body`,
920
1246
  'Hello'
921
1247
  `,
922
1248
  [ops.literal, "Hello"],
1249
+ "jse",
923
1250
  false
924
1251
  );
925
1252
  });
926
1253
 
927
- test("protocolExpression", () => {
928
- assertParse("protocolExpression", "foo://bar", [
929
- [ops.builtin, "foo:"],
930
- [ops.literal, "bar"],
931
- ]);
932
- assertParse("protocolExpression", "http://example.com", [
933
- [ops.builtin, "http:"],
934
- [ops.literal, "example.com"],
935
- ]);
936
- assertParse("protocolExpression", "https://example.com/about/", [
937
- [ops.builtin, "https:"],
938
- [ops.literal, "example.com/"],
939
- [ops.literal, "about/"],
940
- ]);
941
- assertParse("protocolExpression", "https://example.com/about/index.html", [
942
- [ops.builtin, "https:"],
943
- [ops.literal, "example.com/"],
944
- [ops.literal, "about/"],
945
- [ops.literal, "index.html"],
946
- ]);
947
- assertParse("protocolExpression", "http://localhost:5000/foo", [
948
- [ops.builtin, "http:"],
949
- [ops.literal, "localhost:5000/"],
950
- [ops.literal, "foo"],
951
- ]);
952
- assertParse("protocolExpression", "files:///foo/bar.txt", [
953
- [ops.builtin, "files:"],
954
- [ops.literal, "/"],
955
- [ops.literal, "foo/"],
956
- [ops.literal, "bar.txt"],
957
- ]);
958
- });
959
-
960
- test("qualifiedReference", () => {
961
- assertParse("qualifiedReference", "js:Date", [
962
- [ops.builtin, "js:"],
963
- [ops.literal, "Date"],
964
- ]);
1254
+ test("regexLiteral", () => {
1255
+ assertParse("regexLiteral", "/abc+/g", [ops.literal, /abc+/g]);
965
1256
  });
966
1257
 
967
1258
  test("relationalExpression", () => {
@@ -987,17 +1278,6 @@ Body`,
987
1278
  ]);
988
1279
  });
989
1280
 
990
- test("rootDirectory", () => {
991
- assertParse("rootDirectory", "/", [ops.rootDirectory]);
992
- });
993
-
994
- test("scopeReference", () => {
995
- assertParse("scopeReference", "keys", [undetermined, "keys"]);
996
- assertParse("scopeReference", "greet.js", [ops.scope, "greet.js"]);
997
- // scopeReference checks whether a slash follows; hard to test in isolation
998
- // assertParse("scopeReference", "markdown/", [ops.scope, "markdown"]);
999
- });
1000
-
1001
1281
  test("shiftExpression", () => {
1002
1282
  assertParse("shiftExpression", "1 << 2", [
1003
1283
  ops.shiftLeft,
@@ -1020,30 +1300,40 @@ Body`,
1020
1300
  assertParse("shorthandFunction", "=message", [
1021
1301
  ops.lambda,
1022
1302
  [[ops.literal, "_"]],
1023
- [undetermined, "message"],
1303
+ [markers.traverse, [markers.reference, "message"]],
1024
1304
  ]);
1025
1305
  assertParse("shorthandFunction", "=`Hello, ${name}.`", [
1026
1306
  ops.lambda,
1027
1307
  [[ops.literal, "_"]],
1028
- [ops.template, [ops.literal, ["Hello, ", "."]], [ops.scope, "name"]],
1308
+ [
1309
+ ops.templateTree,
1310
+ [ops.literal, ["Hello, ", "."]],
1311
+ [markers.traverse, [markers.reference, "name"]],
1312
+ ],
1029
1313
  ]);
1030
1314
  assertParse("shorthandFunction", "=indent`hello`", [
1031
1315
  ops.lambda,
1032
1316
  [[ops.literal, "_"]],
1033
1317
  [
1034
- [ops.builtin, "indent"],
1318
+ [markers.traverse, [markers.reference, "indent"]],
1035
1319
  [ops.literal, ["hello"]],
1036
1320
  ],
1037
1321
  ]);
1038
1322
  });
1039
1323
 
1040
1324
  test("singleLineComment", () => {
1041
- assertParse("singleLineComment", "// Hello, world!", null, false);
1325
+ assertParse("singleLineComment", "// Hello, world!", null, "jse", false);
1042
1326
  });
1043
1327
 
1044
1328
  test("spreadElement", () => {
1045
- assertParse("spreadElement", "...a", [ops.spread, [ops.scope, "a"]]);
1046
- assertParse("spreadElement", "…a", [ops.spread, [ops.scope, "a"]]);
1329
+ assertParse("spreadElement", "...a", [
1330
+ ops.spread,
1331
+ [markers.traverse, [markers.reference, "a"]],
1332
+ ]);
1333
+ assertParse("spreadElement", "…a", [
1334
+ ops.spread,
1335
+ [markers.traverse, [markers.reference, "a"]],
1336
+ ]);
1047
1337
  });
1048
1338
 
1049
1339
  test("stringLiteral", () => {
@@ -1062,21 +1352,13 @@ Body`,
1062
1352
 
1063
1353
  test("templateBody", () => {
1064
1354
  assertParse("templateBody", "hello${foo}world", [
1065
- ops.lambda,
1066
- [[ops.literal, "_"]],
1067
- [
1068
- ops.templateIndent,
1069
- [ops.literal, ["hello", "world"]],
1070
- [ops.scope, "foo"],
1071
- ],
1355
+ ops.templateIndent,
1356
+ [ops.literal, ["hello", "world"]],
1357
+ [markers.traverse, [markers.reference, "foo"]],
1072
1358
  ]);
1073
1359
  assertParse("templateBody", "Documents can contain ` backticks", [
1074
- ops.lambda,
1075
- [[ops.literal, "_"]],
1076
- [
1077
- ops.templateIndent,
1078
- [ops.literal, ["Documents can contain ` backticks"]],
1079
- ],
1360
+ ops.templateIndent,
1361
+ [ops.literal, ["Documents can contain ` backticks"]],
1080
1362
  ]);
1081
1363
  });
1082
1364
 
@@ -1096,13 +1378,10 @@ title: Title goes here
1096
1378
  ---
1097
1379
  Body text`,
1098
1380
  [
1099
- ops.document,
1100
- [ops.literal, { title: "Title goes here" }],
1101
- [
1102
- ops.lambda,
1103
- [[ops.literal, "_"]],
1104
- [ops.templateIndent, [ops.literal, ["Body text"]]],
1105
- ],
1381
+ ops.object,
1382
+ ["title", [ops.literal, "Title goes here"]],
1383
+ ["(@text)", [ops.templateIndent, [ops.literal, ["Body text"]]]],
1384
+ ["_body", [ops.templateIndent, [ops.literal, ["Body text"]]]],
1106
1385
  ]
1107
1386
  );
1108
1387
  });
@@ -1124,54 +1403,132 @@ Body text`,
1124
1403
  [
1125
1404
  "@text",
1126
1405
  [
1127
- [
1128
- ops.lambda,
1129
- [[ops.literal, "_"]],
1130
- [
1131
- ops.templateIndent,
1132
- [ops.literal, ["<h1>", "</h1>\n"]],
1133
- [ops.scope, "title"],
1134
- ],
1135
- ],
1136
- undefined,
1406
+ ops.templateIndent,
1407
+ [ops.literal, ["<h1>", "</h1>\n"]],
1408
+ [markers.traverse, [markers.reference, "title"]],
1137
1409
  ],
1138
1410
  ],
1139
- ]
1411
+ ],
1412
+ "shell"
1413
+ );
1414
+ });
1415
+
1416
+ test.skip("templateDocument with Origami front matter", () => {
1417
+ assertParse(
1418
+ "templateDocument",
1419
+ `---
1420
+ {
1421
+ title: "Title"
1422
+ _body: _template()
1423
+ }
1424
+ ---
1425
+ <h1>\${ title }</h1>
1426
+ `,
1427
+ [
1428
+ ops.object,
1429
+ ["title", [ops.literal, "Title"]],
1430
+ [
1431
+ "_body",
1432
+ [
1433
+ ops.templateIndent,
1434
+ [ops.literal, ["<h1>", "</h1>\n"]],
1435
+ [markers.traverse, [markers.reference, "title"]],
1436
+ ],
1437
+ ],
1438
+ ],
1439
+ "jse"
1140
1440
  );
1141
1441
  });
1142
1442
 
1143
1443
  test("templateLiteral", () => {
1144
1444
  assertParse("templateLiteral", "`Hello, world.`", [
1145
- ops.template,
1445
+ ops.templateTree,
1146
1446
  [ops.literal, ["Hello, world."]],
1147
1447
  ]);
1448
+ assertParse(
1449
+ "templateLiteral",
1450
+ "`Hello, world.`",
1451
+ [ops.templateTree, [ops.literal, ["Hello, world."]]],
1452
+ "jse"
1453
+ );
1148
1454
  assertParse("templateLiteral", "`foo ${x} bar`", [
1149
- ops.template,
1455
+ ops.templateTree,
1150
1456
  [ops.literal, ["foo ", " bar"]],
1151
- [ops.scope, "x"],
1457
+ [markers.traverse, [markers.reference, "x"]],
1152
1458
  ]);
1153
1459
  assertParse("templateLiteral", "`${`nested`}`", [
1154
- ops.template,
1460
+ ops.templateTree,
1155
1461
  [ops.literal, ["", ""]],
1156
- [ops.template, [ops.literal, ["nested"]]],
1462
+ [ops.templateTree, [ops.literal, ["nested"]]],
1157
1463
  ]);
1158
- assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
1159
- ops.template,
1464
+ assertParse("templateLiteral", "`${ map(people, =`${name}`) }`", [
1465
+ ops.templateTree,
1160
1466
  [ops.literal, ["", ""]],
1161
1467
  [
1162
- [ops.builtin, "map:"],
1163
- [ops.scope, "people"],
1468
+ [markers.traverse, [markers.reference, "map"]],
1469
+ [markers.traverse, [markers.reference, "people"]],
1164
1470
  [
1165
1471
  ops.lambda,
1166
1472
  [[ops.literal, "_"]],
1167
- [ops.template, [ops.literal, ["", ""]], [ops.scope, "name"]],
1473
+ [
1474
+ ops.templateTree,
1475
+ [ops.literal, ["", ""]],
1476
+ [markers.traverse, [markers.reference, "name"]],
1477
+ ],
1168
1478
  ],
1169
1479
  ],
1170
1480
  ]);
1171
1481
  });
1172
1482
 
1173
1483
  test("templateSubtitution", () => {
1174
- assertParse("templateSubstitution", "${foo}", [ops.scope, "foo"], false);
1484
+ assertParse(
1485
+ "templateSubstitution",
1486
+ "${foo}",
1487
+ [markers.traverse, [markers.reference, "foo"]],
1488
+ "shell",
1489
+ false
1490
+ );
1491
+ });
1492
+
1493
+ describe("uri", () => {
1494
+ test("with double slashes after colon", () => {
1495
+ assertParse("uri", "foo://bar/baz", [
1496
+ [markers.global, "foo:"],
1497
+ [ops.literal, "bar/"],
1498
+ [ops.literal, "baz"],
1499
+ ]);
1500
+ assertParse("uri", "http://example.com", [
1501
+ [markers.global, "http:"],
1502
+ [ops.literal, "example.com"],
1503
+ ]);
1504
+ assertParse("uri", "https://example.com/about/", [
1505
+ [markers.global, "https:"],
1506
+ [ops.literal, "example.com/"],
1507
+ [ops.literal, "about/"],
1508
+ ]);
1509
+ assertParse("uri", "https://example.com/about/index.html", [
1510
+ [markers.global, "https:"],
1511
+ [ops.literal, "example.com/"],
1512
+ [ops.literal, "about/"],
1513
+ [ops.literal, "index.html"],
1514
+ ]);
1515
+ assertParse("uri", "http://localhost:5000/foo", [
1516
+ [markers.global, "http:"],
1517
+ [ops.literal, "localhost:5000/"],
1518
+ [ops.literal, "foo"],
1519
+ ]);
1520
+ });
1521
+
1522
+ test("without double slashes after colon", () => {
1523
+ assertParse("uri", "files:build", [
1524
+ [markers.global, "files:"],
1525
+ [ops.literal, "build"],
1526
+ ]);
1527
+ });
1528
+ });
1529
+
1530
+ test("uriScheme", () => {
1531
+ assertParse("uriScheme", "https:", [markers.global, "https:"]);
1175
1532
  });
1176
1533
 
1177
1534
  test("whitespace block", () => {
@@ -1182,6 +1539,7 @@ Body text`,
1182
1539
  // Second comment
1183
1540
  `,
1184
1541
  null,
1542
+ "jse",
1185
1543
  false
1186
1544
  );
1187
1545
  });
@@ -1189,24 +1547,38 @@ Body text`,
1189
1547
  test("unaryExpression", () => {
1190
1548
  assertParse("unaryExpression", "!true", [
1191
1549
  ops.logicalNot,
1192
- [undetermined, "true"],
1550
+ [markers.traverse, [markers.reference, "true"]],
1193
1551
  ]);
1194
1552
  assertParse("unaryExpression", "+1", [ops.unaryPlus, [ops.literal, 1]]);
1195
1553
  assertParse("unaryExpression", "-2", [ops.unaryMinus, [ops.literal, 2]]);
1196
1554
  assertParse("unaryExpression", "~3", [ops.bitwiseNot, [ops.literal, 3]]);
1197
1555
  });
1556
+
1557
+ test("unaryOperator", () => {
1558
+ assertParse("unaryOperator", "!", "!");
1559
+ assertParse("unaryOperator", "+", "+");
1560
+ assertParse("unaryOperator", "-", "-");
1561
+ assertParse("unaryOperator", "~", "~");
1562
+ });
1198
1563
  });
1199
1564
 
1200
- function assertParse(startRule, source, expected, checkLocation = true) {
1565
+ function assertParse(
1566
+ startRule,
1567
+ source,
1568
+ expected,
1569
+ mode = "shell",
1570
+ checkLocation = true
1571
+ ) {
1201
1572
  const code = parse(source, {
1202
1573
  grammarSource: { text: source },
1574
+ mode,
1203
1575
  startRule,
1204
1576
  });
1205
1577
 
1206
1578
  // Verify that the parser returned a `location` property and that it spans the
1207
1579
  // entire source. We skip this check in cases where the source starts or ends
1208
1580
  // with a comment; the parser will strip those.
1209
- if (checkLocation) {
1581
+ if (typeof code === "object" && checkLocation) {
1210
1582
  assertCodeLocations(code);
1211
1583
  const resultSource = code.location.source.text.slice(
1212
1584
  code.location.start.offset,
@@ -1227,10 +1599,12 @@ function assertCodeLocations(code) {
1227
1599
  }
1228
1600
  }
1229
1601
 
1230
- function assertThrows(startRule, source, message, position) {
1602
+ function assertThrows(startRule, source, message, position, mode = "shell") {
1603
+ let code;
1231
1604
  try {
1232
- parse(source, {
1605
+ code = parse(source, {
1233
1606
  grammarSource: { text: source },
1607
+ mode,
1234
1608
  startRule,
1235
1609
  });
1236
1610
  } catch (/** @type {any} */ error) {