@weborigami/language 0.0.72 → 0.1.0

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.
@@ -32,74 +32,38 @@ describe("Origami parser", () => {
32
32
  [ops.array, [ops.literal, 1]],
33
33
  [ops.array, [ops.literal, 2], [ops.literal, 3]],
34
34
  ]);
35
+ assertParse(
36
+ "array",
37
+ `[
38
+ 1
39
+ 2
40
+ 3
41
+ ]`,
42
+ [ops.array, [ops.literal, 1], [ops.literal, 2], [ops.literal, 3]]
43
+ );
35
44
  });
36
45
 
37
- test("expr", () => {
38
- assertParse("expr", "obj.json", [ops.scope, "obj.json"]);
39
- assertParse("expr", "(fn a, b, c)", [
40
- [ops.scope, "fn"],
41
- [ops.scope, "a"],
42
- [ops.scope, "b"],
43
- [ops.scope, "c"],
44
- ]);
45
- assertParse("expr", "foo.bar('hello', 'world')", [
46
- [ops.scope, "foo.bar"],
47
- [ops.literal, "hello"],
48
- [ops.literal, "world"],
49
- ]);
50
- assertParse("expr", "(fn)('a')", [
51
- [ops.scope, "fn"],
52
- [ops.literal, "a"],
53
- ]);
54
- assertParse("expr", "1", [ops.literal, 1]);
55
- assertParse("expr", "{ a: 1, b: 2 }", [
56
- ops.object,
57
- ["a", [ops.literal, 1]],
58
- ["b", [ops.literal, 2]],
59
- ]);
60
- assertParse("expr", "serve { index.html: 'hello' }", [
61
- [ops.scope, "serve"],
62
- [ops.object, ["index.html", [ops.literal, "hello"]]],
63
- ]);
64
- assertParse("expr", "fn =`x`", [
65
- [ops.scope, "fn"],
66
- [ops.lambda, ["_"], [ops.template, [ops.literal, ["x"]]]],
67
- ]);
68
- assertParse("expr", "copy app(formulas), files 'snapshot'", [
69
- [ops.scope, "copy"],
70
- [
71
- [ops.scope, "app"],
72
- [ops.scope, "formulas"],
73
- ],
74
- [
75
- [ops.scope, "files"],
76
- [ops.literal, "snapshot"],
77
- ],
78
- ]);
79
- assertParse("expr", "@map =`<li>${_}</li>`", [
80
- [ops.scope, "@map"],
81
- [
82
- ops.lambda,
83
- ["_"],
84
- [
85
- ops.template,
86
- [ops.literal, ["<li>", "</li>"]],
87
- [ops.concat, [ops.scope, "_"]],
88
- ],
89
- ],
46
+ test("callTarget", () => {
47
+ assertParse("callTarget", "foo", [ops.builtin, "foo"]);
48
+ assertParse("callTarget", "foo.js", [ops.scope, "foo.js"]);
49
+ assertParse("callTarget", "[1, 2]", [
50
+ ops.array,
51
+ [ops.literal, 1],
52
+ [ops.literal, 2],
90
53
  ]);
91
- assertParse("expr", `"https://example.com"`, [
92
- ops.literal,
93
- "https://example.com",
54
+ });
55
+
56
+ test("doubleSlashPath", () => {
57
+ assertParse("doubleSlashPath", "//example.com", [
58
+ [ops.literal, "example.com"],
94
59
  ]);
95
- assertParse("expr", "'Hello' -> test.orit", [
96
- [ops.scope, "test.orit"],
97
- [ops.literal, "Hello"],
60
+ assertParse("doubleSlashPath", "//example.com/index.html", [
61
+ [ops.literal, "example.com/"],
62
+ [ops.literal, "index.html"],
98
63
  ]);
99
- assertParse("expr", "tag`Hello, ${name}!`", [
100
- [ops.scope, "tag"],
101
- [ops.literal, ["Hello, ", "!"]],
102
- [ops.concat, [ops.scope, "name"]],
64
+ assertParse("doubleSlashPath", "//localhost:5000/foo", [
65
+ [ops.literal, "localhost:5000/"],
66
+ [ops.literal, "foo"],
103
67
  ]);
104
68
  });
105
69
 
@@ -109,7 +73,7 @@ describe("Origami parser", () => {
109
73
  `
110
74
  {
111
75
  index.html = index.ori(teamData.yaml)
112
- thumbnails = @map(images, { value: thumbnail.js })
76
+ thumbnails = map(images, { value: thumbnail.js })
113
77
  }
114
78
  `,
115
79
  [
@@ -129,7 +93,7 @@ describe("Origami parser", () => {
129
93
  [
130
94
  ops.getter,
131
95
  [
132
- [ops.scope, "@map"],
96
+ [ops.builtin, "map"],
133
97
  [ops.scope, "images"],
134
98
  [ops.object, ["value", [ops.scope, "thumbnail.js"]]],
135
99
  ],
@@ -138,44 +102,64 @@ describe("Origami parser", () => {
138
102
  ]
139
103
  );
140
104
 
105
+ // Builtin on its own is the function itself, not a function call
106
+ assertParse("expression", "mdHtml:", [ops.builtin, "mdHtml:"]);
107
+
141
108
  // Consecutive slahes in a path are removed
142
109
  assertParse("expression", "path//key", [
143
110
  ops.traverse,
144
111
  [ops.scope, "path/"],
145
112
  [ops.literal, "key"],
146
113
  ]);
114
+
147
115
  // Single slash at start of something = absolute file path
148
116
  assertParse("expression", "/path", [
149
117
  [ops.filesRoot],
150
118
  [ops.literal, "path"],
151
119
  ]);
120
+
152
121
  // Consecutive slashes at start of something = comment
153
122
  assertParse("expression", "path //comment", [ops.scope, "path"], false);
123
+ assertParse("expression", "page.ori(mdHtml:(about.md))", [
124
+ [ops.scope, "page.ori"],
125
+ [
126
+ [ops.builtin, "mdHtml:"],
127
+ [ops.scope, "about.md"],
128
+ ],
129
+ ]);
130
+
131
+ assertParse("expression", "'Hello' -> test.orit", [
132
+ [ops.scope, "test.orit"],
133
+ [ops.literal, "Hello"],
134
+ ]);
154
135
  });
155
136
 
156
137
  test("functionComposition", () => {
157
- assertParse("functionComposition", "fn()", [[ops.scope, "fn"], undefined]);
158
- assertParse("functionComposition", "fn(arg)", [
159
- [ops.scope, "fn"],
138
+ assertParse("functionComposition", "fn()", [
139
+ [ops.builtin, "fn"],
140
+ undefined,
141
+ ]);
142
+ assertParse("functionComposition", "foo.js(arg)", [
143
+ [ops.scope, "foo.js"],
160
144
  [ops.scope, "arg"],
161
145
  ]);
162
146
  assertParse("functionComposition", "fn(a, b)", [
163
- [ops.scope, "fn"],
147
+ [ops.builtin, "fn"],
164
148
  [ops.scope, "a"],
165
149
  [ops.scope, "b"],
166
150
  ]);
167
- assertParse("functionComposition", "fn( a , b )", [
168
- [ops.scope, "fn"],
151
+ assertParse("functionComposition", "foo.js( a , b )", [
152
+ [ops.scope, "foo.js"],
169
153
  [ops.scope, "a"],
170
154
  [ops.scope, "b"],
171
155
  ]);
172
156
  assertParse("functionComposition", "fn()(arg)", [
173
- [[ops.scope, "fn"], undefined],
157
+ [[ops.builtin, "fn"], undefined],
174
158
  [ops.scope, "arg"],
175
159
  ]);
176
- assertParse("functionComposition", "fn()/key", [
160
+ assertParse("functionComposition", "foo.js()/key", [
177
161
  ops.traverse,
178
- [[ops.scope, "fn"], undefined],
162
+ [[ops.scope, "foo.js"], undefined],
179
163
  [ops.literal, "key"],
180
164
  ]);
181
165
  assertParse("functionComposition", "tree/key()", [
@@ -187,22 +171,22 @@ describe("Origami parser", () => {
187
171
  [ops.scope, "tree"],
188
172
  ]);
189
173
  assertParse("functionComposition", "fn()/key()", [
190
- [ops.traverse, [[ops.scope, "fn"], undefined], [ops.literal, "key"]],
174
+ [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
191
175
  undefined,
192
176
  ]);
193
- assertParse("functionComposition", "(fn())('arg')", [
194
- [[ops.scope, "fn"], undefined],
177
+ assertParse("functionComposition", "(foo.js())('arg')", [
178
+ [[ops.scope, "foo.js"], undefined],
195
179
  [ops.literal, "arg"],
196
180
  ]);
197
181
  assertParse("functionComposition", "fn('a')('b')", [
198
182
  [
199
- [ops.scope, "fn"],
183
+ [ops.builtin, "fn"],
200
184
  [ops.literal, "a"],
201
185
  ],
202
186
  [ops.literal, "b"],
203
187
  ]);
204
- assertParse("functionComposition", "(fn())(a, b)", [
205
- [[ops.scope, "fn"], undefined],
188
+ assertParse("functionComposition", "(foo.js())(a, b)", [
189
+ [[ops.scope, "foo.js"], undefined],
206
190
  [ops.scope, "a"],
207
191
  [ops.scope, "b"],
208
192
  ]);
@@ -212,53 +196,89 @@ describe("Origami parser", () => {
212
196
  [ops.literal, "b"],
213
197
  ]);
214
198
  assertParse("functionComposition", "fn arg", [
215
- [ops.scope, "fn"],
199
+ [ops.builtin, "fn"],
216
200
  [ops.scope, "arg"],
217
201
  ]);
218
- assertParse("functionComposition", "fn 'a', 'b'", [
219
- [ops.scope, "fn"],
202
+ assertParse("functionComposition", "page.ori 'a', 'b'", [
203
+ [ops.scope, "page.ori"],
220
204
  [ops.literal, "a"],
221
205
  [ops.literal, "b"],
222
206
  ]);
223
207
  assertParse("functionComposition", "fn a(b), c", [
224
- [ops.scope, "fn"],
208
+ [ops.builtin, "fn"],
225
209
  [
226
- [ops.scope, "a"],
210
+ [ops.builtin, "a"],
227
211
  [ops.scope, "b"],
228
212
  ],
229
213
  [ops.scope, "c"],
230
214
  ]);
231
- assertParse("functionComposition", "fn1 fn2 'arg'", [
232
- [ops.scope, "fn1"],
215
+ assertParse("functionComposition", "foo.js bar.ori 'arg'", [
216
+ [ops.scope, "foo.js"],
233
217
  [
234
- [ops.scope, "fn2"],
218
+ [ops.scope, "bar.ori"],
235
219
  [ops.literal, "arg"],
236
220
  ],
237
221
  ]);
238
222
  assertParse("functionComposition", "(fn()) 'arg'", [
239
- [[ops.scope, "fn"], undefined],
223
+ [[ops.builtin, "fn"], undefined],
240
224
  [ops.literal, "arg"],
241
225
  ]);
242
226
  assertParse("functionComposition", "tree/key arg", [
243
227
  [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
244
228
  [ops.scope, "arg"],
245
229
  ]);
246
- assertParse("functionComposition", "https://example.com/tree.yaml 'key'", [
247
- [ops.https, [ops.literal, "example.com"], [ops.literal, "tree.yaml"]],
248
- [ops.literal, "key"],
230
+ assertParse("functionComposition", "new:(js:Date, '2025-01-01')", [
231
+ [ops.builtin, "new:"],
232
+ [
233
+ [ops.builtin, "js:"],
234
+ [ops.literal, "Date"],
235
+ ],
236
+ [ops.literal, "2025-01-01"],
237
+ ]);
238
+ assertParse("functionComposition", "map(markdown, mdHtml)", [
239
+ [ops.builtin, "map"],
240
+ [ops.scope, "markdown"],
241
+ [ops.scope, "mdHtml"],
249
242
  ]);
243
+ assertParse(
244
+ "functionComposition",
245
+ "package:@weborigami/dropbox/auth(creds)",
246
+ [
247
+ [
248
+ [ops.builtin, "package:"],
249
+ [ops.literal, "@weborigami/"],
250
+ [ops.literal, "dropbox/"],
251
+ [ops.literal, "auth"],
252
+ ],
253
+ [ops.scope, "creds"],
254
+ ]
255
+ );
256
+ });
257
+
258
+ test("functionReference", () => {
259
+ assertParse("functionReference", "json", [ops.builtin, "json"]);
260
+ assertParse("functionReference", "greet.js", [ops.scope, "greet.js"]);
250
261
  });
251
262
 
252
263
  test("group", () => {
253
264
  assertParse("group", "(hello)", [ops.scope, "hello"]);
254
265
  assertParse("group", "(((nested)))", [ops.scope, "nested"]);
255
- assertParse("group", "(fn())", [[ops.scope, "fn"], undefined]);
266
+ assertParse("group", "(fn())", [[ops.builtin, "fn"], undefined]);
267
+ assertParse("group", "(a -> b)", [
268
+ [ops.builtin, "b"],
269
+ [ops.scope, "a"],
270
+ ]);
271
+ });
272
+
273
+ test("homeTree", () => {
274
+ assertParse("homeTree", "~", [ops.homeTree]);
256
275
  });
257
276
 
258
277
  test("host", () => {
259
278
  assertParse("host", "abc", [ops.literal, "abc"]);
260
279
  assertParse("host", "abc:123", [ops.literal, "abc:123"]);
261
280
  assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
281
+ assertParse("host", "example.com/", [ops.literal, "example.com/"]);
262
282
  });
263
283
 
264
284
  test("identifier", () => {
@@ -283,6 +303,14 @@ describe("Origami parser", () => {
283
303
  [ops.concat, [ops.scope, "name"]],
284
304
  ],
285
305
  ]);
306
+ assertParse("lambda", "=indent`hello`", [
307
+ ops.lambda,
308
+ ["_"],
309
+ [
310
+ [ops.builtin, "indent"],
311
+ [ops.literal, ["hello"]],
312
+ ],
313
+ ]);
286
314
  });
287
315
 
288
316
  test("leadingSlashPath", () => {
@@ -324,10 +352,32 @@ describe("Origami parser", () => {
324
352
  assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
325
353
  });
326
354
 
327
- test("new", () => {
328
- assertParse("expression", "new:@js/Date('2025-01-01')", [
329
- [ops.constructor, [ops.literal, "@js"], [ops.literal, "Date"]],
330
- [ops.literal, "2025-01-01"],
355
+ test("namespace", () => {
356
+ assertParse("namespace", "js:", [ops.builtin, "js:"]);
357
+ });
358
+
359
+ test("namespacePath", () => {
360
+ assertParse("namespacePath", "js:Date", [
361
+ [ops.builtin, "js:"],
362
+ [ops.literal, "Date"],
363
+ ]);
364
+ assertParse("namespacePath", "files:src/assets", [
365
+ [ops.builtin, "files:"],
366
+ [ops.literal, "src/"],
367
+ [ops.literal, "assets"],
368
+ ]);
369
+ assertParse("namespacePath", "foo://bar", [
370
+ [ops.builtin, "foo:"],
371
+ [ops.literal, "bar"],
372
+ ]);
373
+ assertParse("namespacePath", "http://example.com", [
374
+ [ops.builtin, "http:"],
375
+ [ops.literal, "example.com"],
376
+ ]);
377
+ assertParse("namespacePath", "https://example.com/foo/", [
378
+ [ops.builtin, "https:"],
379
+ [ops.literal, "example.com/"],
380
+ [ops.literal, "foo/"],
331
381
  ]);
332
382
  });
333
383
 
@@ -366,6 +416,18 @@ describe("Origami parser", () => {
366
416
  ["a", [ops.getter, [ops.scope, "b"]]],
367
417
  ["b", [ops.literal, 2]],
368
418
  ]);
419
+ assertParse(
420
+ "object",
421
+ `{
422
+ a = b
423
+ b = 2
424
+ }`,
425
+ [
426
+ ops.object,
427
+ ["a", [ops.getter, [ops.scope, "b"]]],
428
+ ["b", [ops.literal, 2]],
429
+ ]
430
+ );
369
431
  assertParse("object", "{ a: { b: 1 } }", [
370
432
  ops.object,
371
433
  ["a", [ops.object, ["b", [ops.literal, 1]]]],
@@ -376,16 +438,19 @@ describe("Origami parser", () => {
376
438
  ]);
377
439
  assertParse("object", "{ a: { b = fn() } }", [
378
440
  ops.object,
379
- ["a/", [ops.object, ["b", [ops.getter, [[ops.scope, "fn"], undefined]]]]],
441
+ [
442
+ "a/",
443
+ [ops.object, ["b", [ops.getter, [[ops.builtin, "fn"], undefined]]]],
444
+ ],
380
445
  ]);
381
- assertParse("object", "{ x = fn('a') }", [
446
+ assertParse("object", "{ x = fn.js('a') }", [
382
447
  ops.object,
383
448
  [
384
449
  "x",
385
450
  [
386
451
  ops.getter,
387
452
  [
388
- [ops.scope, "fn"],
453
+ [ops.scope, "fn.js"],
389
454
  [ops.literal, "a"],
390
455
  ],
391
456
  ],
@@ -410,10 +475,10 @@ describe("Origami parser", () => {
410
475
  "a",
411
476
  [ops.lambda, ["a"], [ops.scope, "a"]],
412
477
  ]);
413
- assertParse("objectEntry", "posts/: @map(posts, post.ori)", [
478
+ assertParse("objectEntry", "posts/: map(posts, post.ori)", [
414
479
  "posts/",
415
480
  [
416
- [ops.scope, "@map"],
481
+ [ops.builtin, "map"],
417
482
  [ops.inherited, "posts"],
418
483
  [ops.scope, "post.ori"],
419
484
  ],
@@ -425,12 +490,12 @@ describe("Origami parser", () => {
425
490
  "data",
426
491
  [ops.getter, [ops.scope, "obj.json"]],
427
492
  ]);
428
- assertParse("objectGetter", "foo = fn 'bar'", [
493
+ assertParse("objectGetter", "foo = page.ori 'bar'", [
429
494
  "foo",
430
495
  [
431
496
  ops.getter,
432
497
  [
433
- [ops.scope, "fn"],
498
+ [ops.scope, "page.ori"],
434
499
  [ops.literal, "bar"],
435
500
  ],
436
501
  ],
@@ -446,7 +511,7 @@ describe("Origami parser", () => {
446
511
  assertParse("objectProperty", "x: fn('a')", [
447
512
  "x",
448
513
  [
449
- [ops.scope, "fn"],
514
+ [ops.builtin, "fn"],
450
515
  [ops.literal, "a"],
451
516
  ],
452
517
  ]);
@@ -468,7 +533,7 @@ describe("Origami parser", () => {
468
533
  ops.lambda,
469
534
  ["a", "b", "c"],
470
535
  [
471
- [ops.scope, "fn"],
536
+ [ops.builtin, "fn"],
472
537
  [ops.scope, "a"],
473
538
  [ops.scope, "b"],
474
539
  [ops.scope, "c"],
@@ -481,7 +546,7 @@ describe("Origami parser", () => {
481
546
  ops.lambda,
482
547
  ["b"],
483
548
  [
484
- [ops.scope, "fn"],
549
+ [ops.builtin, "fn"],
485
550
  [ops.scope, "a"],
486
551
  [ops.scope, "b"],
487
552
  ],
@@ -516,8 +581,9 @@ describe("Origami parser", () => {
516
581
  });
517
582
 
518
583
  test("pipeline", () => {
584
+ assertParse("pipeline", "foo", [ops.scope, "foo"]);
519
585
  assertParse("pipeline", "a -> b", [
520
- [ops.scope, "b"],
586
+ [ops.builtin, "b"],
521
587
  [ops.scope, "a"],
522
588
  ]);
523
589
  assertParse("pipeline", "input → one.js → two.js", [
@@ -528,33 +594,28 @@ describe("Origami parser", () => {
528
594
  ],
529
595
  ]);
530
596
  assertParse("pipeline", "fn a -> b", [
531
- [ops.scope, "b"],
597
+ [ops.builtin, "b"],
532
598
  [
533
- [ops.scope, "fn"],
599
+ [ops.builtin, "fn"],
534
600
  [ops.scope, "a"],
535
601
  ],
536
602
  ]);
537
603
  });
538
604
 
539
- test("protocolCall", () => {
540
- assertParse("protocolCall", "foo://bar", [
541
- [ops.scope, "foo"],
542
- [ops.literal, "bar"],
543
- ]);
544
- assertParse("protocolCall", "https://example.com/foo/", [
545
- ops.https,
546
- [ops.literal, "example.com"],
547
- [ops.literal, "foo/"],
548
- ]);
549
- assertParse("protocolCall", "http:example.com", [
550
- ops.http,
551
- [ops.literal, "example.com"],
552
- ]);
553
- assertParse("protocolCall", "http://localhost:5000/foo", [
554
- ops.http,
555
- [ops.literal, "localhost:5000"],
556
- [ops.literal, "foo"],
557
- ]);
605
+ test("pipelineStep", () => {
606
+ assertParse("pipelineStep", "foo", [ops.builtin, "foo"]);
607
+ assertParse("pipelineStep", "=_", [ops.lambda, ["_"], [ops.scope, "_"]]);
608
+ });
609
+
610
+ test("program", () => {
611
+ assertParse(
612
+ "program",
613
+ `#!/usr/bin/env ori invoke
614
+ 'Hello'
615
+ `,
616
+ [ops.literal, "Hello"],
617
+ false
618
+ );
558
619
  });
559
620
 
560
621
  test("scopeReference", () => {
@@ -577,17 +638,6 @@ describe("Origami parser", () => {
577
638
  ]);
578
639
  });
579
640
 
580
- test("shebang", () => {
581
- assertParse(
582
- "expression",
583
- `#!/usr/bin/env ori @invoke
584
- 'Hello'
585
- `,
586
- [ops.literal, "Hello"],
587
- false
588
- );
589
- });
590
-
591
641
  test("singleLineComment", () => {
592
642
  assertParse("singleLineComment", "// Hello, world!", null, false);
593
643
  });
@@ -612,8 +662,12 @@ describe("Origami parser", () => {
612
662
  });
613
663
 
614
664
  test("taggedTemplate", () => {
615
- assertParse("taggedTemplate", "tag`Hello, world.`", [
616
- [ops.scope, "tag"],
665
+ assertParse("taggedTemplate", "indent`hello`", [
666
+ [ops.builtin, "indent"],
667
+ [ops.literal, ["hello"]],
668
+ ]);
669
+ assertParse("taggedTemplate", "fn.js`Hello, world.`", [
670
+ [ops.scope, "fn.js"],
617
671
  [ops.literal, ["Hello, world."]],
618
672
  ]);
619
673
  });
@@ -650,13 +704,13 @@ describe("Origami parser", () => {
650
704
  [ops.literal, ["", ""]],
651
705
  [ops.concat, [ops.template, [ops.literal, ["nested"]]]],
652
706
  ]);
653
- assertParse("templateLiteral", "`${map(people, =`${name}`)}`", [
707
+ assertParse("templateLiteral", "`${ map:(people, =`${name}`) }`", [
654
708
  ops.template,
655
709
  [ops.literal, ["", ""]],
656
710
  [
657
711
  ops.concat,
658
712
  [
659
- [ops.scope, "map"],
713
+ [ops.builtin, "map:"],
660
714
  [ops.scope, "people"],
661
715
  [
662
716
  ops.lambda,
@@ -689,6 +743,80 @@ describe("Origami parser", () => {
689
743
  });
690
744
  });
691
745
 
746
+ test("value", () => {
747
+ assertParse("value", "obj.json", [ops.scope, "obj.json"]);
748
+ assertParse("value", "(fn a, b, c)", [
749
+ [ops.builtin, "fn"],
750
+ [ops.scope, "a"],
751
+ [ops.scope, "b"],
752
+ [ops.scope, "c"],
753
+ ]);
754
+ assertParse("value", "foo.bar('hello', 'world')", [
755
+ [ops.scope, "foo.bar"],
756
+ [ops.literal, "hello"],
757
+ [ops.literal, "world"],
758
+ ]);
759
+ assertParse("value", "(key)('a')", [
760
+ [ops.scope, "key"],
761
+ [ops.literal, "a"],
762
+ ]);
763
+ assertParse("value", "1", [ops.literal, 1]);
764
+ assertParse("value", "{ a: 1, b: 2 }", [
765
+ ops.object,
766
+ ["a", [ops.literal, 1]],
767
+ ["b", [ops.literal, 2]],
768
+ ]);
769
+ assertParse("value", "serve { index.html: 'hello' }", [
770
+ [ops.builtin, "serve"],
771
+ [ops.object, ["index.html", [ops.literal, "hello"]]],
772
+ ]);
773
+ assertParse("value", "fn =`x`", [
774
+ [ops.builtin, "fn"],
775
+ [ops.lambda, ["_"], [ops.template, [ops.literal, ["x"]]]],
776
+ ]);
777
+ assertParse("value", "copy app.js(formulas), files:snapshot", [
778
+ [ops.builtin, "copy"],
779
+ [
780
+ [ops.scope, "app.js"],
781
+ [ops.scope, "formulas"],
782
+ ],
783
+ [
784
+ [ops.builtin, "files:"],
785
+ [ops.literal, "snapshot"],
786
+ ],
787
+ ]);
788
+ assertParse("value", "map =`<li>${_}</li>`", [
789
+ [ops.builtin, "map"],
790
+ [
791
+ ops.lambda,
792
+ ["_"],
793
+ [
794
+ ops.template,
795
+ [ops.literal, ["<li>", "</li>"]],
796
+ [ops.concat, [ops.scope, "_"]],
797
+ ],
798
+ ],
799
+ ]);
800
+ assertParse("value", `"https://example.com"`, [
801
+ ops.literal,
802
+ "https://example.com",
803
+ ]);
804
+ assertParse("value", "tag`Hello, ${name}!`", [
805
+ [ops.builtin, "tag"],
806
+ [ops.literal, ["Hello, ", "!"]],
807
+ [ops.concat, [ops.scope, "name"]],
808
+ ]);
809
+ assertParse("value", "(post, slug) => fn.js(post, slug)", [
810
+ ops.lambda,
811
+ ["post", "slug"],
812
+ [
813
+ [ops.scope, "fn.js"],
814
+ [ops.scope, "post"],
815
+ [ops.scope, "slug"],
816
+ ],
817
+ ]);
818
+ });
819
+
692
820
  function assertParse(startRule, source, expected, checkLocation = true) {
693
821
  const code = parse(source, {
694
822
  grammarSource: { text: source },
@@ -699,7 +827,7 @@ function assertParse(startRule, source, expected, checkLocation = true) {
699
827
  // entire source. We skip this check in cases where the source starts or ends
700
828
  // with a comment; the parser will strip those.
701
829
  if (checkLocation) {
702
- assert(code.location);
830
+ assert(code.location, "no location");
703
831
  const resultSource = code.location.source.text.slice(
704
832
  code.location.start.offset,
705
833
  code.location.end.offset
@@ -1,4 +1,4 @@
1
1
  Greetings:
2
2
 
3
- {{ @map(=`Hello, {{ _ }}.
3
+ {{ map(=`Hello, {{ _ }}.
4
4
  `)(names.yaml) }}
@@ -1,9 +1,9 @@
1
1
  import { ObjectTree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
- import { handleExtension } from "../../src/runtime/extensions.js";
4
+ import { handleExtension } from "../../src/runtime/handlers.js";
5
5
 
6
- describe("extensions", () => {
6
+ describe("handlers", () => {
7
7
  test("attaches an unpack method to a value with an extension", async () => {
8
8
  const fixture = createFixture();
9
9
  const numberValue = await fixture.get("foo");