@weborigami/language 0.1.0 → 0.2.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.
@@ -1,39 +1,32 @@
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
5
  import * as ops from "../../src/runtime/ops.js";
5
6
  import { stripCodeLocations } from "./stripCodeLocations.js";
6
7
 
7
8
  describe("Origami parser", () => {
8
- test("absoluteFilePath", () => {
9
- assertParse("absoluteFilePath", "/foo/bar", [
10
- [ops.filesRoot],
11
- [ops.literal, "foo/"],
12
- [ops.literal, "bar"],
13
- ]);
14
- });
15
-
16
- test("array", () => {
17
- assertParse("array", "[]", [ops.array]);
18
- assertParse("array", "[1, 2, 3]", [
9
+ test("arrayLiteral", () => {
10
+ assertParse("arrayLiteral", "[]", [ops.array]);
11
+ assertParse("arrayLiteral", "[1, 2, 3]", [
19
12
  ops.array,
20
13
  [ops.literal, 1],
21
14
  [ops.literal, 2],
22
15
  [ops.literal, 3],
23
16
  ]);
24
- assertParse("array", "[ 1 , 2 , 3 ]", [
17
+ assertParse("arrayLiteral", "[ 1 , 2 , 3 ]", [
25
18
  ops.array,
26
19
  [ops.literal, 1],
27
20
  [ops.literal, 2],
28
21
  [ops.literal, 3],
29
22
  ]);
30
- assertParse("array", "[ 1, ...[2, 3]]", [
23
+ assertParse("arrayLiteral", "[ 1, ...[2, 3]]", [
31
24
  ops.merge,
32
25
  [ops.array, [ops.literal, 1]],
33
26
  [ops.array, [ops.literal, 2], [ops.literal, 3]],
34
27
  ]);
35
28
  assertParse(
36
- "array",
29
+ "arrayLiteral",
37
30
  `[
38
31
  1
39
32
  2
@@ -43,27 +36,190 @@ describe("Origami parser", () => {
43
36
  );
44
37
  });
45
38
 
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],
39
+ test("arrowFunction", () => {
40
+ assertParse("arrowFunction", "() => foo", [
41
+ ops.lambda,
42
+ [],
43
+ [ops.scope, "foo"],
44
+ ]);
45
+ assertParse("arrowFunction", "(a, b, c) ⇒ fn(a, b, c)", [
46
+ ops.lambda,
47
+ ["a", "b", "c"],
48
+ [
49
+ [ops.builtin, "fn"],
50
+ [ops.scope, "a"],
51
+ [ops.scope, "b"],
52
+ [ops.scope, "c"],
53
+ ],
54
+ ]);
55
+ assertParse("arrowFunction", "(a) => (b) => fn(a, b)", [
56
+ ops.lambda,
57
+ ["a"],
58
+ [
59
+ ops.lambda,
60
+ ["b"],
61
+ [
62
+ [ops.builtin, "fn"],
63
+ [ops.scope, "a"],
64
+ [ops.scope, "b"],
65
+ ],
66
+ ],
53
67
  ]);
54
68
  });
55
69
 
56
- test("doubleSlashPath", () => {
57
- assertParse("doubleSlashPath", "//example.com", [
58
- [ops.literal, "example.com"],
70
+ test("callExpression", () => {
71
+ assertParse("callExpression", "fn()", [[ops.builtin, "fn"], undefined]);
72
+ assertParse("callExpression", "foo.js(arg)", [
73
+ [ops.scope, "foo.js"],
74
+ [ops.scope, "arg"],
59
75
  ]);
60
- assertParse("doubleSlashPath", "//example.com/index.html", [
61
- [ops.literal, "example.com/"],
62
- [ops.literal, "index.html"],
76
+ assertParse("callExpression", "fn(a, b)", [
77
+ [ops.builtin, "fn"],
78
+ [ops.scope, "a"],
79
+ [ops.scope, "b"],
63
80
  ]);
64
- assertParse("doubleSlashPath", "//localhost:5000/foo", [
65
- [ops.literal, "localhost:5000/"],
66
- [ops.literal, "foo"],
81
+ assertParse("callExpression", "foo.js( a , b )", [
82
+ [ops.scope, "foo.js"],
83
+ [ops.scope, "a"],
84
+ [ops.scope, "b"],
85
+ ]);
86
+ assertParse("callExpression", "fn()(arg)", [
87
+ [[ops.builtin, "fn"], undefined],
88
+ [ops.scope, "arg"],
89
+ ]);
90
+ assertParse("callExpression", "tree/", [ops.unpack, [ops.scope, "tree/"]]);
91
+ assertParse("callExpression", "tree/foo/bar", [
92
+ ops.traverse,
93
+ [ops.scope, "tree/"],
94
+ [ops.literal, "foo/"],
95
+ [ops.literal, "bar"],
96
+ ]);
97
+ assertParse("callExpression", "tree/foo/bar/", [
98
+ ops.traverse,
99
+ [ops.scope, "tree/"],
100
+ [ops.literal, "foo/"],
101
+ [ops.literal, "bar/"],
102
+ ]);
103
+ assertParse("callExpression", "/foo/bar", [
104
+ ops.traverse,
105
+ [ops.rootDirectory, [ops.literal, "foo/"]],
106
+ [ops.literal, "bar"],
107
+ ]);
108
+ assertParse("callExpression", "foo.js()/key", [
109
+ ops.traverse,
110
+ [[ops.scope, "foo.js"], undefined],
111
+ [ops.literal, "key"],
112
+ ]);
113
+ assertParse("callExpression", "tree/key()", [
114
+ [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
115
+ undefined,
116
+ ]);
117
+ assertParse("callExpression", "(tree)/", [ops.unpack, [ops.scope, "tree"]]);
118
+ assertParse("callExpression", "fn()/key()", [
119
+ [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
120
+ undefined,
121
+ ]);
122
+ assertParse("callExpression", "(foo.js())('arg')", [
123
+ [[ops.scope, "foo.js"], undefined],
124
+ [ops.literal, "arg"],
125
+ ]);
126
+ assertParse("callExpression", "fn('a')('b')", [
127
+ [
128
+ [ops.builtin, "fn"],
129
+ [ops.literal, "a"],
130
+ ],
131
+ [ops.literal, "b"],
132
+ ]);
133
+ assertParse("callExpression", "(foo.js())(a, b)", [
134
+ [[ops.scope, "foo.js"], undefined],
135
+ [ops.scope, "a"],
136
+ [ops.scope, "b"],
137
+ ]);
138
+ assertParse("callExpression", "{ a: 1, b: 2}/b", [
139
+ ops.traverse,
140
+ [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
141
+ [ops.literal, "b"],
142
+ ]);
143
+ assertParse("callExpression", "indent`hello`", [
144
+ [ops.builtin, "indent"],
145
+ [ops.literal, ["hello"]],
146
+ ]);
147
+ assertParse("callExpression", "fn.js`Hello, world.`", [
148
+ [ops.scope, "fn.js"],
149
+ [ops.literal, ["Hello, world."]],
150
+ ]);
151
+ assertParse("callExpression", "files:src/assets", [
152
+ ops.traverse,
153
+ [
154
+ [ops.builtin, "files:"],
155
+ [ops.literal, "src/"],
156
+ ],
157
+ [ops.literal, "assets"],
158
+ ]);
159
+ assertParse("callExpression", "new:(js:Date, '2025-01-01')", [
160
+ [ops.builtin, "new:"],
161
+ [
162
+ [ops.builtin, "js:"],
163
+ [ops.literal, "Date"],
164
+ ],
165
+ [ops.literal, "2025-01-01"],
166
+ ]);
167
+ assertParse("callExpression", "map(markdown, mdHtml)", [
168
+ [ops.builtin, "map"],
169
+ [ops.scope, "markdown"],
170
+ [ops.scope, "mdHtml"],
171
+ ]);
172
+ assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
173
+ [
174
+ ops.traverse,
175
+ [
176
+ [ops.builtin, "package:"],
177
+ [ops.literal, "@weborigami/"],
178
+ ],
179
+ [ops.literal, "dropbox/"],
180
+ [ops.literal, "auth"],
181
+ ],
182
+ [ops.scope, "creds"],
183
+ ]);
184
+ });
185
+
186
+ test("conditionalExpression", () => {
187
+ assertParse("conditionalExpression", "1", [ops.literal, 1]);
188
+ assertParse("conditionalExpression", "true ? 1 : 0", [
189
+ ops.conditional,
190
+ [ops.scope, "true"],
191
+ [ops.lambda, [], [ops.literal, "1"]],
192
+ [ops.lambda, [], [ops.literal, "0"]],
193
+ ]);
194
+ assertParse("conditionalExpression", "false ? () => 1 : 0", [
195
+ ops.conditional,
196
+ [ops.scope, "false"],
197
+ [ops.lambda, [], [ops.lambda, [], [ops.literal, "1"]]],
198
+ [ops.lambda, [], [ops.literal, "0"]],
199
+ ]);
200
+ assertParse("conditionalExpression", "false ? =1 : 0", [
201
+ ops.conditional,
202
+ [ops.scope, "false"],
203
+ [ops.lambda, [], [ops.lambda, ["_"], [ops.literal, "1"]]],
204
+ [ops.lambda, [], [ops.literal, "0"]],
205
+ ]);
206
+ });
207
+
208
+ test("equalityExpression", () => {
209
+ assertParse("equalityExpression", "1 === 1", [
210
+ ops.strictEqual,
211
+ [ops.literal, 1],
212
+ [ops.literal, 1],
213
+ ]);
214
+ assertParse("equalityExpression", "a === b === c", [
215
+ ops.strictEqual,
216
+ [ops.strictEqual, [undetermined, "a"], [undetermined, "b"]],
217
+ [undetermined, "c"],
218
+ ]);
219
+ assertParse("equalityExpression", "1 !== 1", [
220
+ ops.notStrictEqual,
221
+ [ops.literal, 1],
222
+ [ops.literal, 1],
67
223
  ]);
68
224
  });
69
225
 
@@ -114,7 +270,7 @@ describe("Origami parser", () => {
114
270
 
115
271
  // Single slash at start of something = absolute file path
116
272
  assertParse("expression", "/path", [
117
- [ops.filesRoot],
273
+ ops.rootDirectory,
118
274
  [ops.literal, "path"],
119
275
  ]);
120
276
 
@@ -128,136 +284,88 @@ describe("Origami parser", () => {
128
284
  ],
129
285
  ]);
130
286
 
287
+ // Slash on its own is the root folder
288
+ assertParse("expression", "keys /", [
289
+ [ops.builtin, "keys"],
290
+ [ops.rootDirectory],
291
+ ]);
292
+
131
293
  assertParse("expression", "'Hello' -> test.orit", [
132
294
  [ops.scope, "test.orit"],
133
295
  [ops.literal, "Hello"],
134
296
  ]);
135
- });
136
-
137
- test("functionComposition", () => {
138
- assertParse("functionComposition", "fn()", [
297
+ assertParse("expression", "obj.json", [ops.scope, "obj.json"]);
298
+ assertParse("expression", "(fn a, b, c)", [
139
299
  [ops.builtin, "fn"],
140
- undefined,
141
- ]);
142
- assertParse("functionComposition", "foo.js(arg)", [
143
- [ops.scope, "foo.js"],
144
- [ops.scope, "arg"],
300
+ [undetermined, "a"],
301
+ [undetermined, "b"],
302
+ [undetermined, "c"],
145
303
  ]);
146
- assertParse("functionComposition", "fn(a, b)", [
147
- [ops.builtin, "fn"],
148
- [ops.scope, "a"],
149
- [ops.scope, "b"],
304
+ assertParse("expression", "foo.bar('hello', 'world')", [
305
+ [ops.scope, "foo.bar"],
306
+ [ops.literal, "hello"],
307
+ [ops.literal, "world"],
150
308
  ]);
151
- assertParse("functionComposition", "foo.js( a , b )", [
152
- [ops.scope, "foo.js"],
153
- [ops.scope, "a"],
154
- [ops.scope, "b"],
155
- ]);
156
- assertParse("functionComposition", "fn()(arg)", [
157
- [[ops.builtin, "fn"], undefined],
158
- [ops.scope, "arg"],
159
- ]);
160
- assertParse("functionComposition", "foo.js()/key", [
161
- ops.traverse,
162
- [[ops.scope, "foo.js"], undefined],
163
- [ops.literal, "key"],
164
- ]);
165
- assertParse("functionComposition", "tree/key()", [
166
- [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
167
- undefined,
309
+ assertParse("expression", "(key)('a')", [
310
+ [ops.scope, "key"],
311
+ [ops.literal, "a"],
168
312
  ]);
169
- assertParse("functionComposition", "(tree)/", [
170
- ops.unpack,
171
- [ops.scope, "tree"],
313
+ assertParse("expression", "1", [ops.literal, 1]);
314
+ assertParse("expression", "{ a: 1, b: 2 }", [
315
+ ops.object,
316
+ ["a", [ops.literal, 1]],
317
+ ["b", [ops.literal, 2]],
172
318
  ]);
173
- assertParse("functionComposition", "fn()/key()", [
174
- [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
175
- undefined,
319
+ assertParse("expression", "serve { index.html: 'hello' }", [
320
+ [ops.builtin, "serve"],
321
+ [ops.object, ["index.html", [ops.literal, "hello"]]],
176
322
  ]);
177
- assertParse("functionComposition", "(foo.js())('arg')", [
178
- [[ops.scope, "foo.js"], undefined],
179
- [ops.literal, "arg"],
323
+ assertParse("expression", "fn =`x`", [
324
+ [ops.builtin, "fn"],
325
+ [ops.lambda, ["_"], [ops.template, [ops.literal, ["x"]]]],
180
326
  ]);
181
- assertParse("functionComposition", "fn('a')('b')", [
327
+ assertParse("expression", "copy app.js(formulas), files:snapshot", [
328
+ [ops.builtin, "copy"],
182
329
  [
183
- [ops.builtin, "fn"],
184
- [ops.literal, "a"],
330
+ [ops.scope, "app.js"],
331
+ [ops.scope, "formulas"],
185
332
  ],
186
- [ops.literal, "b"],
187
- ]);
188
- assertParse("functionComposition", "(foo.js())(a, b)", [
189
- [[ops.scope, "foo.js"], undefined],
190
- [ops.scope, "a"],
191
- [ops.scope, "b"],
192
- ]);
193
- assertParse("functionComposition", "{ a: 1, b: 2}/b", [
194
- ops.traverse,
195
- [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
196
- [ops.literal, "b"],
197
- ]);
198
- assertParse("functionComposition", "fn arg", [
199
- [ops.builtin, "fn"],
200
- [ops.scope, "arg"],
201
- ]);
202
- assertParse("functionComposition", "page.ori 'a', 'b'", [
203
- [ops.scope, "page.ori"],
204
- [ops.literal, "a"],
205
- [ops.literal, "b"],
206
- ]);
207
- assertParse("functionComposition", "fn a(b), c", [
208
- [ops.builtin, "fn"],
209
333
  [
210
- [ops.builtin, "a"],
211
- [ops.scope, "b"],
334
+ [ops.builtin, "files:"],
335
+ [ops.literal, "snapshot"],
212
336
  ],
213
- [ops.scope, "c"],
214
337
  ]);
215
- assertParse("functionComposition", "foo.js bar.ori 'arg'", [
216
- [ops.scope, "foo.js"],
338
+ assertParse("expression", "map =`<li>${_}</li>`", [
339
+ [ops.builtin, "map"],
217
340
  [
218
- [ops.scope, "bar.ori"],
219
- [ops.literal, "arg"],
341
+ ops.lambda,
342
+ ["_"],
343
+ [
344
+ ops.template,
345
+ [ops.literal, ["<li>", "</li>"]],
346
+ [ops.concat, [ops.scope, "_"]],
347
+ ],
220
348
  ],
221
349
  ]);
222
- assertParse("functionComposition", "(fn()) 'arg'", [
223
- [[ops.builtin, "fn"], undefined],
224
- [ops.literal, "arg"],
350
+ assertParse("expression", `https://example.com/about/`, [
351
+ [ops.builtin, "https:"],
352
+ [ops.literal, "example.com/"],
353
+ [ops.literal, "about/"],
225
354
  ]);
226
- assertParse("functionComposition", "tree/key arg", [
227
- [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
228
- [ops.scope, "arg"],
355
+ assertParse("expression", "tag`Hello, ${name}!`", [
356
+ [ops.builtin, "tag"],
357
+ [ops.literal, ["Hello, ", "!"]],
358
+ [ops.concat, [ops.scope, "name"]],
229
359
  ]);
230
- assertParse("functionComposition", "new:(js:Date, '2025-01-01')", [
231
- [ops.builtin, "new:"],
360
+ assertParse("expression", "(post, slug) => fn.js(post, slug)", [
361
+ ops.lambda,
362
+ ["post", "slug"],
232
363
  [
233
- [ops.builtin, "js:"],
234
- [ops.literal, "Date"],
364
+ [ops.scope, "fn.js"],
365
+ [ops.scope, "post"],
366
+ [ops.scope, "slug"],
235
367
  ],
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"],
242
368
  ]);
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"]);
261
369
  });
262
370
 
263
371
  test("group", () => {
@@ -270,15 +378,14 @@ describe("Origami parser", () => {
270
378
  ]);
271
379
  });
272
380
 
273
- test("homeTree", () => {
274
- assertParse("homeTree", "~", [ops.homeTree]);
381
+ test("homeDirectory", () => {
382
+ assertParse("homeDirectory", "~", [ops.homeDirectory]);
275
383
  });
276
384
 
277
385
  test("host", () => {
278
386
  assertParse("host", "abc", [ops.literal, "abc"]);
279
387
  assertParse("host", "abc:123", [ops.literal, "abc:123"]);
280
388
  assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
281
- assertParse("host", "example.com/", [ops.literal, "example.com/"]);
282
389
  });
283
390
 
284
391
  test("identifier", () => {
@@ -288,37 +395,41 @@ describe("Origami parser", () => {
288
395
  assertParse("identifier", "x-y-z", "x-y-z", false);
289
396
  });
290
397
 
291
- test("lambda", () => {
292
- assertParse("lambda", "=message", [
293
- ops.lambda,
294
- ["_"],
295
- [ops.scope, "message"],
398
+ test("implicitParenthesesCallExpression", () => {
399
+ assertParse("implicitParenthesesCallExpression", "fn arg", [
400
+ [ops.builtin, "fn"],
401
+ [undetermined, "arg"],
296
402
  ]);
297
- assertParse("lambda", "=`Hello, ${name}.`", [
298
- ops.lambda,
299
- ["_"],
403
+ assertParse("implicitParenthesesCallExpression", "page.ori 'a', 'b'", [
404
+ [ops.scope, "page.ori"],
405
+ [ops.literal, "a"],
406
+ [ops.literal, "b"],
407
+ ]);
408
+ assertParse("implicitParenthesesCallExpression", "fn a(b), c", [
409
+ [ops.builtin, "fn"],
300
410
  [
301
- ops.template,
302
- [ops.literal, ["Hello, ", "."]],
303
- [ops.concat, [ops.scope, "name"]],
411
+ [ops.builtin, "a"],
412
+ [ops.scope, "b"],
304
413
  ],
414
+ [undetermined, "c"],
305
415
  ]);
306
- assertParse("lambda", "=indent`hello`", [
307
- ops.lambda,
308
- ["_"],
416
+ assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
417
+ [[ops.builtin, "fn"], undefined],
418
+ [ops.literal, "arg"],
419
+ ]);
420
+ assertParse("implicitParenthesesCallExpression", "tree/key arg", [
421
+ [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
422
+ [undetermined, "arg"],
423
+ ]);
424
+ assertParse("implicitParenthesesCallExpression", "foo.js bar.ori 'arg'", [
425
+ [ops.scope, "foo.js"],
309
426
  [
310
- [ops.builtin, "indent"],
311
- [ops.literal, ["hello"]],
427
+ [ops.scope, "bar.ori"],
428
+ [ops.literal, "arg"],
312
429
  ],
313
430
  ]);
314
431
  });
315
432
 
316
- test("leadingSlashPath", () => {
317
- assertParse("leadingSlashPath", "/", []);
318
- assertParse("leadingSlashPath", "/tree", [[ops.literal, "tree"]]);
319
- assertParse("leadingSlashPath", "/tree/", [[ops.literal, "tree/"]]);
320
- });
321
-
322
433
  test("list", () => {
323
434
  assertParse("list", "1", [[ops.literal, 1]]);
324
435
  assertParse("list", "1,2,3", [
@@ -348,6 +459,33 @@ describe("Origami parser", () => {
348
459
  ]);
349
460
  });
350
461
 
462
+ test("logicalAndExpression", () => {
463
+ assertParse("logicalAndExpression", "true && false", [
464
+ ops.logicalAnd,
465
+ [ops.scope, "true"],
466
+ [ops.lambda, [], [undetermined, "false"]],
467
+ ]);
468
+ });
469
+
470
+ test("logicalOrExpression", () => {
471
+ assertParse("logicalOrExpression", "1 || 0", [
472
+ ops.logicalOr,
473
+ [ops.literal, 1],
474
+ [ops.literal, 0],
475
+ ]);
476
+ assertParse("logicalOrExpression", "false || false || true", [
477
+ ops.logicalOr,
478
+ [ops.scope, "false"],
479
+ [ops.lambda, [], [undetermined, "false"]],
480
+ [ops.lambda, [], [undetermined, "true"]],
481
+ ]);
482
+ assertParse("logicalOrExpression", "1 || 2 && 0", [
483
+ ops.logicalOr,
484
+ [ops.literal, 1],
485
+ [ops.lambda, [], [ops.logicalAnd, [ops.literal, 2], [ops.literal, 0]]],
486
+ ]);
487
+ });
488
+
351
489
  test("multiLineComment", () => {
352
490
  assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
353
491
  });
@@ -356,68 +494,57 @@ describe("Origami parser", () => {
356
494
  assertParse("namespace", "js:", [ops.builtin, "js:"]);
357
495
  });
358
496
 
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"],
497
+ test("nullishCoalescingExpression", () => {
498
+ assertParse("nullishCoalescingExpression", "a ?? b", [
499
+ ops.nullishCoalescing,
500
+ [ops.scope, "a"],
501
+ [ops.lambda, [], [undetermined, "b"]],
376
502
  ]);
377
- assertParse("namespacePath", "https://example.com/foo/", [
378
- [ops.builtin, "https:"],
379
- [ops.literal, "example.com/"],
380
- [ops.literal, "foo/"],
503
+ assertParse("nullishCoalescingExpression", "a ?? b ?? c", [
504
+ ops.nullishCoalescing,
505
+ [ops.scope, "a"],
506
+ [ops.lambda, [], [undetermined, "b"]],
507
+ [ops.lambda, [], [undetermined, "c"]],
381
508
  ]);
382
509
  });
383
510
 
384
- test("number", () => {
385
- assertParse("number", "123", [ops.literal, 123]);
386
- assertParse("number", "-456", [ops.literal, -456]);
387
- assertParse("number", ".5", [ops.literal, 0.5]);
388
- assertParse("number", "123.45", [ops.literal, 123.45]);
389
- assertParse("number", "-678.90", [ops.literal, -678.9]);
390
- assertParse("number", "+123", [ops.literal, 123]);
391
- assertParse("number", "+456.78", [ops.literal, 456.78]);
511
+ test("numericLiteral", () => {
512
+ assertParse("numericLiteral", "123", [ops.literal, 123]);
513
+ assertParse("numericLiteral", "-456", [ops.literal, -456]);
514
+ assertParse("numericLiteral", ".5", [ops.literal, 0.5]);
515
+ assertParse("numericLiteral", "123.45", [ops.literal, 123.45]);
516
+ assertParse("numericLiteral", "-678.90", [ops.literal, -678.9]);
517
+ assertParse("numericLiteral", "+123", [ops.literal, 123]);
518
+ assertParse("numericLiteral", "+456.78", [ops.literal, 456.78]);
392
519
  });
393
520
 
394
- test("object", () => {
395
- assertParse("object", "{}", [ops.object]);
396
- assertParse("object", "{ a: 1, b }", [
521
+ test("objectLiteral", () => {
522
+ assertParse("objectLiteral", "{}", [ops.object]);
523
+ assertParse("objectLiteral", "{ a: 1, b }", [
397
524
  ops.object,
398
525
  ["a", [ops.literal, 1]],
399
526
  ["b", [ops.inherited, "b"]],
400
527
  ]);
401
- assertParse("object", "{ sub: { a: 1 } }", [
528
+ assertParse("objectLiteral", "{ sub: { a: 1 } }", [
402
529
  ops.object,
403
530
  ["sub", [ops.object, ["a", [ops.literal, 1]]]],
404
531
  ]);
405
- assertParse("object", "{ sub: { a/: 1 } }", [
532
+ assertParse("objectLiteral", "{ sub: { a/: 1 } }", [
406
533
  ops.object,
407
534
  ["sub", [ops.object, ["a/", [ops.literal, 1]]]],
408
535
  ]);
409
- assertParse("object", `{ "a": 1, "b": 2 }`, [
536
+ assertParse("objectLiteral", `{ "a": 1, "b": 2 }`, [
410
537
  ops.object,
411
538
  ["a", [ops.literal, 1]],
412
539
  ["b", [ops.literal, 2]],
413
540
  ]);
414
- assertParse("object", "{ a = b, b = 2 }", [
541
+ assertParse("objectLiteral", "{ a = b, b = 2 }", [
415
542
  ops.object,
416
543
  ["a", [ops.getter, [ops.scope, "b"]]],
417
544
  ["b", [ops.literal, 2]],
418
545
  ]);
419
546
  assertParse(
420
- "object",
547
+ "objectLiteral",
421
548
  `{
422
549
  a = b
423
550
  b = 2
@@ -428,22 +555,22 @@ describe("Origami parser", () => {
428
555
  ["b", [ops.literal, 2]],
429
556
  ]
430
557
  );
431
- assertParse("object", "{ a: { b: 1 } }", [
558
+ assertParse("objectLiteral", "{ a: { b: 1 } }", [
432
559
  ops.object,
433
560
  ["a", [ops.object, ["b", [ops.literal, 1]]]],
434
561
  ]);
435
- assertParse("object", "{ a: { b = 1 } }", [
562
+ assertParse("objectLiteral", "{ a: { b = 1 } }", [
436
563
  ops.object,
437
564
  ["a", [ops.object, ["b", [ops.literal, 1]]]],
438
565
  ]);
439
- assertParse("object", "{ a: { b = fn() } }", [
566
+ assertParse("objectLiteral", "{ a: { b = fn() } }", [
440
567
  ops.object,
441
568
  [
442
569
  "a/",
443
570
  [ops.object, ["b", [ops.getter, [[ops.builtin, "fn"], undefined]]]],
444
571
  ],
445
572
  ]);
446
- assertParse("object", "{ x = fn.js('a') }", [
573
+ assertParse("objectLiteral", "{ x = fn.js('a') }", [
447
574
  ops.object,
448
575
  [
449
576
  "x",
@@ -456,12 +583,12 @@ describe("Origami parser", () => {
456
583
  ],
457
584
  ],
458
585
  ]);
459
- assertParse("object", "{ a: 1, ...b }", [
586
+ assertParse("objectLiteral", "{ a: 1, ...b }", [
460
587
  ops.merge,
461
588
  [ops.object, ["a", [ops.literal, 1]]],
462
- [ops.scope, "b"],
589
+ [undetermined, "b"],
463
590
  ]);
464
- assertParse("object", "{ (a): 1 }", [
591
+ assertParse("objectLiteral", "{ (a): 1 }", [
465
592
  ops.object,
466
593
  ["(a)", [ops.literal, 1]],
467
594
  ]);
@@ -523,40 +650,9 @@ describe("Origami parser", () => {
523
650
  assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
524
651
  });
525
652
 
526
- test("parameterizedLambda", () => {
527
- assertParse("parameterizedLambda", "() => foo", [
528
- ops.lambda,
529
- [],
530
- [ops.scope, "foo"],
531
- ]);
532
- assertParse("parameterizedLambda", "(a, b, c) ⇒ fn(a, b, c)", [
533
- ops.lambda,
534
- ["a", "b", "c"],
535
- [
536
- [ops.builtin, "fn"],
537
- [ops.scope, "a"],
538
- [ops.scope, "b"],
539
- [ops.scope, "c"],
540
- ],
541
- ]);
542
- assertParse("parameterizedLambda", "(a) => (b) => fn(a, b)", [
543
- ops.lambda,
544
- ["a"],
545
- [
546
- ops.lambda,
547
- ["b"],
548
- [
549
- [ops.builtin, "fn"],
550
- [ops.scope, "a"],
551
- [ops.scope, "b"],
552
- ],
553
- ],
554
- ]);
555
- });
556
-
557
- test("parensArgs", () => {
558
- assertParse("parensArgs", "()", [undefined]);
559
- assertParse("parensArgs", "(a, b, c)", [
653
+ test("parenthesesArguments", () => {
654
+ assertParse("parenthesesArguments", "()", [undefined]);
655
+ assertParse("parenthesesArguments", "(a, b, c)", [
560
656
  [ops.scope, "a"],
561
657
  [ops.scope, "b"],
562
658
  [ops.scope, "c"],
@@ -564,47 +660,63 @@ describe("Origami parser", () => {
564
660
  });
565
661
 
566
662
  test("path", () => {
567
- assertParse("path", "tree/", [[ops.literal, "tree/"]]);
568
- assertParse("path", "month/12", [
663
+ assertParse("path", "/tree/", [[ops.literal, "tree/"]]);
664
+ assertParse("path", "/month/12", [
569
665
  [ops.literal, "month/"],
570
666
  [ops.literal, "12"],
571
667
  ]);
572
- assertParse("path", "tree/foo/bar", [
668
+ assertParse("path", "/tree/foo/bar", [
573
669
  [ops.literal, "tree/"],
574
670
  [ops.literal, "foo/"],
575
671
  [ops.literal, "bar"],
576
672
  ]);
577
- assertParse("path", "a///b", [
673
+ assertParse("path", "/a///b", [
578
674
  [ops.literal, "a/"],
579
675
  [ops.literal, "b"],
580
676
  ]);
581
677
  });
582
678
 
583
- test("pipeline", () => {
584
- assertParse("pipeline", "foo", [ops.scope, "foo"]);
585
- assertParse("pipeline", "a -> b", [
679
+ test("pathArguments", () => {
680
+ assertParse("pathArguments", "/", [ops.traverse]);
681
+ assertParse("pathArguments", "/tree", [
682
+ ops.traverse,
683
+ [ops.literal, "tree"],
684
+ ]);
685
+ assertParse("pathArguments", "/tree/", [
686
+ ops.traverse,
687
+ [ops.literal, "tree/"],
688
+ ]);
689
+ });
690
+
691
+ test("pipelineExpression", () => {
692
+ assertParse("pipelineExpression", "foo", [ops.scope, "foo"]);
693
+ assertParse("pipelineExpression", "a -> b", [
586
694
  [ops.builtin, "b"],
587
695
  [ops.scope, "a"],
588
696
  ]);
589
- assertParse("pipeline", "input → one.js → two.js", [
697
+ assertParse("pipelineExpression", "input → one.js → two.js", [
590
698
  [ops.scope, "two.js"],
591
699
  [
592
700
  [ops.scope, "one.js"],
593
701
  [ops.scope, "input"],
594
702
  ],
595
703
  ]);
596
- assertParse("pipeline", "fn a -> b", [
704
+ assertParse("pipelineExpression", "fn a -> b", [
597
705
  [ops.builtin, "b"],
598
706
  [
599
707
  [ops.builtin, "fn"],
600
- [ops.scope, "a"],
708
+ [undetermined, "a"],
601
709
  ],
602
710
  ]);
603
711
  });
604
712
 
605
- test("pipelineStep", () => {
606
- assertParse("pipelineStep", "foo", [ops.builtin, "foo"]);
607
- assertParse("pipelineStep", "=_", [ops.lambda, ["_"], [ops.scope, "_"]]);
713
+ test("primary", () => {
714
+ assertParse("primary", "foo.js", [ops.scope, "foo.js"]);
715
+ assertParse("primary", "[1, 2]", [
716
+ ops.array,
717
+ [ops.literal, 1],
718
+ [ops.literal, 2],
719
+ ]);
608
720
  });
609
721
 
610
722
  test("program", () => {
@@ -618,23 +730,73 @@ describe("Origami parser", () => {
618
730
  );
619
731
  });
620
732
 
733
+ test("protocolExpression", () => {
734
+ assertParse("protocolExpression", "foo://bar", [
735
+ [ops.builtin, "foo:"],
736
+ [ops.literal, "bar"],
737
+ ]);
738
+ assertParse("protocolExpression", "http://example.com", [
739
+ [ops.builtin, "http:"],
740
+ [ops.literal, "example.com"],
741
+ ]);
742
+ assertParse("protocolExpression", "https://example.com/about/", [
743
+ [ops.builtin, "https:"],
744
+ [ops.literal, "example.com/"],
745
+ [ops.literal, "about/"],
746
+ ]);
747
+ assertParse("protocolExpression", "https://example.com/about/index.html", [
748
+ [ops.builtin, "https:"],
749
+ [ops.literal, "example.com/"],
750
+ [ops.literal, "about/"],
751
+ [ops.literal, "index.html"],
752
+ ]);
753
+ assertParse("protocolExpression", "http://localhost:5000/foo", [
754
+ [ops.builtin, "http:"],
755
+ [ops.literal, "localhost:5000/"],
756
+ [ops.literal, "foo"],
757
+ ]);
758
+ });
759
+
760
+ test("qualifiedReference", () => {
761
+ assertParse("qualifiedReference", "js:Date", [
762
+ [ops.builtin, "js:"],
763
+ [ops.literal, "Date"],
764
+ ]);
765
+ });
766
+
767
+ test("rootDirectory", () => {
768
+ assertParse("rootDirectory", "/", [ops.rootDirectory]);
769
+ });
770
+
621
771
  test("scopeReference", () => {
622
- assertParse("scopeReference", "x", [ops.scope, "x"]);
772
+ assertParse("scopeReference", "keys", [undetermined, "keys"]);
773
+ assertParse("scopeReference", "greet.js", [ops.scope, "greet.js"]);
774
+ // scopeReference checks whether a slash follows; hard to test in isolation
775
+ // assertParse("scopeReference", "markdown/", [ops.scope, "markdown"]);
623
776
  });
624
777
 
625
- test("scopeTraverse", () => {
626
- assertParse("scopeTraverse", "tree/", [ops.traverse, [ops.scope, "tree/"]]);
627
- assertParse("scopeTraverse", "tree/foo/bar", [
628
- ops.traverse,
629
- [ops.scope, "tree/"],
630
- [ops.literal, "foo/"],
631
- [ops.literal, "bar"],
778
+ test("shorthandFunction", () => {
779
+ assertParse("shorthandFunction", "=message", [
780
+ ops.lambda,
781
+ ["_"],
782
+ [undetermined, "message"],
632
783
  ]);
633
- assertParse("scopeTraverse", "tree/foo/bar/", [
634
- ops.traverse,
635
- [ops.scope, "tree/"],
636
- [ops.literal, "foo/"],
637
- [ops.literal, "bar/"],
784
+ assertParse("shorthandFunction", "=`Hello, ${name}.`", [
785
+ ops.lambda,
786
+ ["_"],
787
+ [
788
+ ops.template,
789
+ [ops.literal, ["Hello, ", "."]],
790
+ [ops.concat, [ops.scope, "name"]],
791
+ ],
792
+ ]);
793
+ assertParse("shorthandFunction", "=indent`hello`", [
794
+ ops.lambda,
795
+ ["_"],
796
+ [
797
+ [ops.builtin, "indent"],
798
+ [ops.literal, ["hello"]],
799
+ ],
638
800
  ]);
639
801
  });
640
802
 
@@ -643,35 +805,24 @@ describe("Origami parser", () => {
643
805
  });
644
806
 
645
807
  test("spread", () => {
646
- assertParse("spread", "...a", [ops.spread, [ops.scope, "a"]]);
647
- assertParse("spread", "…a", [ops.spread, [ops.scope, "a"]]);
648
- });
649
-
650
- test("string", () => {
651
- assertParse("string", '"foo"', [ops.literal, "foo"]);
652
- assertParse("string", "'bar'", [ops.literal, "bar"]);
653
- assertParse("string", '"foo bar"', [ops.literal, "foo bar"]);
654
- assertParse("string", "'bar baz'", [ops.literal, "bar baz"]);
655
- assertParse("string", `"foo\\"s bar"`, [ops.literal, `foo"s bar`]);
656
- assertParse("string", `'bar\\'s baz'`, [ops.literal, `bar's baz`]);
657
- assertParse("string", `«string»`, [ops.literal, "string"]);
658
- assertParse("string", `"\\0\\b\\f\\n\\r\\t\\v"`, [
808
+ assertParse("spread", "...a", [ops.spread, [undetermined, "a"]]);
809
+ assertParse("spread", "…a", [ops.spread, [undetermined, "a"]]);
810
+ });
811
+
812
+ test("stringLiteral", () => {
813
+ assertParse("stringLiteral", '"foo"', [ops.literal, "foo"]);
814
+ assertParse("stringLiteral", "'bar'", [ops.literal, "bar"]);
815
+ assertParse("stringLiteral", '"foo bar"', [ops.literal, "foo bar"]);
816
+ assertParse("stringLiteral", "'bar baz'", [ops.literal, "bar baz"]);
817
+ assertParse("stringLiteral", `"foo\\"s bar"`, [ops.literal, `foo"s bar`]);
818
+ assertParse("stringLiteral", `'bar\\'s baz'`, [ops.literal, `bar's baz`]);
819
+ assertParse("stringLiteral", `«string»`, [ops.literal, "string"]);
820
+ assertParse("stringLiteral", `"\\0\\b\\f\\n\\r\\t\\v"`, [
659
821
  ops.literal,
660
822
  "\0\b\f\n\r\t\v",
661
823
  ]);
662
824
  });
663
825
 
664
- test("taggedTemplate", () => {
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"],
671
- [ops.literal, ["Hello, world."]],
672
- ]);
673
- });
674
-
675
826
  test("templateDocument", () => {
676
827
  assertParse("templateDocument", "hello${foo}world", [
677
828
  ops.lambda,
@@ -741,80 +892,13 @@ describe("Origami parser", () => {
741
892
  false
742
893
  );
743
894
  });
744
- });
745
895
 
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
- ]);
896
+ test("unaryExpression", () => {
897
+ assertParse("unaryExpression", "!true", [
898
+ ops.logicalNot,
899
+ [undetermined, "true"],
900
+ ]);
901
+ });
818
902
  });
819
903
 
820
904
  function assertParse(startRule, source, expected, checkLocation = true) {