@weborigami/language 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,39 +1,52 @@
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"],
9
+ test("additiveExpression", () => {
10
+ assertParse("additiveExpression", "1 + 2", [
11
+ ops.addition,
12
+ [ops.literal, 1],
13
+ [ops.literal, 2],
14
+ ]);
15
+ assertParse("additiveExpression", "5 - 4", [
16
+ ops.subtraction,
17
+ [ops.literal, 5],
18
+ [ops.literal, 4],
13
19
  ]);
14
20
  });
15
21
 
16
- test("array", () => {
17
- assertParse("array", "[]", [ops.array]);
18
- assertParse("array", "[1, 2, 3]", [
22
+ test("arrayLiteral", () => {
23
+ assertParse("arrayLiteral", "[]", [ops.array]);
24
+ assertParse("arrayLiteral", "[1, 2, 3]", [
19
25
  ops.array,
20
26
  [ops.literal, 1],
21
27
  [ops.literal, 2],
22
28
  [ops.literal, 3],
23
29
  ]);
24
- assertParse("array", "[ 1 , 2 , 3 ]", [
30
+ assertParse("arrayLiteral", "[ 1 , 2 , 3 ]", [
25
31
  ops.array,
26
32
  [ops.literal, 1],
27
33
  [ops.literal, 2],
28
34
  [ops.literal, 3],
29
35
  ]);
30
- assertParse("array", "[ 1, ...[2, 3]]", [
36
+ assertParse("arrayLiteral", "[1,,,4]", [
37
+ ops.array,
38
+ [ops.literal, 1],
39
+ [ops.literal, undefined],
40
+ [ops.literal, undefined],
41
+ [ops.literal, 4],
42
+ ]);
43
+ assertParse("arrayLiteral", "[ 1, ...[2, 3]]", [
31
44
  ops.merge,
32
45
  [ops.array, [ops.literal, 1]],
33
46
  [ops.array, [ops.literal, 2], [ops.literal, 3]],
34
47
  ]);
35
48
  assertParse(
36
- "array",
49
+ "arrayLiteral",
37
50
  `[
38
51
  1
39
52
  2
@@ -43,27 +56,237 @@ describe("Origami parser", () => {
43
56
  );
44
57
  });
45
58
 
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],
59
+ test("arrowFunction", () => {
60
+ assertParse("arrowFunction", "() => foo", [
61
+ ops.lambda,
62
+ [],
63
+ [ops.scope, "foo"],
64
+ ]);
65
+ assertParse("arrowFunction", "x => y", [
66
+ ops.lambda,
67
+ ["x"],
68
+ [ops.scope, "y"],
69
+ ]);
70
+ assertParse("arrowFunction", "(a, b, c) ⇒ fn(a, b, c)", [
71
+ ops.lambda,
72
+ ["a", "b", "c"],
73
+ [
74
+ [ops.builtin, "fn"],
75
+ [ops.scope, "a"],
76
+ [ops.scope, "b"],
77
+ [ops.scope, "c"],
78
+ ],
79
+ ]);
80
+ assertParse("arrowFunction", "a => b => fn(a, b)", [
81
+ ops.lambda,
82
+ ["a"],
83
+ [
84
+ ops.lambda,
85
+ ["b"],
86
+ [
87
+ [ops.builtin, "fn"],
88
+ [ops.scope, "a"],
89
+ [ops.scope, "b"],
90
+ ],
91
+ ],
53
92
  ]);
54
93
  });
55
94
 
56
- test("doubleSlashPath", () => {
57
- assertParse("doubleSlashPath", "//example.com", [
58
- [ops.literal, "example.com"],
95
+ test("bitwiseAndExpression", () => {
96
+ assertParse("bitwiseAndExpression", "5 & 3", [
97
+ ops.bitwiseAnd,
98
+ [ops.literal, 5],
99
+ [ops.literal, 3],
59
100
  ]);
60
- assertParse("doubleSlashPath", "//example.com/index.html", [
61
- [ops.literal, "example.com/"],
62
- [ops.literal, "index.html"],
101
+ });
102
+
103
+ test("bitwiseOrExpression", () => {
104
+ assertParse("bitwiseOrExpression", "5 | 3", [
105
+ ops.bitwiseOr,
106
+ [ops.literal, 5],
107
+ [ops.literal, 3],
63
108
  ]);
64
- assertParse("doubleSlashPath", "//localhost:5000/foo", [
65
- [ops.literal, "localhost:5000/"],
66
- [ops.literal, "foo"],
109
+ });
110
+
111
+ test("bitwiseXorExpression", () => {
112
+ assertParse("bitwiseXorExpression", "5 ^ 3", [
113
+ ops.bitwiseXor,
114
+ [ops.literal, 5],
115
+ [ops.literal, 3],
116
+ ]);
117
+ });
118
+
119
+ test("callExpression", () => {
120
+ assertParse("callExpression", "fn()", [[ops.builtin, "fn"], undefined]);
121
+ assertParse("callExpression", "foo.js(arg)", [
122
+ [ops.scope, "foo.js"],
123
+ [ops.scope, "arg"],
124
+ ]);
125
+ assertParse("callExpression", "fn(a, b)", [
126
+ [ops.builtin, "fn"],
127
+ [ops.scope, "a"],
128
+ [ops.scope, "b"],
129
+ ]);
130
+ assertParse("callExpression", "foo.js( a , b )", [
131
+ [ops.scope, "foo.js"],
132
+ [ops.scope, "a"],
133
+ [ops.scope, "b"],
134
+ ]);
135
+ assertParse("callExpression", "fn()(arg)", [
136
+ [[ops.builtin, "fn"], undefined],
137
+ [ops.scope, "arg"],
138
+ ]);
139
+ assertParse("callExpression", "tree/", [ops.unpack, [ops.scope, "tree/"]]);
140
+ assertParse("callExpression", "tree/foo/bar", [
141
+ ops.traverse,
142
+ [ops.scope, "tree/"],
143
+ [ops.literal, "foo/"],
144
+ [ops.literal, "bar"],
145
+ ]);
146
+ assertParse("callExpression", "tree/foo/bar/", [
147
+ ops.traverse,
148
+ [ops.scope, "tree/"],
149
+ [ops.literal, "foo/"],
150
+ [ops.literal, "bar/"],
151
+ ]);
152
+ assertParse("callExpression", "/foo/bar", [
153
+ ops.traverse,
154
+ [ops.rootDirectory, [ops.literal, "foo/"]],
155
+ [ops.literal, "bar"],
156
+ ]);
157
+ assertParse("callExpression", "foo.js()/key", [
158
+ ops.traverse,
159
+ [[ops.scope, "foo.js"], undefined],
160
+ [ops.literal, "key"],
161
+ ]);
162
+ assertParse("callExpression", "tree/key()", [
163
+ [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
164
+ undefined,
165
+ ]);
166
+ assertParse("callExpression", "(tree)/", [ops.unpack, [ops.scope, "tree"]]);
167
+ assertParse("callExpression", "fn()/key()", [
168
+ [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
169
+ undefined,
170
+ ]);
171
+ assertParse("callExpression", "(foo.js())('arg')", [
172
+ [[ops.scope, "foo.js"], undefined],
173
+ [ops.literal, "arg"],
174
+ ]);
175
+ assertParse("callExpression", "fn('a')('b')", [
176
+ [
177
+ [ops.builtin, "fn"],
178
+ [ops.literal, "a"],
179
+ ],
180
+ [ops.literal, "b"],
181
+ ]);
182
+ assertParse("callExpression", "(foo.js())(a, b)", [
183
+ [[ops.scope, "foo.js"], undefined],
184
+ [ops.scope, "a"],
185
+ [ops.scope, "b"],
186
+ ]);
187
+ assertParse("callExpression", "{ a: 1, b: 2}/b", [
188
+ ops.traverse,
189
+ [ops.object, ["a", [ops.literal, 1]], ["b", [ops.literal, 2]]],
190
+ [ops.literal, "b"],
191
+ ]);
192
+ assertParse("callExpression", "indent`hello`", [
193
+ [ops.builtin, "indent"],
194
+ [ops.literal, ["hello"]],
195
+ ]);
196
+ assertParse("callExpression", "fn.js`Hello, world.`", [
197
+ [ops.scope, "fn.js"],
198
+ [ops.literal, ["Hello, world."]],
199
+ ]);
200
+ assertParse("callExpression", "files:src/assets", [
201
+ ops.traverse,
202
+ [
203
+ [ops.builtin, "files:"],
204
+ [ops.literal, "src/"],
205
+ ],
206
+ [ops.literal, "assets"],
207
+ ]);
208
+ assertParse("callExpression", "new:(js:Date, '2025-01-01')", [
209
+ [ops.builtin, "new:"],
210
+ [
211
+ [ops.builtin, "js:"],
212
+ [ops.literal, "Date"],
213
+ ],
214
+ [ops.literal, "2025-01-01"],
215
+ ]);
216
+ assertParse("callExpression", "map(markdown, mdHtml)", [
217
+ [ops.builtin, "map"],
218
+ [ops.scope, "markdown"],
219
+ [ops.scope, "mdHtml"],
220
+ ]);
221
+ assertParse("callExpression", "package:@weborigami/dropbox/auth(creds)", [
222
+ [
223
+ ops.traverse,
224
+ [
225
+ [ops.builtin, "package:"],
226
+ [ops.literal, "@weborigami/"],
227
+ ],
228
+ [ops.literal, "dropbox/"],
229
+ [ops.literal, "auth"],
230
+ ],
231
+ [ops.scope, "creds"],
232
+ ]);
233
+ });
234
+
235
+ test("commaExpression", () => {
236
+ assertParse("commaExpression", "1", [ops.literal, 1]);
237
+ assertParse("commaExpression", "a, b, c", [
238
+ ops.comma,
239
+ [ops.scope, "a"],
240
+ [ops.scope, "b"],
241
+ [ops.scope, "c"],
242
+ ]);
243
+ });
244
+
245
+ test("conditionalExpression", () => {
246
+ assertParse("conditionalExpression", "1", [ops.literal, 1]);
247
+ assertParse("conditionalExpression", "true ? 1 : 0", [
248
+ ops.conditional,
249
+ [ops.scope, "true"],
250
+ [ops.lambda, [], [ops.literal, "1"]],
251
+ [ops.lambda, [], [ops.literal, "0"]],
252
+ ]);
253
+ assertParse("conditionalExpression", "false ? () => 1 : 0", [
254
+ ops.conditional,
255
+ [ops.scope, "false"],
256
+ [ops.lambda, [], [ops.lambda, [], [ops.literal, "1"]]],
257
+ [ops.lambda, [], [ops.literal, "0"]],
258
+ ]);
259
+ assertParse("conditionalExpression", "false ? =1 : 0", [
260
+ ops.conditional,
261
+ [ops.scope, "false"],
262
+ [ops.lambda, [], [ops.lambda, ["_"], [ops.literal, "1"]]],
263
+ [ops.lambda, [], [ops.literal, "0"]],
264
+ ]);
265
+ });
266
+
267
+ test("equalityExpression", () => {
268
+ assertParse("equalityExpression", "1 === 1", [
269
+ ops.strictEqual,
270
+ [ops.literal, 1],
271
+ [ops.literal, 1],
272
+ ]);
273
+ assertParse("equalityExpression", "a === b === c", [
274
+ ops.strictEqual,
275
+ [ops.strictEqual, [undetermined, "a"], [undetermined, "b"]],
276
+ [undetermined, "c"],
277
+ ]);
278
+ assertParse("equalityExpression", "1 !== 1", [
279
+ ops.notStrictEqual,
280
+ [ops.literal, 1],
281
+ [ops.literal, 1],
282
+ ]);
283
+ });
284
+
285
+ test("exponentiationExpression", () => {
286
+ assertParse("exponentiationExpression", "2 ** 2 ** 3", [
287
+ ops.exponentiation,
288
+ [ops.literal, 2],
289
+ [ops.exponentiation, [ops.literal, 2], [ops.literal, 3]],
67
290
  ]);
68
291
  });
69
292
 
@@ -114,7 +337,7 @@ describe("Origami parser", () => {
114
337
 
115
338
  // Single slash at start of something = absolute file path
116
339
  assertParse("expression", "/path", [
117
- [ops.filesRoot],
340
+ ops.rootDirectory,
118
341
  [ops.literal, "path"],
119
342
  ]);
120
343
 
@@ -128,136 +351,108 @@ describe("Origami parser", () => {
128
351
  ],
129
352
  ]);
130
353
 
354
+ // Slash on its own is the root folder
355
+ assertParse("expression", "keys /", [
356
+ [ops.builtin, "keys"],
357
+ [ops.rootDirectory],
358
+ ]);
359
+
131
360
  assertParse("expression", "'Hello' -> test.orit", [
132
361
  [ops.scope, "test.orit"],
133
362
  [ops.literal, "Hello"],
134
363
  ]);
135
- });
136
-
137
- test("functionComposition", () => {
138
- assertParse("functionComposition", "fn()", [
364
+ assertParse("expression", "obj.json", [ops.scope, "obj.json"]);
365
+ assertParse("expression", "(fn a, b, c)", [
139
366
  [ops.builtin, "fn"],
140
- undefined,
367
+ [undetermined, "a"],
368
+ [undetermined, "b"],
369
+ [undetermined, "c"],
141
370
  ]);
142
- assertParse("functionComposition", "foo.js(arg)", [
143
- [ops.scope, "foo.js"],
144
- [ops.scope, "arg"],
145
- ]);
146
- assertParse("functionComposition", "fn(a, b)", [
147
- [ops.builtin, "fn"],
148
- [ops.scope, "a"],
149
- [ops.scope, "b"],
150
- ]);
151
- assertParse("functionComposition", "foo.js( a , b )", [
152
- [ops.scope, "foo.js"],
153
- [ops.scope, "a"],
154
- [ops.scope, "b"],
371
+ assertParse("expression", "foo.bar('hello', 'world')", [
372
+ [ops.scope, "foo.bar"],
373
+ [ops.literal, "hello"],
374
+ [ops.literal, "world"],
155
375
  ]);
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,
376
+ assertParse("expression", "(key)('a')", [
377
+ [ops.scope, "key"],
378
+ [ops.literal, "a"],
168
379
  ]);
169
- assertParse("functionComposition", "(tree)/", [
170
- ops.unpack,
171
- [ops.scope, "tree"],
380
+ assertParse("expression", "1", [ops.literal, 1]);
381
+ assertParse("expression", "{ a: 1, b: 2 }", [
382
+ ops.object,
383
+ ["a", [ops.literal, 1]],
384
+ ["b", [ops.literal, 2]],
172
385
  ]);
173
- assertParse("functionComposition", "fn()/key()", [
174
- [ops.traverse, [[ops.builtin, "fn"], undefined], [ops.literal, "key"]],
175
- undefined,
386
+ assertParse("expression", "serve { index.html: 'hello' }", [
387
+ [ops.builtin, "serve"],
388
+ [ops.object, ["index.html", [ops.literal, "hello"]]],
176
389
  ]);
177
- assertParse("functionComposition", "(foo.js())('arg')", [
178
- [[ops.scope, "foo.js"], undefined],
179
- [ops.literal, "arg"],
390
+ assertParse("expression", "fn =`x`", [
391
+ [ops.builtin, "fn"],
392
+ [ops.lambda, ["_"], [ops.template, [ops.literal, ["x"]]]],
180
393
  ]);
181
- assertParse("functionComposition", "fn('a')('b')", [
394
+ assertParse("expression", "copy app.js(formulas), files:snapshot", [
395
+ [ops.builtin, "copy"],
182
396
  [
183
- [ops.builtin, "fn"],
184
- [ops.literal, "a"],
397
+ [ops.scope, "app.js"],
398
+ [ops.scope, "formulas"],
185
399
  ],
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
400
  [
210
- [ops.builtin, "a"],
211
- [ops.scope, "b"],
401
+ [ops.builtin, "files:"],
402
+ [ops.literal, "snapshot"],
212
403
  ],
213
- [ops.scope, "c"],
214
404
  ]);
215
- assertParse("functionComposition", "foo.js bar.ori 'arg'", [
216
- [ops.scope, "foo.js"],
405
+ assertParse("expression", "map =`<li>${_}</li>`", [
406
+ [ops.builtin, "map"],
217
407
  [
218
- [ops.scope, "bar.ori"],
219
- [ops.literal, "arg"],
408
+ ops.lambda,
409
+ ["_"],
410
+ [
411
+ ops.template,
412
+ [ops.literal, ["<li>", "</li>"]],
413
+ [ops.concat, [ops.scope, "_"]],
414
+ ],
220
415
  ],
221
416
  ]);
222
- assertParse("functionComposition", "(fn()) 'arg'", [
223
- [[ops.builtin, "fn"], undefined],
224
- [ops.literal, "arg"],
417
+ assertParse("expression", `https://example.com/about/`, [
418
+ [ops.builtin, "https:"],
419
+ [ops.literal, "example.com/"],
420
+ [ops.literal, "about/"],
225
421
  ]);
226
- assertParse("functionComposition", "tree/key arg", [
227
- [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
228
- [ops.scope, "arg"],
422
+ assertParse("expression", "tag`Hello, ${name}!`", [
423
+ [ops.builtin, "tag"],
424
+ [ops.literal, ["Hello, ", "!"]],
425
+ [ops.concat, [ops.scope, "name"]],
229
426
  ]);
230
- assertParse("functionComposition", "new:(js:Date, '2025-01-01')", [
231
- [ops.builtin, "new:"],
427
+ assertParse("expression", "(post, slug) => fn.js(post, slug)", [
428
+ ops.lambda,
429
+ ["post", "slug"],
232
430
  [
233
- [ops.builtin, "js:"],
234
- [ops.literal, "Date"],
431
+ [ops.scope, "fn.js"],
432
+ [ops.scope, "post"],
433
+ [ops.scope, "slug"],
235
434
  ],
236
- [ops.literal, "2025-01-01"],
237
435
  ]);
238
- assertParse("functionComposition", "map(markdown, mdHtml)", [
239
- [ops.builtin, "map"],
240
- [ops.scope, "markdown"],
241
- [ops.scope, "mdHtml"],
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
436
 
258
- test("functionReference", () => {
259
- assertParse("functionReference", "json", [ops.builtin, "json"]);
260
- assertParse("functionReference", "greet.js", [ops.scope, "greet.js"]);
437
+ // Verify parser treatment of identifiers containing operators
438
+ assertParse("expression", "a + b", [
439
+ ops.addition,
440
+ [undetermined, "a"],
441
+ [undetermined, "b"],
442
+ ]);
443
+ assertParse("expression", "a+b", [ops.scope, "a+b"]);
444
+ assertParse("expression", "a - b", [
445
+ ops.subtraction,
446
+ [undetermined, "a"],
447
+ [undetermined, "b"],
448
+ ]);
449
+ assertParse("expression", "a-b", [ops.scope, "a-b"]);
450
+ assertParse("expression", "a&b", [ops.scope, "a&b"]);
451
+ assertParse("expression", "a & b", [
452
+ ops.bitwiseAnd,
453
+ [undetermined, "a"],
454
+ [undetermined, "b"],
455
+ ]);
261
456
  });
262
457
 
263
458
  test("group", () => {
@@ -270,15 +465,14 @@ describe("Origami parser", () => {
270
465
  ]);
271
466
  });
272
467
 
273
- test("homeTree", () => {
274
- assertParse("homeTree", "~", [ops.homeTree]);
468
+ test("homeDirectory", () => {
469
+ assertParse("homeDirectory", "~", [ops.homeDirectory]);
275
470
  });
276
471
 
277
472
  test("host", () => {
278
473
  assertParse("host", "abc", [ops.literal, "abc"]);
279
474
  assertParse("host", "abc:123", [ops.literal, "abc:123"]);
280
475
  assertParse("host", "foo\\ bar", [ops.literal, "foo bar"]);
281
- assertParse("host", "example.com/", [ops.literal, "example.com/"]);
282
476
  });
283
477
 
284
478
  test("identifier", () => {
@@ -288,37 +482,41 @@ describe("Origami parser", () => {
288
482
  assertParse("identifier", "x-y-z", "x-y-z", false);
289
483
  });
290
484
 
291
- test("lambda", () => {
292
- assertParse("lambda", "=message", [
293
- ops.lambda,
294
- ["_"],
295
- [ops.scope, "message"],
485
+ test("implicitParenthesesCallExpression", () => {
486
+ assertParse("implicitParenthesesCallExpression", "fn arg", [
487
+ [ops.builtin, "fn"],
488
+ [undetermined, "arg"],
296
489
  ]);
297
- assertParse("lambda", "=`Hello, ${name}.`", [
298
- ops.lambda,
299
- ["_"],
490
+ assertParse("implicitParenthesesCallExpression", "page.ori 'a', 'b'", [
491
+ [ops.scope, "page.ori"],
492
+ [ops.literal, "a"],
493
+ [ops.literal, "b"],
494
+ ]);
495
+ assertParse("implicitParenthesesCallExpression", "fn a(b), c", [
496
+ [ops.builtin, "fn"],
300
497
  [
301
- ops.template,
302
- [ops.literal, ["Hello, ", "."]],
303
- [ops.concat, [ops.scope, "name"]],
498
+ [ops.builtin, "a"],
499
+ [ops.scope, "b"],
304
500
  ],
501
+ [undetermined, "c"],
305
502
  ]);
306
- assertParse("lambda", "=indent`hello`", [
307
- ops.lambda,
308
- ["_"],
503
+ assertParse("implicitParenthesesCallExpression", "(fn()) 'arg'", [
504
+ [[ops.builtin, "fn"], undefined],
505
+ [ops.literal, "arg"],
506
+ ]);
507
+ assertParse("implicitParenthesesCallExpression", "tree/key arg", [
508
+ [ops.traverse, [ops.scope, "tree/"], [ops.literal, "key"]],
509
+ [undetermined, "arg"],
510
+ ]);
511
+ assertParse("implicitParenthesesCallExpression", "foo.js bar.ori 'arg'", [
512
+ [ops.scope, "foo.js"],
309
513
  [
310
- [ops.builtin, "indent"],
311
- [ops.literal, ["hello"]],
514
+ [ops.scope, "bar.ori"],
515
+ [ops.literal, "arg"],
312
516
  ],
313
517
  ]);
314
518
  });
315
519
 
316
- test("leadingSlashPath", () => {
317
- assertParse("leadingSlashPath", "/", []);
318
- assertParse("leadingSlashPath", "/tree", [[ops.literal, "tree"]]);
319
- assertParse("leadingSlashPath", "/tree/", [[ops.literal, "tree/"]]);
320
- });
321
-
322
520
  test("list", () => {
323
521
  assertParse("list", "1", [[ops.literal, 1]]);
324
522
  assertParse("list", "1,2,3", [
@@ -348,76 +546,106 @@ describe("Origami parser", () => {
348
546
  ]);
349
547
  });
350
548
 
549
+ test("logicalAndExpression", () => {
550
+ assertParse("logicalAndExpression", "true && false", [
551
+ ops.logicalAnd,
552
+ [ops.scope, "true"],
553
+ [ops.lambda, [], [undetermined, "false"]],
554
+ ]);
555
+ });
556
+
557
+ test("logicalOrExpression", () => {
558
+ assertParse("logicalOrExpression", "1 || 0", [
559
+ ops.logicalOr,
560
+ [ops.literal, 1],
561
+ [ops.literal, 0],
562
+ ]);
563
+ assertParse("logicalOrExpression", "false || false || true", [
564
+ ops.logicalOr,
565
+ [ops.scope, "false"],
566
+ [ops.lambda, [], [undetermined, "false"]],
567
+ [ops.lambda, [], [undetermined, "true"]],
568
+ ]);
569
+ assertParse("logicalOrExpression", "1 || 2 && 0", [
570
+ ops.logicalOr,
571
+ [ops.literal, 1],
572
+ [ops.lambda, [], [ops.logicalAnd, [ops.literal, 2], [ops.literal, 0]]],
573
+ ]);
574
+ });
575
+
351
576
  test("multiLineComment", () => {
352
577
  assertParse("multiLineComment", "/*\nHello, world!\n*/", null, false);
353
578
  });
354
579
 
580
+ test("multiplicativeExpression", () => {
581
+ assertParse("multiplicativeExpression", "3 * 4", [
582
+ ops.multiplication,
583
+ [ops.literal, 3],
584
+ [ops.literal, 4],
585
+ ]);
586
+ assertParse("multiplicativeExpression", "5 / 2", [
587
+ ops.division,
588
+ [ops.literal, 5],
589
+ [ops.literal, 2],
590
+ ]);
591
+ assertParse("multiplicativeExpression", "6 % 5", [
592
+ ops.remainder,
593
+ [ops.literal, 6],
594
+ [ops.literal, 5],
595
+ ]);
596
+ });
597
+
355
598
  test("namespace", () => {
356
599
  assertParse("namespace", "js:", [ops.builtin, "js:"]);
357
600
  });
358
601
 
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"],
602
+ test("nullishCoalescingExpression", () => {
603
+ assertParse("nullishCoalescingExpression", "a ?? b", [
604
+ ops.nullishCoalescing,
605
+ [ops.scope, "a"],
606
+ [ops.lambda, [], [undetermined, "b"]],
376
607
  ]);
377
- assertParse("namespacePath", "https://example.com/foo/", [
378
- [ops.builtin, "https:"],
379
- [ops.literal, "example.com/"],
380
- [ops.literal, "foo/"],
608
+ assertParse("nullishCoalescingExpression", "a ?? b ?? c", [
609
+ ops.nullishCoalescing,
610
+ [ops.scope, "a"],
611
+ [ops.lambda, [], [undetermined, "b"]],
612
+ [ops.lambda, [], [undetermined, "c"]],
381
613
  ]);
382
614
  });
383
615
 
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]);
616
+ test("numericLiteral", () => {
617
+ assertParse("numericLiteral", "123", [ops.literal, 123]);
618
+ assertParse("numericLiteral", ".5", [ops.literal, 0.5]);
619
+ assertParse("numericLiteral", "123.45", [ops.literal, 123.45]);
392
620
  });
393
621
 
394
- test("object", () => {
395
- assertParse("object", "{}", [ops.object]);
396
- assertParse("object", "{ a: 1, b }", [
622
+ test("objectLiteral", () => {
623
+ assertParse("objectLiteral", "{}", [ops.object]);
624
+ assertParse("objectLiteral", "{ a: 1, b }", [
397
625
  ops.object,
398
626
  ["a", [ops.literal, 1]],
399
627
  ["b", [ops.inherited, "b"]],
400
628
  ]);
401
- assertParse("object", "{ sub: { a: 1 } }", [
629
+ assertParse("objectLiteral", "{ sub: { a: 1 } }", [
402
630
  ops.object,
403
631
  ["sub", [ops.object, ["a", [ops.literal, 1]]]],
404
632
  ]);
405
- assertParse("object", "{ sub: { a/: 1 } }", [
633
+ assertParse("objectLiteral", "{ sub: { a/: 1 } }", [
406
634
  ops.object,
407
635
  ["sub", [ops.object, ["a/", [ops.literal, 1]]]],
408
636
  ]);
409
- assertParse("object", `{ "a": 1, "b": 2 }`, [
637
+ assertParse("objectLiteral", `{ "a": 1, "b": 2 }`, [
410
638
  ops.object,
411
639
  ["a", [ops.literal, 1]],
412
640
  ["b", [ops.literal, 2]],
413
641
  ]);
414
- assertParse("object", "{ a = b, b = 2 }", [
642
+ assertParse("objectLiteral", "{ a = b, b = 2 }", [
415
643
  ops.object,
416
644
  ["a", [ops.getter, [ops.scope, "b"]]],
417
645
  ["b", [ops.literal, 2]],
418
646
  ]);
419
647
  assertParse(
420
- "object",
648
+ "objectLiteral",
421
649
  `{
422
650
  a = b
423
651
  b = 2
@@ -428,22 +656,22 @@ describe("Origami parser", () => {
428
656
  ["b", [ops.literal, 2]],
429
657
  ]
430
658
  );
431
- assertParse("object", "{ a: { b: 1 } }", [
659
+ assertParse("objectLiteral", "{ a: { b: 1 } }", [
432
660
  ops.object,
433
661
  ["a", [ops.object, ["b", [ops.literal, 1]]]],
434
662
  ]);
435
- assertParse("object", "{ a: { b = 1 } }", [
663
+ assertParse("objectLiteral", "{ a: { b = 1 } }", [
436
664
  ops.object,
437
665
  ["a", [ops.object, ["b", [ops.literal, 1]]]],
438
666
  ]);
439
- assertParse("object", "{ a: { b = fn() } }", [
667
+ assertParse("objectLiteral", "{ a: { b = fn() } }", [
440
668
  ops.object,
441
669
  [
442
670
  "a/",
443
671
  [ops.object, ["b", [ops.getter, [[ops.builtin, "fn"], undefined]]]],
444
672
  ],
445
673
  ]);
446
- assertParse("object", "{ x = fn.js('a') }", [
674
+ assertParse("objectLiteral", "{ x = fn.js('a') }", [
447
675
  ops.object,
448
676
  [
449
677
  "x",
@@ -456,12 +684,12 @@ describe("Origami parser", () => {
456
684
  ],
457
685
  ],
458
686
  ]);
459
- assertParse("object", "{ a: 1, ...b }", [
687
+ assertParse("objectLiteral", "{ a: 1, ...b }", [
460
688
  ops.merge,
461
689
  [ops.object, ["a", [ops.literal, 1]]],
462
- [ops.scope, "b"],
690
+ [undetermined, "b"],
463
691
  ]);
464
- assertParse("object", "{ (a): 1 }", [
692
+ assertParse("objectLiteral", "{ (a): 1 }", [
465
693
  ops.object,
466
694
  ["(a)", [ops.literal, 1]],
467
695
  ]);
@@ -523,40 +751,9 @@ describe("Origami parser", () => {
523
751
  assertParse("objectPublicKey", "foo\\ bar", "foo bar", false);
524
752
  });
525
753
 
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)", [
754
+ test("parenthesesArguments", () => {
755
+ assertParse("parenthesesArguments", "()", [undefined]);
756
+ assertParse("parenthesesArguments", "(a, b, c)", [
560
757
  [ops.scope, "a"],
561
758
  [ops.scope, "b"],
562
759
  [ops.scope, "c"],
@@ -564,47 +761,63 @@ describe("Origami parser", () => {
564
761
  });
565
762
 
566
763
  test("path", () => {
567
- assertParse("path", "tree/", [[ops.literal, "tree/"]]);
568
- assertParse("path", "month/12", [
764
+ assertParse("path", "/tree/", [[ops.literal, "tree/"]]);
765
+ assertParse("path", "/month/12", [
569
766
  [ops.literal, "month/"],
570
767
  [ops.literal, "12"],
571
768
  ]);
572
- assertParse("path", "tree/foo/bar", [
769
+ assertParse("path", "/tree/foo/bar", [
573
770
  [ops.literal, "tree/"],
574
771
  [ops.literal, "foo/"],
575
772
  [ops.literal, "bar"],
576
773
  ]);
577
- assertParse("path", "a///b", [
774
+ assertParse("path", "/a///b", [
578
775
  [ops.literal, "a/"],
579
776
  [ops.literal, "b"],
580
777
  ]);
581
778
  });
582
779
 
583
- test("pipeline", () => {
584
- assertParse("pipeline", "foo", [ops.scope, "foo"]);
585
- assertParse("pipeline", "a -> b", [
780
+ test("pathArguments", () => {
781
+ assertParse("pathArguments", "/", [ops.traverse]);
782
+ assertParse("pathArguments", "/tree", [
783
+ ops.traverse,
784
+ [ops.literal, "tree"],
785
+ ]);
786
+ assertParse("pathArguments", "/tree/", [
787
+ ops.traverse,
788
+ [ops.literal, "tree/"],
789
+ ]);
790
+ });
791
+
792
+ test("pipelineExpression", () => {
793
+ assertParse("pipelineExpression", "foo", [ops.scope, "foo"]);
794
+ assertParse("pipelineExpression", "a -> b", [
586
795
  [ops.builtin, "b"],
587
796
  [ops.scope, "a"],
588
797
  ]);
589
- assertParse("pipeline", "input → one.js → two.js", [
798
+ assertParse("pipelineExpression", "input → one.js → two.js", [
590
799
  [ops.scope, "two.js"],
591
800
  [
592
801
  [ops.scope, "one.js"],
593
802
  [ops.scope, "input"],
594
803
  ],
595
804
  ]);
596
- assertParse("pipeline", "fn a -> b", [
805
+ assertParse("pipelineExpression", "fn a -> b", [
597
806
  [ops.builtin, "b"],
598
807
  [
599
808
  [ops.builtin, "fn"],
600
- [ops.scope, "a"],
809
+ [undetermined, "a"],
601
810
  ],
602
811
  ]);
603
812
  });
604
813
 
605
- test("pipelineStep", () => {
606
- assertParse("pipelineStep", "foo", [ops.builtin, "foo"]);
607
- assertParse("pipelineStep", "=_", [ops.lambda, ["_"], [ops.scope, "_"]]);
814
+ test("primary", () => {
815
+ assertParse("primary", "foo.js", [ops.scope, "foo.js"]);
816
+ assertParse("primary", "[1, 2]", [
817
+ ops.array,
818
+ [ops.literal, 1],
819
+ [ops.literal, 2],
820
+ ]);
608
821
  });
609
822
 
610
823
  test("program", () => {
@@ -618,23 +831,114 @@ describe("Origami parser", () => {
618
831
  );
619
832
  });
620
833
 
834
+ test("protocolExpression", () => {
835
+ assertParse("protocolExpression", "foo://bar", [
836
+ [ops.builtin, "foo:"],
837
+ [ops.literal, "bar"],
838
+ ]);
839
+ assertParse("protocolExpression", "http://example.com", [
840
+ [ops.builtin, "http:"],
841
+ [ops.literal, "example.com"],
842
+ ]);
843
+ assertParse("protocolExpression", "https://example.com/about/", [
844
+ [ops.builtin, "https:"],
845
+ [ops.literal, "example.com/"],
846
+ [ops.literal, "about/"],
847
+ ]);
848
+ assertParse("protocolExpression", "https://example.com/about/index.html", [
849
+ [ops.builtin, "https:"],
850
+ [ops.literal, "example.com/"],
851
+ [ops.literal, "about/"],
852
+ [ops.literal, "index.html"],
853
+ ]);
854
+ assertParse("protocolExpression", "http://localhost:5000/foo", [
855
+ [ops.builtin, "http:"],
856
+ [ops.literal, "localhost:5000/"],
857
+ [ops.literal, "foo"],
858
+ ]);
859
+ });
860
+
861
+ test("qualifiedReference", () => {
862
+ assertParse("qualifiedReference", "js:Date", [
863
+ [ops.builtin, "js:"],
864
+ [ops.literal, "Date"],
865
+ ]);
866
+ });
867
+
868
+ test("relationalExpression", () => {
869
+ assertParse("relationalExpression", "1 < 2", [
870
+ ops.lessThan,
871
+ [ops.literal, 1],
872
+ [ops.literal, 2],
873
+ ]);
874
+ assertParse("relationalExpression", "3 > 4", [
875
+ ops.greaterThan,
876
+ [ops.literal, 3],
877
+ [ops.literal, 4],
878
+ ]);
879
+ assertParse("relationalExpression", "5 <= 6", [
880
+ ops.lessThanOrEqual,
881
+ [ops.literal, 5],
882
+ [ops.literal, 6],
883
+ ]);
884
+ assertParse("relationalExpression", "7 >= 8", [
885
+ ops.greaterThanOrEqual,
886
+ [ops.literal, 7],
887
+ [ops.literal, 8],
888
+ ]);
889
+ });
890
+
891
+ test("rootDirectory", () => {
892
+ assertParse("rootDirectory", "/", [ops.rootDirectory]);
893
+ });
894
+
621
895
  test("scopeReference", () => {
622
- assertParse("scopeReference", "x", [ops.scope, "x"]);
896
+ assertParse("scopeReference", "keys", [undetermined, "keys"]);
897
+ assertParse("scopeReference", "greet.js", [ops.scope, "greet.js"]);
898
+ // scopeReference checks whether a slash follows; hard to test in isolation
899
+ // assertParse("scopeReference", "markdown/", [ops.scope, "markdown"]);
623
900
  });
624
901
 
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"],
902
+ test("shiftExpression", () => {
903
+ assertParse("shiftExpression", "1 << 2", [
904
+ ops.shiftLeft,
905
+ [ops.literal, 1],
906
+ [ops.literal, 2],
632
907
  ]);
633
- assertParse("scopeTraverse", "tree/foo/bar/", [
634
- ops.traverse,
635
- [ops.scope, "tree/"],
636
- [ops.literal, "foo/"],
637
- [ops.literal, "bar/"],
908
+ assertParse("shiftExpression", "3 >> 4", [
909
+ ops.shiftRightSigned,
910
+ [ops.literal, 3],
911
+ [ops.literal, 4],
912
+ ]);
913
+ assertParse("shiftExpression", "5 >>> 6", [
914
+ ops.shiftRightUnsigned,
915
+ [ops.literal, 5],
916
+ [ops.literal, 6],
917
+ ]);
918
+ });
919
+
920
+ test("shorthandFunction", () => {
921
+ assertParse("shorthandFunction", "=message", [
922
+ ops.lambda,
923
+ ["_"],
924
+ [undetermined, "message"],
925
+ ]);
926
+ assertParse("shorthandFunction", "=`Hello, ${name}.`", [
927
+ ops.lambda,
928
+ ["_"],
929
+ [
930
+ ops.template,
931
+ [ops.literal, ["Hello, ", "."]],
932
+ [ops.concat, [ops.scope, "name"]],
933
+ ],
934
+ ]);
935
+ assertParse("shorthandFunction", "=indent`hello`", [
936
+ ops.lambda,
937
+ ["_"],
938
+ [
939
+ [ops.builtin, "indent"],
940
+ [ops.literal, ["hello"]],
941
+ ],
638
942
  ]);
639
943
  });
640
944
 
@@ -643,32 +947,21 @@ describe("Origami parser", () => {
643
947
  });
644
948
 
645
949
  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"`, [
659
- ops.literal,
660
- "\0\b\f\n\r\t\v",
661
- ]);
950
+ assertParse("spread", "...a", [ops.spread, [undetermined, "a"]]);
951
+ assertParse("spread", "…a", [ops.spread, [undetermined, "a"]]);
662
952
  });
663
953
 
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."]],
954
+ test("stringLiteral", () => {
955
+ assertParse("stringLiteral", '"foo"', [ops.literal, "foo"]);
956
+ assertParse("stringLiteral", "'bar'", [ops.literal, "bar"]);
957
+ assertParse("stringLiteral", '"foo bar"', [ops.literal, "foo bar"]);
958
+ assertParse("stringLiteral", "'bar baz'", [ops.literal, "bar baz"]);
959
+ assertParse("stringLiteral", `"foo\\"s bar"`, [ops.literal, `foo"s bar`]);
960
+ assertParse("stringLiteral", `'bar\\'s baz'`, [ops.literal, `bar's baz`]);
961
+ assertParse("stringLiteral", `«string»`, [ops.literal, "string"]);
962
+ assertParse("stringLiteral", `"\\0\\b\\f\\n\\r\\t\\v"`, [
963
+ ops.literal,
964
+ "\0\b\f\n\r\t\v",
672
965
  ]);
673
966
  });
674
967
 
@@ -741,80 +1034,16 @@ describe("Origami parser", () => {
741
1034
  false
742
1035
  );
743
1036
  });
744
- });
745
1037
 
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
- ]);
1038
+ test("unaryExpression", () => {
1039
+ assertParse("unaryExpression", "!true", [
1040
+ ops.logicalNot,
1041
+ [undetermined, "true"],
1042
+ ]);
1043
+ assertParse("unaryExpression", "+1", [ops.unaryPlus, [ops.literal, 1]]);
1044
+ assertParse("unaryExpression", "-2", [ops.unaryMinus, [ops.literal, 2]]);
1045
+ assertParse("unaryExpression", "~3", [ops.bitwiseNot, [ops.literal, 3]]);
1046
+ });
818
1047
  });
819
1048
 
820
1049
  function assertParse(startRule, source, expected, checkLocation = true) {