@weborigami/language 0.3.3-jse.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.
@@ -21,38 +21,47 @@ describe("Origami parser", () => {
21
21
 
22
22
  describe("angleBracketLiteral", () => {
23
23
  test("with path", () => {
24
+ assertParse("angleBracketLiteral", "<index.html>", [
25
+ markers.traverse,
26
+ [markers.external, "index.html"],
27
+ ]);
24
28
  assertParse(
25
29
  "angleBracketLiteral",
26
- "<index.html>",
27
- [[ops.scope], [ops.literal, "index.html"]],
28
- "jse"
29
- );
30
- assertParse(
31
- "angleBracketLiteral",
32
- "<foo/bar/baz>",
30
+ "<Path with spaces (and parens).html>",
33
31
  [
34
- [ops.scope],
35
- [ops.literal, "foo/"],
36
- [ops.literal, "bar/"],
37
- [ops.literal, "baz"],
38
- ],
39
- "jse"
32
+ markers.traverse,
33
+ [markers.external, "Path with spaces (and parens).html"],
34
+ ]
40
35
  );
36
+ assertParse("angleBracketLiteral", "<foo/bar/baz>", [
37
+ markers.traverse,
38
+ [markers.external, "foo/"],
39
+ [ops.literal, "bar/"],
40
+ [ops.literal, "baz"],
41
+ ]);
41
42
  });
42
43
 
43
44
  test("root directory", () => {
44
- assertParse("angleBracketLiteral", "</>", [ops.rootDirectory]);
45
+ assertParse("angleBracketLiteral", "</>", [
46
+ markers.traverse,
47
+ [markers.external, "/"],
48
+ ]);
45
49
  assertParse("angleBracketLiteral", "</etc/passwd>", [
46
- [ops.rootDirectory],
50
+ markers.traverse,
51
+ [markers.external, "/"],
47
52
  [ops.literal, "etc/"],
48
53
  [ops.literal, "passwd"],
49
54
  ]);
50
55
  });
51
56
 
52
57
  test("home directory", () => {
53
- assertParse("angleBracketLiteral", "<~>", [ops.homeDirectory]);
58
+ assertParse("angleBracketLiteral", "<~>", [
59
+ markers.traverse,
60
+ [markers.external, "~"],
61
+ ]);
54
62
  assertParse("angleBracketLiteral", "<~/.bash_profile>", [
55
- [ops.homeDirectory],
63
+ markers.traverse,
64
+ [markers.external, "~/"],
56
65
  [ops.literal, ".bash_profile"],
57
66
  ]);
58
67
  });
@@ -117,12 +126,12 @@ describe("Origami parser", () => {
117
126
  assertParse("arrowFunction", "() => foo", [
118
127
  ops.lambda,
119
128
  [],
120
- [markers.reference, [ops.literal, "foo"]],
129
+ [markers.traverse, [markers.reference, "foo"]],
121
130
  ]);
122
131
  assertParse("arrowFunction", "x => y", [
123
132
  ops.lambda,
124
133
  [[ops.literal, "x"]],
125
- [markers.reference, [ops.literal, "y"]],
134
+ [markers.traverse, [markers.reference, "y"]],
126
135
  ]);
127
136
  assertParse("arrowFunction", "(a, b, c) ⇒ fn(a, b, c)", [
128
137
  ops.lambda,
@@ -132,10 +141,10 @@ describe("Origami parser", () => {
132
141
  [ops.literal, "c"],
133
142
  ],
134
143
  [
135
- [markers.global, "fn"],
136
- [markers.reference, [ops.literal, "a"]],
137
- [markers.reference, [ops.literal, "b"]],
138
- [markers.reference, [ops.literal, "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"]],
139
148
  ],
140
149
  ]);
141
150
  assertParse("arrowFunction", "a => b => fn(a, b)", [
@@ -145,9 +154,9 @@ describe("Origami parser", () => {
145
154
  ops.lambda,
146
155
  [[ops.literal, "b"]],
147
156
  [
148
- [markers.global, "fn"],
149
- [markers.reference, [ops.literal, "a"]],
150
- [markers.reference, [ops.literal, "b"]],
157
+ [markers.traverse, [markers.reference, "fn"]],
158
+ [markers.traverse, [markers.reference, "a"]],
159
+ [markers.traverse, [markers.reference, "b"]],
151
160
  ],
152
161
  ],
153
162
  ]);
@@ -180,75 +189,74 @@ describe("Origami parser", () => {
180
189
  describe("callExpression", () => {
181
190
  test("with parentheses arguments", () => {
182
191
  assertParse("callExpression", "fn()", [
183
- [markers.global, "fn"],
192
+ [markers.traverse, [markers.reference, "fn"]],
184
193
  undefined,
185
194
  ]);
186
195
  assertParse("callExpression", "foo.js(arg)", [
187
- [markers.reference, [ops.literal, "foo.js"]],
188
- [markers.reference, [ops.literal, "arg"]],
196
+ [markers.traverse, [markers.reference, "foo.js"]],
197
+ [markers.traverse, [markers.reference, "arg"]],
189
198
  ]);
190
199
  assertParse("callExpression", "fn(a, b)", [
191
- [markers.global, "fn"],
192
- [markers.reference, [ops.literal, "a"]],
193
- [markers.reference, [ops.literal, "b"]],
200
+ [markers.traverse, [markers.reference, "fn"]],
201
+ [markers.traverse, [markers.reference, "a"]],
202
+ [markers.traverse, [markers.reference, "b"]],
194
203
  ]);
195
204
  assertParse("callExpression", "foo.js( a , b )", [
196
- [markers.reference, [ops.literal, "foo.js"]],
197
- [markers.reference, [ops.literal, "a"]],
198
- [markers.reference, [ops.literal, "b"]],
205
+ [markers.traverse, [markers.reference, "foo.js"]],
206
+ [markers.traverse, [markers.reference, "a"]],
207
+ [markers.traverse, [markers.reference, "b"]],
199
208
  ]);
200
209
  assertParse("callExpression", "fn()(arg)", [
201
- [[markers.global, "fn"], undefined],
202
- [markers.reference, [ops.literal, "arg"]],
210
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
211
+ [markers.traverse, [markers.reference, "arg"]],
203
212
  ]);
204
213
  });
214
+
205
215
  test("call chains", () => {
206
216
  assertParse("callExpression", "(foo.js())('arg')", [
207
- [[markers.reference, [ops.literal, "foo.js"]], undefined],
217
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
208
218
  [ops.literal, "arg"],
209
219
  ]);
210
220
  assertParse("callExpression", "fn('a')('b')", [
211
221
  [
212
- [markers.global, "fn"],
222
+ [markers.traverse, [markers.reference, "fn"]],
213
223
  [ops.literal, "a"],
214
224
  ],
215
225
  [ops.literal, "b"],
216
226
  ]);
217
227
  assertParse("callExpression", "(foo.js())(a, b)", [
218
- [[markers.reference, [ops.literal, "foo.js"]], undefined],
219
- [markers.reference, [ops.literal, "a"]],
220
- [markers.reference, [ops.literal, "b"]],
228
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
229
+ [markers.traverse, [markers.reference, "a"]],
230
+ [markers.traverse, [markers.reference, "b"]],
221
231
  ]);
222
232
  });
233
+
223
234
  test("with paths", () => {
224
- assertParse("callExpression", "/", [ops.rootDirectory]);
225
235
  assertParse("callExpression", "tree/", [
226
236
  ops.unpack,
227
- [markers.reference, [ops.literal, "tree/"]],
237
+ [markers.traverse, [markers.reference, "tree/"]],
228
238
  ]);
229
239
  assertParse("callExpression", "tree/foo/bar", [
230
- markers.reference,
231
- [ops.literal, "tree/"],
240
+ markers.traverse,
241
+ [markers.reference, "tree/"],
232
242
  [ops.literal, "foo/"],
233
243
  [ops.literal, "bar"],
234
244
  ]);
235
245
  assertParse("callExpression", "tree/foo/bar/", [
236
- markers.reference,
237
- [ops.literal, "tree/"],
238
- [ops.literal, "foo/"],
239
- [ops.literal, "bar/"],
246
+ ops.unpack,
247
+ [
248
+ markers.traverse,
249
+ [markers.reference, "tree/"],
250
+ [ops.literal, "foo/"],
251
+ [ops.literal, "bar/"],
252
+ ],
240
253
  ]);
241
254
  // Consecutive slahes in a path are removed
242
- assertParse("callExpression", "path//key", [
243
- markers.reference,
244
- [ops.literal, "path/"],
255
+ assertParse("callExpression", "tree//key", [
256
+ markers.traverse,
257
+ [markers.reference, "tree/"],
245
258
  [ops.literal, "key"],
246
259
  ]);
247
- assertParse("callExpression", "/foo/bar", [
248
- [ops.rootDirectory],
249
- [ops.literal, "foo/"],
250
- [ops.literal, "bar"],
251
- ]);
252
260
  assertParse("callExpression", "{ a: 1, b: 2}/b", [
253
261
  [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
254
262
  [ops.literal, "b"],
@@ -259,22 +267,19 @@ describe("Origami parser", () => {
259
267
  [ops.literal, "bar"],
260
268
  ]);
261
269
  });
270
+
262
271
  test("path and parentheses chains", () => {
263
272
  assertParse("callExpression", "foo.js()/key", [
264
- [[markers.reference, [ops.literal, "foo.js"]], undefined],
273
+ [[markers.traverse, [markers.reference, "foo.js"]], undefined],
265
274
  [ops.literal, "key"],
266
275
  ]);
267
276
  assertParse("callExpression", "tree/key()", [
268
- [markers.reference, [ops.literal, "tree/"], [ops.literal, "key"]],
277
+ [markers.traverse, [markers.reference, "tree/"], [ops.literal, "key"]],
269
278
  undefined,
270
279
  ]);
271
- assertParse("callExpression", "(tree)/", [
272
- ops.unpack,
273
- [markers.reference, [ops.literal, "tree/"]],
274
- ]);
275
280
  assertParse("callExpression", "fn()/key()", [
276
281
  [
277
- [[markers.global, "fn"], undefined],
282
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
278
283
  [ops.literal, "key"],
279
284
  ],
280
285
  undefined,
@@ -286,68 +291,44 @@ describe("Origami parser", () => {
286
291
  [ops.literal, "dropbox/"],
287
292
  [ops.literal, "auth"],
288
293
  ],
289
- [markers.reference, [ops.literal, "creds"]],
294
+ [markers.traverse, [markers.reference, "creds"]],
290
295
  ]);
291
296
  });
297
+
292
298
  test("tagged templates", () => {
293
299
  assertParse("callExpression", "indent`hello`", [
294
- [markers.global, "indent"],
300
+ [markers.traverse, [markers.reference, "indent"]],
295
301
  [ops.literal, ["hello"]],
296
302
  ]);
297
303
  assertParse("callExpression", "fn.js`Hello, world.`", [
298
- [markers.reference, [ops.literal, "fn.js"]],
304
+ [markers.traverse, [markers.reference, "fn.js"]],
299
305
  [ops.literal, ["Hello, world."]],
300
306
  ]);
301
307
  });
308
+
302
309
  test("protocols", () => {
303
310
  assertParse("callExpression", "files:src/assets", [
304
311
  [markers.global, "files:"],
305
312
  [ops.literal, "src/"],
306
313
  [ops.literal, "assets"],
307
314
  ]);
308
- assertParse("callExpression", "new:(js:Date, '2025-01-01')", [
309
- [markers.global, "new:"],
315
+ assertParse("callExpression", "<node:process>.env", [
310
316
  [
311
- [markers.global, "js:"],
312
- [ops.literal, "Date"],
317
+ [markers.global, "node:"],
318
+ [ops.literal, "process"],
313
319
  ],
314
- [ops.literal, "2025-01-01"],
320
+ [ops.literal, "env"],
315
321
  ]);
316
322
  });
317
323
  });
318
324
 
319
- test("callExpression using property acccess", () => {
320
- assertParse("callExpression", "(foo).bar", [
321
- markers.reference,
322
- [ops.literal, "foo/"],
323
- [ops.literal, "bar"],
324
- ]);
325
- assertParse("callExpression", "(foo).bar.baz", [
326
- markers.reference,
327
- [ops.literal, "foo/"],
328
- [ops.literal, "bar/"],
329
- [ops.literal, "baz"],
330
- ]);
331
- assertParse("callExpression", "foo[bar]", [
332
- markers.reference,
333
- [ops.literal, "foo/"],
334
- [markers.reference, [ops.literal, "bar"]],
335
- ]);
336
- assertParse(
337
- "callExpression",
338
- "Tree.map",
339
- [markers.reference, [ops.literal, "Tree/"], [ops.literal, "map"]],
340
- "jse"
341
- );
342
- });
343
-
344
325
  test("commaExpression", () => {
345
326
  assertParse("commaExpression", "1", [ops.literal, 1]);
346
327
  assertParse("commaExpression", "a, b, c", [
347
328
  ops.comma,
348
- [markers.reference, [ops.literal, "a"]],
349
- [markers.reference, [ops.literal, "b"]],
350
- [markers.reference, [ops.literal, "c"]],
329
+ [markers.traverse, [markers.reference, "a"]],
330
+ [markers.traverse, [markers.reference, "b"]],
331
+ [markers.traverse, [markers.reference, "c"]],
351
332
  ]);
352
333
  });
353
334
 
@@ -355,19 +336,19 @@ describe("Origami parser", () => {
355
336
  assertParse("conditionalExpression", "1", [ops.literal, 1]);
356
337
  assertParse("conditionalExpression", "true ? 1 : 0", [
357
338
  ops.conditional,
358
- [markers.reference, [ops.literal, "true"]],
339
+ [markers.traverse, [markers.reference, "true"]],
359
340
  [ops.literal, 1],
360
341
  [ops.literal, 0],
361
342
  ]);
362
343
  assertParse("conditionalExpression", "false ? () => 1 : 0", [
363
344
  ops.conditional,
364
- [markers.reference, [ops.literal, "false"]],
345
+ [markers.traverse, [markers.reference, "false"]],
365
346
  [ops.lambda, [], [ops.lambda, [], [ops.literal, 1]]],
366
347
  [ops.literal, 0],
367
348
  ]);
368
349
  assertParse("conditionalExpression", "false ? =1 : 0", [
369
350
  ops.conditional,
370
- [markers.reference, [ops.literal, "false"]],
351
+ [markers.traverse, [markers.reference, "false"]],
371
352
  [ops.lambda, [], [ops.lambda, [[ops.literal, "_"]], [ops.literal, 1]]],
372
353
  [ops.literal, 0],
373
354
  ]);
@@ -383,10 +364,10 @@ describe("Origami parser", () => {
383
364
  ops.strictEqual,
384
365
  [
385
366
  ops.strictEqual,
386
- [markers.reference, [ops.literal, "a"]],
387
- [markers.reference, [ops.literal, "b"]],
367
+ [markers.traverse, [markers.reference, "a"]],
368
+ [markers.traverse, [markers.reference, "b"]],
388
369
  ],
389
- [markers.reference, [ops.literal, "c"]],
370
+ [markers.traverse, [markers.reference, "c"]],
390
371
  ]);
391
372
  assertParse("equalityExpression", "1 !== 1", [
392
373
  ops.notStrictEqual,
@@ -440,182 +421,244 @@ Body`,
440
421
  ]);
441
422
  });
442
423
 
443
- test("expression", () => {
444
- assertParse(
445
- "expression",
446
- `{
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
+ `{
447
627
  index.html = index.ori(teamData.yaml)
448
628
  thumbnails = map(images, { value: thumbnail.js })
449
629
  }`,
450
- [
451
- ops.object,
452
630
  [
453
- "index.html",
631
+ ops.object,
454
632
  [
455
- ops.getter,
633
+ "index.html",
456
634
  [
457
- [markers.reference, [ops.literal, "index.ori"]],
458
- [markers.reference, [ops.literal, "teamData.yaml"]],
635
+ ops.getter,
636
+ [
637
+ [markers.traverse, [markers.reference, "index.ori"]],
638
+ [markers.traverse, [markers.reference, "teamData.yaml"]],
639
+ ],
459
640
  ],
460
641
  ],
461
- ],
462
- [
463
- "thumbnails",
464
642
  [
465
- ops.getter,
643
+ "thumbnails",
466
644
  [
467
- [markers.global, "map"],
468
- [markers.reference, [ops.literal, "images"]],
645
+ ops.getter,
469
646
  [
470
- ops.object,
471
- ["value", [markers.reference, [ops.literal, "thumbnail.js"]]],
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
+ ],
472
656
  ],
473
657
  ],
474
658
  ],
475
- ],
476
- ]
477
- );
478
-
479
- // Builtin on its own is the function itself, not a function call
480
- assertParse("expression", "mdHtml:", [markers.global, "mdHtml:"]);
481
-
482
- // Consecutive slashes at start of something = comment
483
- assertParse(
484
- "expression",
485
- "x //comment",
486
- [markers.reference, [ops.literal, "x"]],
487
- "jse",
488
- false
489
- );
490
-
491
- assertParse("expression", "page.ori(mdHtml:(about.md))", [
492
- [markers.reference, [ops.literal, "page.ori"]],
493
- [
494
- [markers.global, "mdHtml:"],
495
- [markers.reference, [ops.literal, "about.md"]],
496
- ],
497
- ]);
498
-
499
- // Slash on its own is the root folder
500
- assertParse("expression", "keys /", [
501
- [markers.global, "keys"],
502
- [ops.rootDirectory],
503
- ]);
504
-
505
- assertParse("expression", "'Hello' -> test.orit", [
506
- [markers.reference, [ops.literal, "test.orit"]],
507
- [ops.literal, "Hello"],
508
- ]);
509
- assertParse("expression", "obj.json", [
510
- markers.reference,
511
- [ops.literal, "obj.json"],
512
- ]);
513
- assertParse("expression", "(fn a, b, c)", [
514
- [markers.global, "fn"],
515
- [markers.reference, [ops.literal, "a"]],
516
- [markers.reference, [ops.literal, "b"]],
517
- [markers.reference, [ops.literal, "c"]],
518
- ]);
519
- assertParse("expression", "foo.bar('hello', 'world')", [
520
- [markers.reference, [ops.literal, "foo.bar"]],
521
- [ops.literal, "hello"],
522
- [ops.literal, "world"],
523
- ]);
524
- // assertParse("expression", "(key)('a')", [
525
- // [markers.reference, [ops.literal, "key"]],
526
- // [ops.literal, "a"],
527
- // ]);
528
- assertParse("expression", "1", [ops.literal, 1]);
529
- assertParse("expression", "{ a: 1, b: 2 }", [
530
- ops.object,
531
- ["a", [ops.literal, 1]],
532
- ["b", [ops.literal, 2]],
533
- ]);
534
- assertParse("expression", "serve { index.html: 'hello' }", [
535
- [markers.global, "serve"],
536
- [ops.object, ["index.html", [ops.literal, "hello"]]],
537
- ]);
538
- assertParse("expression", "fn =`x`", [
539
- [markers.global, "fn"],
540
- [
541
- ops.lambda,
542
- [[ops.literal, "_"]],
543
- [ops.templateTree, [ops.literal, ["x"]]],
544
- ],
545
- ]);
546
- assertParse("expression", "copy app.js(formulas), files:snapshot", [
547
- [markers.global, "copy"],
548
- [
549
- [markers.reference, [ops.literal, "app.js"]],
550
- [markers.reference, [ops.literal, "formulas"]],
551
- ],
552
- [
553
- [markers.global, "files:"],
554
- [ops.literal, "snapshot"],
555
- ],
556
- ]);
557
- assertParse("expression", "map =`<li>${_}</li>`", [
558
- [markers.global, "map"],
559
- [
560
- ops.lambda,
561
- [[ops.literal, "_"]],
562
- [
563
- ops.templateTree,
564
- [ops.literal, ["<li>", "</li>"]],
565
- [markers.reference, [ops.literal, "_"]],
566
- ],
567
- ],
568
- ]);
569
- assertParse("expression", `https://example.com/about/`, [
570
- [markers.global, "https:"],
571
- [ops.literal, "example.com/"],
572
- [ops.literal, "about/"],
573
- ]);
574
- assertParse("expression", "tag`Hello, ${name}!`", [
575
- [markers.global, "tag"],
576
- [ops.literal, ["Hello, ", "!"]],
577
- [ops.concat, [markers.reference, [ops.literal, "name"]]],
578
- ]);
579
- assertParse("expression", "(post, slug) => fn.js(post, slug)", [
580
- ops.lambda,
581
- [
582
- [ops.literal, "post"],
583
- [ops.literal, "slug"],
584
- ],
585
- [
586
- [markers.reference, [ops.literal, "fn.js"]],
587
- [markers.reference, [ops.literal, "post"]],
588
- [markers.reference, [ops.literal, "slug"]],
589
- ],
590
- ]);
591
- assertParse("expression", "keys ~", [
592
- [markers.global, "keys"],
593
- [ops.homeDirectory],
594
- ]);
595
- assertParse("expression", "keys /Users/alice", [
596
- [markers.global, "keys"],
597
- [[ops.rootDirectory], [ops.literal, "Users/"], [ops.literal, "alice"]],
598
- ]);
599
-
600
- // Verify parser treatment of identifiers containing operators
601
- assertParse("expression", "a + b", [
602
- ops.addition,
603
- [markers.reference, [ops.literal, "a"]],
604
- [markers.reference, [ops.literal, "b"]],
605
- ]);
606
- assertParse("expression", "a+b", [markers.reference, [ops.literal, "a+b"]]);
607
- assertParse("expression", "a - b", [
608
- ops.subtraction,
609
- [markers.reference, [ops.literal, "a"]],
610
- [markers.reference, [ops.literal, "b"]],
611
- ]);
612
- assertParse("expression", "a-b", [markers.reference, [ops.literal, "a-b"]]);
613
- assertParse("expression", "a&b", [markers.reference, [ops.literal, "a&b"]]);
614
- assertParse("expression", "a & b", [
615
- ops.bitwiseAnd,
616
- [markers.reference, [ops.literal, "a"]],
617
- [markers.reference, [ops.literal, "b"]],
618
- ]);
659
+ ]
660
+ );
661
+ });
619
662
  });
620
663
 
621
664
  test("frontMatterExpression", () => {
@@ -628,7 +671,7 @@ Body`,
628
671
  [
629
672
  ops.lambda,
630
673
  [[ops.literal, "name"]],
631
- [[markers.reference, [ops.literal, "_template"]], undefined],
674
+ [[markers.traverse, [markers.reference, "_template"]], undefined],
632
675
  ],
633
676
  "jse",
634
677
  false
@@ -637,90 +680,98 @@ Body`,
637
680
 
638
681
  test("group", () => {
639
682
  assertParse("group", "(hello)", [
640
- markers.reference,
641
- [ops.literal, "hello"],
683
+ markers.traverse,
684
+ [markers.reference, "hello"],
642
685
  ]);
643
686
  assertParse("group", "(((nested)))", [
644
- markers.reference,
645
- [ops.literal, "nested"],
687
+ markers.traverse,
688
+ [markers.reference, "nested"],
689
+ ]);
690
+ assertParse("group", "(fn())", [
691
+ [markers.traverse, [markers.reference, "fn"]],
692
+ undefined,
646
693
  ]);
647
- assertParse("group", "(fn())", [[markers.global, "fn"], undefined]);
648
694
  assertParse("group", "(a -> b)", [
649
- [markers.global, "b"],
650
- [markers.reference, [ops.literal, "a"]],
695
+ [markers.traverse, [markers.reference, "b"]],
696
+ [markers.traverse, [markers.reference, "a"]],
651
697
  ]);
652
698
  });
653
699
 
654
- test("homeDirectory", () => {
655
- assertParse("homeDirectory", "~", [ops.homeDirectory]);
656
- });
657
-
658
700
  test("host", () => {
659
701
  assertParse("host", "abc", [ops.literal, "abc"]);
660
702
  assertParse("host", "abc:123", [ops.literal, "abc:123"]);
661
- assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
662
703
  });
663
704
 
664
705
  test("identifier", () => {
665
- assertParse("identifier", "abc", "abc", "shell", false);
666
- assertParse("identifier", "index.html", "index.html", "shell", false);
667
- assertParse("identifier", "foo\\ bar", "foo bar", "shell", false);
668
- assertParse("identifier", "x-y-z", "x-y-z", "shell", 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
+ );
669
723
  });
670
724
 
671
725
  test("implicitParenthesesCallExpression", () => {
672
726
  assertParse("implicitParenthesesCallExpression", "fn arg", [
673
- [markers.global, "fn"],
674
- [markers.reference, [ops.literal, "arg"]],
727
+ [markers.traverse, [markers.reference, "fn"]],
728
+ [markers.traverse, [markers.reference, "arg"]],
675
729
  ]);
676
730
  assertParse("implicitParenthesesCallExpression", "page.ori 'a', 'b'", [
677
- [markers.reference, [ops.literal, "page.ori"]],
731
+ [markers.traverse, [markers.reference, "page.ori"]],
678
732
  [ops.literal, "a"],
679
733
  [ops.literal, "b"],
680
734
  ]);
681
735
  assertParse("implicitParenthesesCallExpression", "fn a(b), c", [
682
- [markers.global, "fn"],
736
+ [markers.traverse, [markers.reference, "fn"]],
683
737
  [
684
- [markers.global, "a"],
685
- [markers.reference, [ops.literal, "b"]],
738
+ [markers.traverse, [markers.reference, "a"]],
739
+ [markers.traverse, [markers.reference, "b"]],
686
740
  ],
687
- [markers.reference, [ops.literal, "c"]],
741
+ [markers.traverse, [markers.reference, "c"]],
688
742
  ]);
689
743
  assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
690
- [[markers.global, "fn"], undefined],
744
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
691
745
  [ops.literal, "arg"],
692
746
  ]);
693
- assertParse("implicitParenthesesCallExpression", "tree/key arg", [
694
- [markers.reference, [ops.literal, "tree/"], [ops.literal, "key"]],
695
- [markers.reference, [ops.literal, "arg"]],
696
- ]);
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
+ );
697
756
  assertParse("implicitParenthesesCallExpression", "foo.js bar.ori 'arg'", [
698
- [markers.reference, [ops.literal, "foo.js"]],
757
+ [markers.traverse, [markers.reference, "foo.js"]],
699
758
  [
700
- [markers.reference, [ops.literal, "bar.ori"]],
759
+ [markers.traverse, [markers.reference, "bar.ori"]],
701
760
  [ops.literal, "arg"],
702
761
  ],
703
762
  ]);
704
763
  });
705
764
 
706
- test("jsIdentifier", () => {
707
- assertParse("jsIdentifier", "foo", [ops.literal, "foo"], "jse");
708
- assertParse("jsIdentifier", "$Δelta", [ops.literal, "$Δelta"], "jse");
709
- assertThrows(
710
- "jsIdentifier",
711
- "1stCharacterIsNumber",
712
- "Expected JavaScript identifier start"
713
- );
714
- assertThrows(
715
- "jsIdentifier",
716
- "has space",
717
- "Expected JavaScript identifier continuation"
718
- );
719
- assertThrows(
720
- "jsIdentifier",
721
- "foo.bar",
722
- "Expected JavaScript identifier continuation"
723
- );
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");
724
775
  });
725
776
 
726
777
  test("list", () => {
@@ -755,8 +806,8 @@ Body`,
755
806
  test("logicalAndExpression", () => {
756
807
  assertParse("logicalAndExpression", "true && false", [
757
808
  ops.logicalAnd,
758
- [markers.reference, [ops.literal, "true"]],
759
- [ops.lambda, [], [markers.reference, [ops.literal, "false"]]],
809
+ [markers.traverse, [markers.reference, "true"]],
810
+ [ops.lambda, [], [markers.traverse, [markers.reference, "false"]]],
760
811
  ]);
761
812
  });
762
813
 
@@ -768,9 +819,9 @@ Body`,
768
819
  ]);
769
820
  assertParse("logicalOrExpression", "false || false || true", [
770
821
  ops.logicalOr,
771
- [markers.reference, [ops.literal, "false"]],
772
- [ops.lambda, [], [markers.reference, [ops.literal, "false"]]],
773
- [ops.lambda, [], [markers.reference, [ops.literal, "true"]]],
822
+ [markers.traverse, [markers.reference, "false"]],
823
+ [ops.lambda, [], [markers.traverse, [markers.reference, "false"]]],
824
+ [ops.lambda, [], [markers.traverse, [markers.reference, "true"]]],
774
825
  ]);
775
826
  assertParse("logicalOrExpression", "1 || 2 && 0", [
776
827
  ops.logicalOr,
@@ -807,32 +858,28 @@ Body`,
807
858
  ]);
808
859
  });
809
860
 
810
- test("namespace", () => {
811
- assertParse("namespace", "js:", [markers.global, "js:"]);
812
- });
813
-
814
861
  test("newExpression", () => {
815
862
  assertParse("newExpression", "new Foo()", [
816
863
  ops.construct,
817
- [markers.reference, [ops.literal, "Foo"]],
864
+ [markers.traverse, [markers.reference, "Foo"]],
818
865
  ]);
819
866
  assertParse("newExpression", "new:Foo()", [
820
867
  ops.construct,
821
- [markers.reference, [ops.literal, "Foo"]],
868
+ [markers.traverse, [markers.reference, "Foo"]],
822
869
  ]);
823
870
  });
824
871
 
825
872
  test("nullishCoalescingExpression", () => {
826
873
  assertParse("nullishCoalescingExpression", "a ?? b", [
827
874
  ops.nullishCoalescing,
828
- [markers.reference, [ops.literal, "a"]],
829
- [ops.lambda, [], [markers.reference, [ops.literal, "b"]]],
875
+ [markers.traverse, [markers.reference, "a"]],
876
+ [ops.lambda, [], [markers.traverse, [markers.reference, "b"]]],
830
877
  ]);
831
878
  assertParse("nullishCoalescingExpression", "a ?? b ?? c", [
832
879
  ops.nullishCoalescing,
833
- [markers.reference, [ops.literal, "a"]],
834
- [ops.lambda, [], [markers.reference, [ops.literal, "b"]]],
835
- [ops.lambda, [], [markers.reference, [ops.literal, "c"]]],
880
+ [markers.traverse, [markers.reference, "a"]],
881
+ [ops.lambda, [], [markers.traverse, [markers.reference, "b"]]],
882
+ [ops.lambda, [], [markers.traverse, [markers.reference, "c"]]],
836
883
  ]);
837
884
  });
838
885
 
@@ -847,7 +894,7 @@ Body`,
847
894
  assertParse("objectLiteral", "{ a: 1, b }", [
848
895
  ops.object,
849
896
  ["a", [ops.literal, 1]],
850
- ["b", [markers.reference, "b"]],
897
+ ["b", [markers.traverse, [markers.reference, "b"]]],
851
898
  ]);
852
899
  assertParse("objectLiteral", "{ sub: { a: 1 } }", [
853
900
  ops.object,
@@ -864,7 +911,7 @@ Body`,
864
911
  ]);
865
912
  assertParse("objectLiteral", "{ a = b, b = 2 }", [
866
913
  ops.object,
867
- ["a", [ops.getter, [markers.reference, [ops.literal, "b"]]]],
914
+ ["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
868
915
  ["b", [ops.literal, 2]],
869
916
  ]);
870
917
  assertParse(
@@ -875,7 +922,7 @@ Body`,
875
922
  }`,
876
923
  [
877
924
  ops.object,
878
- ["a", [ops.getter, [markers.reference, [ops.literal, "b"]]]],
925
+ ["a", [ops.getter, [markers.traverse, [markers.reference, "b"]]]],
879
926
  ["b", [ops.literal, 2]],
880
927
  ]
881
928
  );
@@ -891,7 +938,16 @@ Body`,
891
938
  ops.object,
892
939
  [
893
940
  "a",
894
- [ops.object, ["b", [ops.getter, [[markers.global, "fn"], undefined]]]],
941
+ [
942
+ ops.object,
943
+ [
944
+ "b",
945
+ [
946
+ ops.getter,
947
+ [[markers.traverse, [markers.reference, "fn"]], undefined],
948
+ ],
949
+ ],
950
+ ],
895
951
  ],
896
952
  ]);
897
953
  assertParse("objectLiteral", "{ x = fn.js('a') }", [
@@ -901,7 +957,7 @@ Body`,
901
957
  [
902
958
  ops.getter,
903
959
  [
904
- [markers.reference, [ops.literal, "fn.js"]],
960
+ [markers.traverse, [markers.reference, "fn.js"]],
905
961
  [ops.literal, "a"],
906
962
  ],
907
963
  ],
@@ -911,13 +967,13 @@ Body`,
911
967
  [
912
968
  ops.object,
913
969
  ["a", [ops.literal, 1]],
914
- ["c", [markers.reference, [ops.literal, "a"]]],
970
+ ["c", [markers.traverse, [markers.reference, "a"]]],
915
971
  [
916
972
  "_result",
917
973
  [
918
974
  ops.merge,
919
975
  [ops.object, ["a", [ops.getter, [[ops.context, 1], "a"]]]],
920
- [markers.reference, [ops.literal, "more"]],
976
+ [markers.traverse, [markers.reference, "more"]],
921
977
  [ops.object, ["c", [ops.getter, [[ops.context, 1], "c"]]]],
922
978
  ],
923
979
  ],
@@ -941,8 +997,8 @@ Body`,
941
997
  [
942
998
  "file.txt",
943
999
  [
944
- [ops.scope],
945
- [ops.literal, "path/"],
1000
+ markers.traverse,
1001
+ [markers.external, "path/"],
946
1002
  [ops.literal, "to/"],
947
1003
  [ops.literal, "file.txt"],
948
1004
  ],
@@ -953,14 +1009,17 @@ Body`,
953
1009
  });
954
1010
 
955
1011
  test("objectEntry", () => {
956
- assertParse("objectEntry", "foo", ["foo", [markers.reference, "foo"]]);
957
- assertParse("objectEntry", "x: y", [
958
- "x",
959
- [markers.reference, [ops.literal, "y"]],
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"]],
960
1019
  ]);
961
1020
  assertParse("objectEntry", "a: a", [
962
1021
  "a",
963
- [markers.reference, [ops.literal, "a"]],
1022
+ [markers.traverse, [markers.reference, "a"]],
964
1023
  ]);
965
1024
  assertParse(
966
1025
  "objectEntry",
@@ -968,8 +1027,8 @@ Body`,
968
1027
  [
969
1028
  "file.txt",
970
1029
  [
971
- [ops.scope],
972
- [ops.literal, "path/"],
1030
+ markers.traverse,
1031
+ [markers.external, "path/"],
973
1032
  [ops.literal, "to/"],
974
1033
  [ops.literal, "file.txt"],
975
1034
  ],
@@ -981,15 +1040,15 @@ Body`,
981
1040
  [
982
1041
  ops.lambda,
983
1042
  [[ops.literal, "a"]],
984
- [markers.reference, [ops.literal, "a"]],
1043
+ [markers.traverse, [markers.reference, "a"]],
985
1044
  ],
986
1045
  ]);
987
1046
  assertParse("objectEntry", "posts/: map(posts, post.ori)", [
988
1047
  "posts/",
989
1048
  [
990
- [markers.global, "map"],
991
- [markers.reference, [ops.literal, "posts"]],
992
- [markers.reference, [ops.literal, "post.ori"]],
1049
+ [markers.traverse, [markers.reference, "map"]],
1050
+ [markers.traverse, [markers.reference, "posts"]],
1051
+ [markers.traverse, [markers.reference, "post.ori"]],
993
1052
  ],
994
1053
  ]);
995
1054
  });
@@ -997,15 +1056,15 @@ Body`,
997
1056
  test("objectGetter", () => {
998
1057
  assertParse("objectGetter", "data = obj.json", [
999
1058
  "data",
1000
- [ops.getter, [markers.reference, [ops.literal, "obj.json"]]],
1059
+ [ops.getter, [markers.traverse, [markers.reference, "obj.json"]]],
1001
1060
  ]);
1002
- assertParse("objectGetter", "foo = page.ori 'bar'", [
1003
- "foo",
1061
+ assertParse("objectGetter", "index.html = index.ori(teamData.yaml)", [
1062
+ "index.html",
1004
1063
  [
1005
1064
  ops.getter,
1006
1065
  [
1007
- [markers.reference, [ops.literal, "page.ori"]],
1008
- [ops.literal, "bar"],
1066
+ [markers.traverse, [markers.reference, "index.ori"]],
1067
+ [markers.traverse, [markers.reference, "teamData.yaml"]],
1009
1068
  ],
1010
1069
  ],
1011
1070
  ]);
@@ -1013,26 +1072,27 @@ Body`,
1013
1072
 
1014
1073
  test("objectProperty", () => {
1015
1074
  assertParse("objectProperty", "a: 1", ["a", [ops.literal, 1]]);
1016
- assertParse("objectProperty", "name: 'Alice'", [
1017
- "name",
1075
+ assertParse("objectProperty", "data.json: 'Alice'", [
1076
+ "data.json",
1018
1077
  [ops.literal, "Alice"],
1019
1078
  ]);
1020
1079
  assertParse("objectProperty", "x: fn('a')", [
1021
1080
  "x",
1022
1081
  [
1023
- [markers.global, "fn"],
1082
+ [markers.traverse, [markers.reference, "fn"]],
1024
1083
  [ops.literal, "a"],
1025
1084
  ],
1026
1085
  ]);
1027
1086
  });
1028
1087
 
1029
1088
  test("objectPublicKey", () => {
1030
- assertParse("objectPublicKey", "a", "a", "jse", false);
1031
- assertParse("objectPublicKey", "markdown/", "markdown/", "jse", false);
1032
- assertParse("objectPublicKey", "foo\\ bar", "foo bar", "jse", 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);
1033
1093
  });
1034
1094
 
1035
- test("optionalChaining", () => {
1095
+ test.skip("optionalChaining", () => {
1036
1096
  assertParse("optionalChaining", "?.key", [
1037
1097
  ops.optionalTraverse,
1038
1098
  [ops.literal, "key"],
@@ -1042,27 +1102,53 @@ Body`,
1042
1102
  test("parenthesesArguments", () => {
1043
1103
  assertParse("parenthesesArguments", "()", [undefined]);
1044
1104
  assertParse("parenthesesArguments", "(a, b, c)", [
1045
- [markers.reference, [ops.literal, "a"]],
1046
- [markers.reference, [ops.literal, "b"]],
1047
- [markers.reference, [ops.literal, "c"]],
1105
+ [markers.traverse, [markers.reference, "a"]],
1106
+ [markers.traverse, [markers.reference, "b"]],
1107
+ [markers.traverse, [markers.reference, "c"]],
1048
1108
  ]);
1049
1109
  });
1050
1110
 
1051
- test("path", () => {
1052
- assertParse("path", "/tree/", [[ops.literal, "tree/"]]);
1053
- assertParse("path", "/month/12", [
1054
- [ops.literal, "month/"],
1055
- [ops.literal, "12"],
1056
- ]);
1057
- assertParse("path", "/tree/foo/bar", [
1058
- [ops.literal, "tree/"],
1059
- [ops.literal, "foo/"],
1060
- [ops.literal, "bar"],
1061
- ]);
1062
- assertParse("path", "/a///b", [
1063
- [ops.literal, "a/"],
1064
- [ops.literal, "b"],
1065
- ]);
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
+ );
1066
1152
  });
1067
1153
 
1068
1154
  test("pathArguments", () => {
@@ -1077,50 +1163,80 @@ Body`,
1077
1163
  ]);
1078
1164
  });
1079
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
+
1080
1196
  test("pipelineExpression", () => {
1081
1197
  assertParse("pipelineExpression", "foo", [
1082
- markers.reference,
1083
- [ops.literal, "foo"],
1198
+ markers.traverse,
1199
+ [markers.reference, "foo"],
1084
1200
  ]);
1085
1201
  assertParse("pipelineExpression", "a -> b", [
1086
- [markers.global, "b"],
1087
- [markers.reference, [ops.literal, "a"]],
1202
+ [markers.traverse, [markers.reference, "b"]],
1203
+ [markers.traverse, [markers.reference, "a"]],
1088
1204
  ]);
1089
1205
  assertParse("pipelineExpression", "input → one.js → two.js", [
1090
- [markers.reference, [ops.literal, "two.js"]],
1206
+ [markers.traverse, [markers.reference, "two.js"]],
1091
1207
  [
1092
- [markers.reference, [ops.literal, "one.js"]],
1093
- [markers.reference, [ops.literal, "input"]],
1208
+ [markers.traverse, [markers.reference, "one.js"]],
1209
+ [markers.traverse, [markers.reference, "input"]],
1094
1210
  ],
1095
1211
  ]);
1096
1212
  assertParse("pipelineExpression", "fn a -> b", [
1097
- [markers.global, "b"],
1213
+ [markers.traverse, [markers.reference, "b"]],
1098
1214
  [
1099
- [markers.global, "fn"],
1100
- [markers.reference, [ops.literal, "a"]],
1215
+ [markers.traverse, [markers.reference, "fn"]],
1216
+ [markers.traverse, [markers.reference, "a"]],
1101
1217
  ],
1102
1218
  ]);
1103
1219
  });
1104
1220
 
1105
1221
  test("primary", () => {
1222
+ assertParse("primary", "123", [ops.literal, 123]);
1223
+ assertParse("primary", "123.html", [
1224
+ markers.traverse,
1225
+ [markers.reference, "123.html"],
1226
+ ]);
1106
1227
  assertParse("primary", "foo.js", [
1107
- markers.reference,
1108
- [ops.literal, "foo.js"],
1228
+ markers.traverse,
1229
+ [markers.reference, "foo.js"],
1109
1230
  ]);
1110
1231
  assertParse("primary", "[1, 2]", [
1111
1232
  ops.array,
1112
1233
  [ops.literal, 1],
1113
1234
  [ops.literal, 2],
1114
1235
  ]);
1115
- // Only in JSE
1116
- assertParse(
1117
- "primary",
1118
- "<index.html>",
1119
- [[ops.scope], [ops.literal, "index.html"]],
1120
- "jse",
1121
- false
1122
- );
1123
- assertThrows("primary", "<index.html>", `but "<" found`, 0, "shell");
1236
+ assertParse("primary", "<index.html>", [
1237
+ markers.traverse,
1238
+ [markers.external, "index.html"],
1239
+ ]);
1124
1240
  });
1125
1241
 
1126
1242
  test("program", () => {
@@ -1135,47 +1251,6 @@ Body`,
1135
1251
  );
1136
1252
  });
1137
1253
 
1138
- test("protocolExpression", () => {
1139
- assertParse("protocolExpression", "foo://bar/baz", [
1140
- [markers.global, "foo:"],
1141
- [ops.literal, "bar/"],
1142
- [ops.literal, "baz"],
1143
- ]);
1144
- assertParse("protocolExpression", "http://example.com", [
1145
- [markers.global, "http:"],
1146
- [ops.literal, "example.com"],
1147
- ]);
1148
- assertParse("protocolExpression", "https://example.com/about/", [
1149
- [markers.global, "https:"],
1150
- [ops.literal, "example.com/"],
1151
- [ops.literal, "about/"],
1152
- ]);
1153
- assertParse("protocolExpression", "https://example.com/about/index.html", [
1154
- [markers.global, "https:"],
1155
- [ops.literal, "example.com/"],
1156
- [ops.literal, "about/"],
1157
- [ops.literal, "index.html"],
1158
- ]);
1159
- assertParse("protocolExpression", "http://localhost:5000/foo", [
1160
- [markers.global, "http:"],
1161
- [ops.literal, "localhost:5000/"],
1162
- [ops.literal, "foo"],
1163
- ]);
1164
- assertParse("protocolExpression", "files:///foo/bar.txt", [
1165
- [markers.global, "files:"],
1166
- [ops.literal, "/"],
1167
- [ops.literal, "foo/"],
1168
- [ops.literal, "bar.txt"],
1169
- ]);
1170
- });
1171
-
1172
- test("qualifiedReference", () => {
1173
- assertParse("qualifiedReference", "js:Date", [
1174
- [markers.global, "js:"],
1175
- [ops.literal, "Date"],
1176
- ]);
1177
- });
1178
-
1179
1254
  test("regexLiteral", () => {
1180
1255
  assertParse("regexLiteral", "/abc+/g", [ops.literal, /abc+/g]);
1181
1256
  });
@@ -1203,17 +1278,6 @@ Body`,
1203
1278
  ]);
1204
1279
  });
1205
1280
 
1206
- test("scopeReference", () => {
1207
- assertParse("scopeReference", "keys", [
1208
- markers.reference,
1209
- [ops.literal, "keys"],
1210
- ]);
1211
- assertParse("scopeReference", "greet.js", [
1212
- markers.reference,
1213
- [ops.literal, "greet.js"],
1214
- ]);
1215
- });
1216
-
1217
1281
  test("shiftExpression", () => {
1218
1282
  assertParse("shiftExpression", "1 << 2", [
1219
1283
  ops.shiftLeft,
@@ -1236,7 +1300,7 @@ Body`,
1236
1300
  assertParse("shorthandFunction", "=message", [
1237
1301
  ops.lambda,
1238
1302
  [[ops.literal, "_"]],
1239
- [markers.reference, [ops.literal, "message"]],
1303
+ [markers.traverse, [markers.reference, "message"]],
1240
1304
  ]);
1241
1305
  assertParse("shorthandFunction", "=`Hello, ${name}.`", [
1242
1306
  ops.lambda,
@@ -1244,14 +1308,14 @@ Body`,
1244
1308
  [
1245
1309
  ops.templateTree,
1246
1310
  [ops.literal, ["Hello, ", "."]],
1247
- [markers.reference, [ops.literal, "name"]],
1311
+ [markers.traverse, [markers.reference, "name"]],
1248
1312
  ],
1249
1313
  ]);
1250
1314
  assertParse("shorthandFunction", "=indent`hello`", [
1251
1315
  ops.lambda,
1252
1316
  [[ops.literal, "_"]],
1253
1317
  [
1254
- [markers.global, "indent"],
1318
+ [markers.traverse, [markers.reference, "indent"]],
1255
1319
  [ops.literal, ["hello"]],
1256
1320
  ],
1257
1321
  ]);
@@ -1264,11 +1328,11 @@ Body`,
1264
1328
  test("spreadElement", () => {
1265
1329
  assertParse("spreadElement", "...a", [
1266
1330
  ops.spread,
1267
- [markers.reference, [ops.literal, "a"]],
1331
+ [markers.traverse, [markers.reference, "a"]],
1268
1332
  ]);
1269
1333
  assertParse("spreadElement", "…a", [
1270
1334
  ops.spread,
1271
- [markers.reference, [ops.literal, "a"]],
1335
+ [markers.traverse, [markers.reference, "a"]],
1272
1336
  ]);
1273
1337
  });
1274
1338
 
@@ -1290,7 +1354,7 @@ Body`,
1290
1354
  assertParse("templateBody", "hello${foo}world", [
1291
1355
  ops.templateIndent,
1292
1356
  [ops.literal, ["hello", "world"]],
1293
- [markers.reference, [ops.literal, "foo"]],
1357
+ [markers.traverse, [markers.reference, "foo"]],
1294
1358
  ]);
1295
1359
  assertParse("templateBody", "Documents can contain ` backticks", [
1296
1360
  ops.templateIndent,
@@ -1316,7 +1380,8 @@ Body text`,
1316
1380
  [
1317
1381
  ops.object,
1318
1382
  ["title", [ops.literal, "Title goes here"]],
1319
- ["@text", [ops.templateIndent, [ops.literal, ["Body text"]]]],
1383
+ ["(@text)", [ops.templateIndent, [ops.literal, ["Body text"]]]],
1384
+ ["_body", [ops.templateIndent, [ops.literal, ["Body text"]]]],
1320
1385
  ]
1321
1386
  );
1322
1387
  });
@@ -1340,10 +1405,38 @@ Body text`,
1340
1405
  [
1341
1406
  ops.templateIndent,
1342
1407
  [ops.literal, ["<h1>", "</h1>\n"]],
1343
- [markers.reference, [ops.literal, "title"]],
1408
+ [markers.traverse, [markers.reference, "title"]],
1344
1409
  ],
1345
1410
  ],
1346
- ]
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"
1347
1440
  );
1348
1441
  });
1349
1442
 
@@ -1355,32 +1448,32 @@ Body text`,
1355
1448
  assertParse(
1356
1449
  "templateLiteral",
1357
1450
  "`Hello, world.`",
1358
- [ops.templateStandard, [ops.literal, ["Hello, world."]]],
1451
+ [ops.templateTree, [ops.literal, ["Hello, world."]]],
1359
1452
  "jse"
1360
1453
  );
1361
1454
  assertParse("templateLiteral", "`foo ${x} bar`", [
1362
1455
  ops.templateTree,
1363
1456
  [ops.literal, ["foo ", " bar"]],
1364
- [markers.reference, [ops.literal, "x"]],
1457
+ [markers.traverse, [markers.reference, "x"]],
1365
1458
  ]);
1366
1459
  assertParse("templateLiteral", "`${`nested`}`", [
1367
1460
  ops.templateTree,
1368
1461
  [ops.literal, ["", ""]],
1369
1462
  [ops.templateTree, [ops.literal, ["nested"]]],
1370
1463
  ]);
1371
- assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
1464
+ assertParse("templateLiteral", "`${ map(people, =`${name}`) }`", [
1372
1465
  ops.templateTree,
1373
1466
  [ops.literal, ["", ""]],
1374
1467
  [
1375
- [markers.global, "map:"],
1376
- [markers.reference, [ops.literal, "people"]],
1468
+ [markers.traverse, [markers.reference, "map"]],
1469
+ [markers.traverse, [markers.reference, "people"]],
1377
1470
  [
1378
1471
  ops.lambda,
1379
1472
  [[ops.literal, "_"]],
1380
1473
  [
1381
1474
  ops.templateTree,
1382
1475
  [ops.literal, ["", ""]],
1383
- [markers.reference, [ops.literal, "name"]],
1476
+ [markers.traverse, [markers.reference, "name"]],
1384
1477
  ],
1385
1478
  ],
1386
1479
  ],
@@ -1391,12 +1484,53 @@ Body text`,
1391
1484
  assertParse(
1392
1485
  "templateSubstitution",
1393
1486
  "${foo}",
1394
- [markers.reference, [ops.literal, "foo"]],
1487
+ [markers.traverse, [markers.reference, "foo"]],
1395
1488
  "shell",
1396
1489
  false
1397
1490
  );
1398
1491
  });
1399
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:"]);
1532
+ });
1533
+
1400
1534
  test("whitespace block", () => {
1401
1535
  assertParse(
1402
1536
  "__",
@@ -1413,12 +1547,19 @@ Body text`,
1413
1547
  test("unaryExpression", () => {
1414
1548
  assertParse("unaryExpression", "!true", [
1415
1549
  ops.logicalNot,
1416
- [markers.reference, [ops.literal, "true"]],
1550
+ [markers.traverse, [markers.reference, "true"]],
1417
1551
  ]);
1418
1552
  assertParse("unaryExpression", "+1", [ops.unaryPlus, [ops.literal, 1]]);
1419
1553
  assertParse("unaryExpression", "-2", [ops.unaryMinus, [ops.literal, 2]]);
1420
1554
  assertParse("unaryExpression", "~3", [ops.bitwiseNot, [ops.literal, 3]]);
1421
1555
  });
1556
+
1557
+ test("unaryOperator", () => {
1558
+ assertParse("unaryOperator", "!", "!");
1559
+ assertParse("unaryOperator", "+", "+");
1560
+ assertParse("unaryOperator", "-", "-");
1561
+ assertParse("unaryOperator", "~", "~");
1562
+ });
1422
1563
  });
1423
1564
 
1424
1565
  function assertParse(
@@ -1437,7 +1578,7 @@ function assertParse(
1437
1578
  // Verify that the parser returned a `location` property and that it spans the
1438
1579
  // entire source. We skip this check in cases where the source starts or ends
1439
1580
  // with a comment; the parser will strip those.
1440
- if (checkLocation) {
1581
+ if (typeof code === "object" && checkLocation) {
1441
1582
  assertCodeLocations(code);
1442
1583
  const resultSource = code.location.source.text.slice(
1443
1584
  code.location.start.offset,
@@ -1459,8 +1600,9 @@ function assertCodeLocations(code) {
1459
1600
  }
1460
1601
 
1461
1602
  function assertThrows(startRule, source, message, position, mode = "shell") {
1603
+ let code;
1462
1604
  try {
1463
- parse(source, {
1605
+ code = parse(source, {
1464
1606
  grammarSource: { text: source },
1465
1607
  mode,
1466
1608
  startRule,