@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.
@@ -2,19 +2,29 @@
2
2
  //
3
3
  // Origami language parser
4
4
  //
5
+ // This generally follows the pattern of the JavaScript expression grammar at
6
+ // https://github.com/pegjs/pegjs/blob/master/examples/javascript.pegjs. Like
7
+ // that parser, this one uses the ECMAScript grammar terms where relevant.
8
+ //
5
9
  // Generate the parser via `npm build`.
10
+ //
6
11
  // @ts-nocheck
7
12
  //
8
13
 
9
14
  import * as ops from "../runtime/ops.js";
10
15
  import {
11
16
  annotate,
17
+ downgradeReference,
12
18
  makeArray,
13
- makeFunctionCall,
19
+ makeBinaryOperation,
20
+ makeCall,
21
+ makeDeferredArguments,
14
22
  makeObject,
15
23
  makePipeline,
16
24
  makeProperty,
17
- makeTemplate
25
+ makeReference,
26
+ makeTemplate,
27
+ makeUnaryOperation
18
28
  } from "./parserHelpers.js";
19
29
 
20
30
  }}
@@ -25,21 +35,21 @@ __
25
35
  return null;
26
36
  }
27
37
 
28
- // A filesystem path that begins with a slash: `/foo/bar`
29
- // We take care to avoid treating two consecutive leading slashes as a path;
30
- // that starts a comment.
31
- absoluteFilePath "absolute file path"
32
- = !"//" path:leadingSlashPath {
33
- return annotate([[ops.filesRoot], ...path], location());
38
+ additiveExpression
39
+ = head:multiplicativeExpression tail:(__ @additiveOperator __ @multiplicativeExpression)* {
40
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
34
41
  }
35
42
 
36
- args "function arguments"
37
- = parensArgs
38
- / path:leadingSlashPath {
39
- return annotate([ops.traverse, ...path], location());
40
- }
43
+ additiveOperator
44
+ = "+"
45
+ / "-"
41
46
 
42
- array "array"
47
+ arguments "function arguments"
48
+ = parenthesesArguments
49
+ / pathArguments
50
+ / templateLiteral
51
+
52
+ arrayLiteral "array"
43
53
  = "[" __ entries:arrayEntries? __ closingBracket {
44
54
  return annotate(makeArray(entries ?? []), location());
45
55
  }
@@ -52,19 +62,51 @@ arrayEntries
52
62
 
53
63
  arrayEntry
54
64
  = spread
55
- / pipeline
56
-
57
- // Something that can be called. This is more restrictive than the `value`
58
- // parser; it doesn't accept regular function calls.
59
- callTarget "function call"
60
- = absoluteFilePath
61
- / scopeTraverse
62
- / array
63
- / object
64
- / group
65
- / namespacePath
66
- / namespace
67
- / functionReference
65
+ / pipelineExpression
66
+ // JavaScript treats a missing value as `undefined`
67
+ / __ !"]" {
68
+ return annotate([ops.literal, undefined], location());
69
+ }
70
+
71
+ arrowFunction
72
+ = "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipelineExpression {
73
+ return annotate([ops.lambda, parameters ?? [], pipeline], location());
74
+ }
75
+ / identifier:identifier __ doubleArrow __ pipeline:pipelineExpression {
76
+ return annotate([ops.lambda, [identifier], pipeline], location());
77
+ }
78
+ / conditionalExpression
79
+
80
+ bitwiseAndExpression
81
+ = head:equalityExpression tail:(__ @bitwiseAndOperator __ @equalityExpression)* {
82
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
83
+ }
84
+
85
+ bitwiseAndOperator
86
+ = @"&" !"&"
87
+
88
+ bitwiseOrExpression
89
+ = head:bitwiseXorExpression tail:(__ @bitwiseOrOperator __ @bitwiseXorExpression)* {
90
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
91
+ }
92
+
93
+ bitwiseOrOperator
94
+ = @"|" !"|"
95
+
96
+ bitwiseXorExpression
97
+ = head:bitwiseAndExpression tail:(__ @bitwiseXorOperator __ @bitwiseAndExpression)* {
98
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
99
+ }
100
+
101
+ bitwiseXorOperator
102
+ = "^"
103
+
104
+ // A function call: `fn(arg)`, possibly part of a chain of function calls, like
105
+ // `fn(arg1)(arg2)(arg3)`.
106
+ callExpression "function call"
107
+ = head:protocolExpression tail:arguments* {
108
+ return annotate(tail.reduce(makeCall, head), location());
109
+ }
68
110
 
69
111
  // Required closing curly brace. We use this for the `object` term: if the
70
112
  // parser sees a left curly brace, here we must see a right curly brace.
@@ -84,17 +126,40 @@ closingBracket
84
126
  // Required closing parenthesis. We use this for the `group` term: it's the last
85
127
  // term in the `step` parser that starts with a parenthesis, so if that parser
86
128
  // sees a left parenthesis, here we must see a right parenthesis.
87
- closingParen
129
+ closingParenthesis
88
130
  = ")"
89
131
  / .? {
90
132
  error("Expected right parenthesis");
91
133
  }
92
134
 
135
+ // A comma-separated list of expressions: `x, y, z`
136
+ commaExpression
137
+ // The commas are required, but the list can have a single item.
138
+ = list:pipelineExpression|1.., __ "," __ | {
139
+ return list.length === 1
140
+ ? list[0]
141
+ : annotate([ops.comma, ...list], location());
142
+ }
143
+
93
144
  // A single line comment
94
145
  comment "comment"
95
146
  = multiLineComment
96
147
  / singleLineComment
97
148
 
149
+ conditionalExpression
150
+ = condition:logicalOrExpression __
151
+ "?" __ truthy:pipelineExpression __
152
+ ":" __ falsy:pipelineExpression
153
+ {
154
+ return annotate([
155
+ ops.conditional,
156
+ downgradeReference(condition),
157
+ [ops.lambda, [], downgradeReference(truthy)],
158
+ [ops.lambda, [], downgradeReference(falsy)]
159
+ ], location());
160
+ }
161
+ / logicalOrExpression
162
+
98
163
  digits
99
164
  = @[0-9]+
100
165
 
@@ -108,13 +173,18 @@ doubleQuoteString "double quote string"
108
173
  doubleQuoteStringChar
109
174
  = !('"' / newLine) @textChar
110
175
 
111
- // Path that follows a builtin reference in a URL: `//example.com/index.html`
112
- doubleSlashPath
113
- = "//" host:host path:path? {
114
- return annotate([host, ...(path ?? [])], location());
176
+ ellipsis = "..." / "…" // Unicode ellipsis
177
+
178
+ equalityExpression
179
+ = head:relationalExpression tail:(__ @equalityOperator __ @relationalExpression)* {
180
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
115
181
  }
116
182
 
117
- ellipsis = "..." / "…" // Unicode ellipsis
183
+ equalityOperator
184
+ = "==="
185
+ / "!=="
186
+ / "=="
187
+ / "!="
118
188
 
119
189
  escapedChar "backslash-escaped character"
120
190
  = "\\0" { return "\0"; }
@@ -126,50 +196,26 @@ escapedChar "backslash-escaped character"
126
196
  / "\\v" { return "\v"; }
127
197
  / "\\" @.
128
198
 
199
+ exponentiationExpression
200
+ = left:unaryExpression __ "**" __ right:exponentiationExpression {
201
+ return annotate([ops.exponentiation, left, right], location());
202
+ }
203
+ / unaryExpression
204
+
129
205
  // A top-level expression, possibly with leading/trailing whitespace
130
206
  expression
131
- = __ @pipeline __
207
+ = __ @commaExpression __
132
208
 
133
- float "floating-point number"
134
- = sign? digits? "." digits {
209
+ floatLiteral "floating-point number"
210
+ = digits? "." digits {
135
211
  return annotate([ops.literal, parseFloat(text())], location());
136
212
  }
137
213
 
138
- // Parse a function and its arguments, e.g. `fn(arg)`, possibly part of a chain
139
- // of function calls, like `fn(arg1)(arg2)(arg3)`.
140
- functionComposition "function composition"
141
- // Function with at least one argument and maybe implicit parens arguments
142
- = target:callTarget chain:args+ end:implicitParensArgs? {
143
- if (end) {
144
- chain.push(end);
145
- }
146
- return annotate(makeFunctionCall(target, chain, location()), location());
147
- }
148
- // Function with implicit parens arguments after maybe other arguments
149
- / target:callTarget chain:args* end:implicitParensArgs {
150
- if (end) {
151
- chain.push(end);
152
- }
153
- return annotate(makeFunctionCall(target, chain, location()), location());
154
- }
155
-
156
- // A reference to a function in scope: `fn` or `fn.js`.
157
- functionReference
158
- = ref:scopeReference {
159
- // If the reference looks like a builtin name, we treat it as a builtin
160
- // reference, otherwise it's a regular scope reference. We can't make this
161
- // distinction in the grammar.
162
- const name = ref[1];
163
- const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
164
- const op = builtinRegex.test(name) ? ops.builtin : ops.scope;
165
- return annotate([op, name], location());
166
- }
167
-
168
214
  // An expression in parentheses: `(foo)`
169
215
  group "parenthetical group"
170
- = "(" __ pipeline:pipeline __ closingParen {
171
- return annotate(pipeline, location());
172
- }
216
+ = "(" expression:expression closingParenthesis {
217
+ return annotate(downgradeReference(expression), location());
218
+ }
173
219
 
174
220
  guillemetString "guillemet string"
175
221
  = '«' chars:guillemetStringChar* '»' {
@@ -179,18 +225,19 @@ guillemetString "guillemet string"
179
225
  guillemetStringChar
180
226
  = !('»' / newLine) @textChar
181
227
 
182
- homeTree
228
+ // The user's home directory: `~`
229
+ homeDirectory
183
230
  = "~" {
184
- return annotate([ops.homeTree], location());
231
+ return annotate([ops.homeDirectory], location());
185
232
  }
186
233
 
187
234
  // A host identifier that may include a colon and port number: `example.com:80`.
188
235
  // This is used as a special case at the head of a path, where we want to
189
236
  // interpret a colon as part of a text identifier.
190
237
  host "HTTP/HTTPS host"
191
- = identifier:identifier port:(":" @number)? slash:"/"? {
238
+ = identifier:identifier port:(":" @integerLiteral)? slashFollows:slashFollows? {
192
239
  const portText = port ? `:${port[1]}` : "";
193
- const slashText = slash ? "/" : "";
240
+ const slashText = slashFollows ? "/" : "";
194
241
  const hostText = identifier + portText + slashText;
195
242
  return annotate([ops.literal, hostText], location());
196
243
  }
@@ -199,7 +246,7 @@ identifier "identifier"
199
246
  = chars:identifierChar+ { return chars.join(""); }
200
247
 
201
248
  identifierChar
202
- = [^(){}\[\]<>\-=,/:\`"'«»\\ →⇒\t\n\r] // No unescaped whitespace or special chars
249
+ = [^(){}\[\]<>\?!\|\-=,/:\`"'«»\\→⇒… \t\n\r] // No unescaped whitespace or special chars
203
250
  / @'-' !'>' // Accept a hyphen but not in a single arrow combination
204
251
  / escapedChar
205
252
 
@@ -208,38 +255,69 @@ identifierList
208
255
  return annotate(list, location());
209
256
  }
210
257
 
211
- implicitParensArgs "arguments with implicit parentheses"
212
- = inlineSpace+ @list
258
+ implicitParenthesesCallExpression "function call with implicit parentheses"
259
+ = head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
260
+ return args ? makeCall(head, args) : head;
261
+ }
262
+
263
+ // A separated list of values for an implicit parens call. This differs from
264
+ // `list` in that the value term can't be a pipeline.
265
+ implicitParensthesesArguments
266
+ = values:shorthandFunction|1.., separator| separator? {
267
+ return annotate(values, location());
268
+ }
213
269
 
214
270
  inlineSpace
215
271
  = [ \t]
216
272
 
217
- integer "integer"
218
- = sign? digits {
273
+ integerLiteral "integer"
274
+ = digits {
219
275
  return annotate([ops.literal, parseInt(text())], location());
220
276
  }
221
-
222
- // A lambda expression: `=foo()`
223
- lambda "lambda function"
224
- = "=" __ pipeline:pipeline {
225
- return annotate([ops.lambda, ["_"], pipeline], location());
277
+
278
+ // A separated list of values
279
+ list "list"
280
+ = values:pipelineExpression|1.., separator| separator? {
281
+ return annotate(values, location());
226
282
  }
227
283
 
228
- // A path that begins with a slash: `/foo/bar`
229
- leadingSlashPath "path with a leading slash"
230
- = "/" path:path? {
231
- return annotate(path ?? [], location());
284
+ literal
285
+ = numericLiteral
286
+ / stringLiteral
287
+
288
+ logicalAndExpression
289
+ = head:bitwiseOrExpression tail:(__ "&&" __ @bitwiseOrExpression)* {
290
+ return tail.length === 0
291
+ ? head
292
+ : annotate(
293
+ [ops.logicalAnd, downgradeReference(head), ...makeDeferredArguments(tail)],
294
+ location()
295
+ );
232
296
  }
233
297
 
234
- // A separated list of values
235
- list "list"
236
- = values:value|1.., separator| separator? {
237
- return annotate(values, location());
298
+ logicalOrExpression
299
+ = head:nullishCoalescingExpression tail:(__ "||" __ @nullishCoalescingExpression)* {
300
+ return tail.length === 0
301
+ ? head
302
+ : annotate(
303
+ [ops.logicalOr, downgradeReference(head), ...makeDeferredArguments(tail)],
304
+ location()
305
+ );
238
306
  }
239
307
 
240
308
  multiLineComment
241
309
  = "/*" (!"*/" .)* "*/" { return null; }
242
310
 
311
+ multiplicativeExpression
312
+ = head:exponentiationExpression tail:(__ @multiplicativeOperator __ @exponentiationExpression)* {
313
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
314
+ }
315
+
316
+ multiplicativeOperator
317
+ = "*"
318
+ / "/"
319
+ / "%"
320
+
243
321
  // A namespace reference is a string of letters only, followed by a colon.
244
322
  // For the time being, we also allow a leading `@`, which is deprecated.
245
323
  namespace
@@ -247,27 +325,28 @@ namespace
247
325
  return annotate([ops.builtin, (at ?? "") + chars.join("") + ":"], location());
248
326
  }
249
327
 
250
- // A namespace followed by a path: `fn:a/b/c`
251
- namespacePath
252
- = fn:namespace path:doubleSlashPath {
253
- return annotate(makeFunctionCall(fn, [path], location()), location());
254
- }
255
- / fn:namespace path:path {
256
- return annotate(makeFunctionCall(fn, [path], location()), location());
257
- }
258
-
259
328
  newLine
260
329
  = "\n"
261
330
  / "\r\n"
262
331
  / "\r"
263
332
 
264
333
  // A number
265
- number "number"
266
- = float
267
- / integer
334
+ numericLiteral "number"
335
+ = floatLiteral
336
+ / integerLiteral
337
+
338
+ nullishCoalescingExpression
339
+ = head:logicalAndExpression tail:(__ "??" __ @logicalAndExpression)* {
340
+ return tail.length === 0
341
+ ? head
342
+ : annotate(
343
+ [ops.nullishCoalescing, downgradeReference(head), ...makeDeferredArguments(tail)],
344
+ location()
345
+ );
346
+ }
268
347
 
269
348
  // An object literal: `{foo: 1, bar: 2}`
270
- object "object literal"
349
+ objectLiteral "object literal"
271
350
  = "{" __ entries:objectEntries? __ closingBrace {
272
351
  return annotate(makeObject(entries ?? [], ops.object), location());
273
352
  }
@@ -286,7 +365,7 @@ objectEntry
286
365
 
287
366
  // A getter definition inside an object literal: `foo = 1`
288
367
  objectGetter "object getter"
289
- = key:objectKey __ "=" __ pipeline:pipeline {
368
+ = key:objectKey __ "=" __ pipeline:pipelineExpression {
290
369
  return annotate(
291
370
  makeProperty(key, annotate([ops.getter, pipeline], location())),
292
371
  location()
@@ -302,7 +381,7 @@ objectKey "object key"
302
381
 
303
382
  // A property definition in an object literal: `x: 1`
304
383
  objectProperty "object property"
305
- = key:objectKey __ ":" __ pipeline:pipeline {
384
+ = key:objectKey __ ":" __ pipeline:pipelineExpression {
306
385
  return annotate(makeProperty(key, pipeline), location());
307
386
  }
308
387
 
@@ -316,98 +395,152 @@ objectPublicKey
316
395
  = identifier:identifier slash:"/"? {
317
396
  return identifier + (slash ?? "");
318
397
  }
319
- / string:string {
398
+ / string:stringLiteral {
320
399
  // Remove `ops.literal` from the string code
321
400
  return string[1];
322
401
  }
323
402
 
324
- parameterizedLambda
325
- = "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipeline {
326
- return annotate([ops.lambda, parameters ?? [], pipeline], location());
327
- }
328
-
329
403
  // Function arguments in parentheses
330
- parensArgs "function arguments in parentheses"
404
+ parenthesesArguments "function arguments in parentheses"
331
405
  = "(" __ list:list? __ ")" {
332
406
  return annotate(list ?? [undefined], location());
333
407
  }
334
408
 
335
- // A slash-separated path of keys
409
+ // A slash-separated path of keys: `a/b/c`
336
410
  path "slash-separated path"
337
411
  // Path with at least a tail
338
- = head:pathElement|0..| tail:pathTail {
339
- let path = tail ? [...head, tail] : head;
340
- // Remove parts for consecutive slashes
341
- path = path.filter((part) => part[1] !== "/");
342
- return annotate(path, location());
343
- }
344
- // Path with slashes, maybe no tail
345
- / head:pathElement|1..| tail:pathTail? {
346
- let path = tail ? [...head, tail] : head;
347
- // Remove parts for consecutive slashes
348
- path = path.filter((part) => part[1] !== "/");
349
- return annotate(path, location());
350
- }
412
+ = segments:pathSegment|1..| {
413
+ // Drop empty segments that represent consecutive or final slashes
414
+ segments = segments.filter(segment => segment);
415
+ return annotate(segments, location());
416
+ }
417
+
418
+ // A slash-separated path of keys that follows a call target
419
+ pathArguments
420
+ = path:path {
421
+ return annotate([ops.traverse, ...path], location());
422
+ }
351
423
 
352
- // A path key followed by a slash
353
- pathElement
354
- = chars:pathKeyChar* "/" {
355
- return annotate([ops.literal, chars.join("") + "/"], location());
424
+ // A single key in a slash-separated path: `/a`
425
+ pathKey
426
+ = chars:pathSegmentChar+ slashFollows:slashFollows? {
427
+ // Append a trailing slash if one follows (but don't consume it)
428
+ const key = chars.join("") + (slashFollows ? "/" : "");
429
+ return annotate([ops.literal, key], location());
356
430
  }
357
431
 
358
- // A single character in a slash-separated path.
359
- pathKeyChar
432
+ pathSegment
433
+ = "/" @pathKey?
434
+
435
+ // A single character in a slash-separated path segment
436
+ pathSegmentChar
360
437
  // This is more permissive than an identifier. It allows some characters like
361
438
  // brackets or quotes that are not allowed in identifiers.
362
439
  = [^(){}\[\],:/\\ \t\n\r]
363
440
  / escapedChar
364
441
 
365
- // A path key without a slash
366
- pathTail
367
- = chars:pathKeyChar+ {
368
- return annotate([ops.literal, chars.join("")], location());
369
- }
370
-
371
442
  // A pipeline that starts with a value and optionally applies a series of
372
443
  // functions to it.
373
- pipeline
374
- = head:value tail:(__ singleArrow __ @pipelineStep)* {
375
- return tail.length === 0
376
- ? head
377
- : annotate(makePipeline([head, ...tail]), location());
444
+ pipelineExpression
445
+ = head:shorthandFunction tail:(__ singleArrow __ @shorthandFunction)* {
446
+ return annotate(
447
+ tail.reduce(makePipeline, downgradeReference(head)),
448
+ location()
449
+ );
378
450
  }
379
451
 
380
- // A step in a pipeline
381
- pipelineStep
382
- = lambda
383
- / parameterizedLambda
384
- / callTarget
452
+ primary
453
+ = literal
454
+ / arrayLiteral
455
+ / objectLiteral
456
+ / group
457
+ / templateLiteral
458
+ / reference
385
459
 
386
460
  // Top-level Origami progam with possible shebang directive (which is ignored)
387
461
  program "Origami program"
388
462
  = shebang? @expression
389
463
 
390
- scopeReference "scope reference"
391
- = key:identifier {
392
- return annotate([ops.scope, key], location());
464
+ // Protocol with double-slash path: `https://example.com/index.html`
465
+ protocolExpression
466
+ = fn:namespace "//" host:host path:path? {
467
+ const keys = annotate([host, ...(path ?? [])], location());
468
+ return annotate(makeCall(fn, keys), location());
469
+ }
470
+ / primary
471
+
472
+ // A namespace followed by a key: `foo:x`
473
+ qualifiedReference
474
+ = fn:namespace reference:scopeReference {
475
+ const literal = annotate([ops.literal, reference[1]], reference.location);
476
+ return annotate(makeCall(fn, [literal]), location());
477
+ }
478
+
479
+ reference
480
+ = rootDirectory
481
+ / homeDirectory
482
+ / qualifiedReference
483
+ / namespace
484
+ / scopeReference
485
+
486
+ relationalExpression
487
+ = head:shiftExpression tail:(__ @relationalOperator __ @shiftExpression)* {
488
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
489
+ }
490
+
491
+ relationalOperator
492
+ = "<="
493
+ / "<"
494
+ / ">="
495
+ / ">"
496
+
497
+ // A top-level folder below the root: `/foo`
498
+ // or the root folder itself: `/`
499
+ rootDirectory
500
+ = "/" key:pathKey {
501
+ return annotate([ops.rootDirectory, key], location());
502
+ }
503
+ / "/" !"/" {
504
+ return annotate([ops.rootDirectory], location());
393
505
  }
394
506
 
395
- scopeTraverse
396
- = ref:scopeReference "/" path:path? {
397
- const head = [ops.scope, `${ ref[1] }/`];
398
- head.location = ref.location;
399
- return annotate([ops.traverse, head, ...(path ?? [])], location());
507
+ scopeReference "scope reference"
508
+ = identifier:identifier slashFollows:slashFollows? {
509
+ const id = identifier + (slashFollows ? "/" : "");
510
+ return annotate(makeReference(id), location());
400
511
  }
401
512
 
402
513
  separator
403
514
  = __ "," __
404
515
  / whitespaceWithNewLine
405
516
 
517
+ // Check whether next character is a slash without consuming input
518
+ slashFollows
519
+ // This expression returned `undefined` if successful; we convert to `true`
520
+ = &"/" {
521
+ return true;
522
+ }
523
+
406
524
  shebang
407
525
  = "#!" [^\n\r]* { return null; }
408
526
 
409
- sign
410
- = [+\-]
527
+ shiftExpression
528
+ = head:additiveExpression tail:(__ @shiftOperator __ @additiveExpression)* {
529
+ return annotate(tail.reduce(makeBinaryOperation, head), location());
530
+ }
531
+
532
+ shiftOperator
533
+ = "<<"
534
+ / ">>>"
535
+ / ">>"
536
+
537
+ // A shorthand lambda expression: `=foo(_)`
538
+ shorthandFunction "lambda function"
539
+ // Avoid a following equal sign (for an equality)
540
+ = "=" !"=" __ definition:implicitParenthesesCallExpression {
541
+ return annotate([ops.lambda, ["_"], definition], location());
542
+ }
543
+ / implicitParenthesesCallExpression
411
544
 
412
545
  singleArrow
413
546
  = "→"
@@ -425,23 +558,15 @@ singleQuoteStringChar
425
558
  = !("'" / newLine) @textChar
426
559
 
427
560
  spread
428
- = ellipsis value:value {
561
+ = ellipsis __ value:conditionalExpression {
429
562
  return annotate([ops.spread, value], location());
430
563
  }
431
564
 
432
- start
433
- = number
434
-
435
- string "string"
565
+ stringLiteral "string"
436
566
  = doubleQuoteString
437
567
  / singleQuoteString
438
568
  / guillemetString
439
569
 
440
- taggedTemplate
441
- = tag:callTarget "`" contents:templateLiteralContents "`" {
442
- return annotate(makeTemplate(tag, contents[0], contents[1]), location());
443
- }
444
-
445
570
  // A top-level document defining a template. This is the same as a template
446
571
  // literal, but can contain backticks at the top level.
447
572
  templateDocument "template"
@@ -485,37 +610,26 @@ templateLiteralText
485
610
 
486
611
  // A substitution in a template literal: `${x}`
487
612
  templateSubstitution "template substitution"
488
- = "${" @expression "}"
613
+ = "${" expression:expression "}" {
614
+ return annotate(expression, location());
615
+ }
489
616
 
490
617
  textChar
491
618
  = escapedChar
492
619
  / .
493
620
 
494
- // An Origami expression that produces a value, no leading/trailing whitespace
495
- value
496
- // Literals that can't start a function call
497
- = number
498
- // Try functions next; they can start with expression types that follow
499
- // (array, object, etc.), and we want to parse the larger thing first.
500
- / parameterizedLambda
501
- / functionComposition
502
- / taggedTemplate
503
- / namespacePath
504
- // Then try parsers that look for a distinctive token at the start: an opening
505
- // slash, bracket, curly brace, etc.
506
- / absoluteFilePath
507
- / array
508
- / object
509
- / lambda
510
- / templateLiteral
511
- / string
512
- / group
513
- / homeTree
514
- // Things that have a distinctive character, but not at the start
515
- / scopeTraverse
516
- / namespace
517
- // Least distinctive option is a simple scope reference, so it comes last.
518
- / scopeReference
621
+ // A unary prefix operator: `!x`
622
+ unaryExpression
623
+ = operator:unaryOperator __ expression:unaryExpression {
624
+ return annotate(makeUnaryOperation(operator, expression), location());
625
+ }
626
+ / callExpression
627
+
628
+ unaryOperator
629
+ = "!"
630
+ / "+"
631
+ / "-"
632
+ / "~"
519
633
 
520
634
  whitespaceWithNewLine
521
635
  = inlineSpace* comment? newLine __