@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.
- package/index.ts +1 -0
- package/package.json +6 -4
- package/src/compiler/compile.js +33 -15
- package/src/compiler/origami.pegjs +222 -193
- package/src/compiler/parse.js +1380 -1040
- package/src/compiler/parserHelpers.js +171 -45
- package/src/runtime/ops.js +137 -55
- package/test/cases/ReadMe.md +1 -0
- package/test/cases/conditionalExpression.yaml +101 -0
- package/test/cases/logicalAndExpression.yaml +146 -0
- package/test/cases/logicalOrExpression.yaml +145 -0
- package/test/cases/nullishCoalescingExpression.yaml +105 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/parse.test.js +447 -363
- package/test/generated/conditionalExpression.test.js +58 -0
- package/test/generated/logicalAndExpression.test.js +80 -0
- package/test/generated/logicalOrExpression.test.js +78 -0
- package/test/generated/nullishCoalescingExpression.test.js +64 -0
- package/test/generator/generateTests.js +80 -0
- package/test/generator/oriEval.js +15 -0
- package/test/runtime/ops.test.js +127 -23
|
@@ -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
|
-
|
|
19
|
+
makeBinaryOperatorChain,
|
|
20
|
+
makeCall,
|
|
21
|
+
makeDeferredArguments,
|
|
14
22
|
makeObject,
|
|
15
23
|
makePipeline,
|
|
16
24
|
makeProperty,
|
|
17
|
-
|
|
25
|
+
makeReference,
|
|
26
|
+
makeTemplate,
|
|
27
|
+
makeUnaryOperatorCall
|
|
18
28
|
} from "./parserHelpers.js";
|
|
19
29
|
|
|
20
30
|
}}
|
|
@@ -25,21 +35,12 @@ __
|
|
|
25
35
|
return null;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
= !"//" path:leadingSlashPath {
|
|
33
|
-
return annotate([[ops.filesRoot], ...path], location());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
args "function arguments"
|
|
37
|
-
= parensArgs
|
|
38
|
-
/ path:leadingSlashPath {
|
|
39
|
-
return annotate([ops.traverse, ...path], location());
|
|
40
|
-
}
|
|
38
|
+
arguments "function arguments"
|
|
39
|
+
= parenthesesArguments
|
|
40
|
+
/ pathArguments
|
|
41
|
+
/ templateLiteral
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
arrayLiteral "array"
|
|
43
44
|
= "[" __ entries:arrayEntries? __ closingBracket {
|
|
44
45
|
return annotate(makeArray(entries ?? []), location());
|
|
45
46
|
}
|
|
@@ -52,19 +53,22 @@ arrayEntries
|
|
|
52
53
|
|
|
53
54
|
arrayEntry
|
|
54
55
|
= spread
|
|
55
|
-
/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
/ pipelineExpression
|
|
57
|
+
|
|
58
|
+
arrowFunction
|
|
59
|
+
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipelineExpression {
|
|
60
|
+
return annotate([ops.lambda, parameters ?? [], pipeline], location());
|
|
61
|
+
}
|
|
62
|
+
/ conditionalExpression
|
|
63
|
+
|
|
64
|
+
// A function call: `fn(arg)`, possibly part of a chain of function calls, like
|
|
65
|
+
// `fn(arg1)(arg2)(arg3)`.
|
|
66
|
+
callExpression "function call"
|
|
67
|
+
= head:protocolExpression tail:arguments* {
|
|
68
|
+
return tail.length === 0
|
|
69
|
+
? head
|
|
70
|
+
: annotate(tail.reduce(makeCall, head), location());
|
|
71
|
+
}
|
|
68
72
|
|
|
69
73
|
// Required closing curly brace. We use this for the `object` term: if the
|
|
70
74
|
// parser sees a left curly brace, here we must see a right curly brace.
|
|
@@ -84,7 +88,7 @@ closingBracket
|
|
|
84
88
|
// Required closing parenthesis. We use this for the `group` term: it's the last
|
|
85
89
|
// term in the `step` parser that starts with a parenthesis, so if that parser
|
|
86
90
|
// sees a left parenthesis, here we must see a right parenthesis.
|
|
87
|
-
|
|
91
|
+
closingParenthesis
|
|
88
92
|
= ")"
|
|
89
93
|
/ .? {
|
|
90
94
|
error("Expected right parenthesis");
|
|
@@ -95,6 +99,20 @@ comment "comment"
|
|
|
95
99
|
= multiLineComment
|
|
96
100
|
/ singleLineComment
|
|
97
101
|
|
|
102
|
+
conditionalExpression
|
|
103
|
+
= condition:logicalOrExpression __
|
|
104
|
+
"?" __ truthy:pipelineExpression __
|
|
105
|
+
":" __ falsy:pipelineExpression
|
|
106
|
+
{
|
|
107
|
+
return annotate([
|
|
108
|
+
ops.conditional,
|
|
109
|
+
downgradeReference(condition),
|
|
110
|
+
[ops.lambda, [], downgradeReference(truthy)],
|
|
111
|
+
[ops.lambda, [], downgradeReference(falsy)]
|
|
112
|
+
], location());
|
|
113
|
+
}
|
|
114
|
+
/ logicalOrExpression
|
|
115
|
+
|
|
98
116
|
digits
|
|
99
117
|
= @[0-9]+
|
|
100
118
|
|
|
@@ -108,13 +126,20 @@ doubleQuoteString "double quote string"
|
|
|
108
126
|
doubleQuoteStringChar
|
|
109
127
|
= !('"' / newLine) @textChar
|
|
110
128
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
129
|
+
ellipsis = "..." / "…" // Unicode ellipsis
|
|
130
|
+
|
|
131
|
+
equalityExpression
|
|
132
|
+
= head:unaryExpression tail:(__ @equalityOperator __ @unaryExpression)* {
|
|
133
|
+
return tail.length === 0
|
|
134
|
+
? head
|
|
135
|
+
: annotate(makeBinaryOperatorChain(head, tail), location());
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
|
|
138
|
+
equalityOperator
|
|
139
|
+
= "==="
|
|
140
|
+
/ "!=="
|
|
141
|
+
/ "=="
|
|
142
|
+
/ "!="
|
|
118
143
|
|
|
119
144
|
escapedChar "backslash-escaped character"
|
|
120
145
|
= "\\0" { return "\0"; }
|
|
@@ -128,48 +153,18 @@ escapedChar "backslash-escaped character"
|
|
|
128
153
|
|
|
129
154
|
// A top-level expression, possibly with leading/trailing whitespace
|
|
130
155
|
expression
|
|
131
|
-
= __ @
|
|
156
|
+
= __ @pipelineExpression __
|
|
132
157
|
|
|
133
|
-
|
|
158
|
+
floatLiteral "floating-point number"
|
|
134
159
|
= sign? digits? "." digits {
|
|
135
160
|
return annotate([ops.literal, parseFloat(text())], location());
|
|
136
161
|
}
|
|
137
162
|
|
|
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
163
|
// An expression in parentheses: `(foo)`
|
|
169
164
|
group "parenthetical group"
|
|
170
|
-
= "("
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
= "(" expression:expression closingParenthesis {
|
|
166
|
+
return annotate(downgradeReference(expression), location());
|
|
167
|
+
}
|
|
173
168
|
|
|
174
169
|
guillemetString "guillemet string"
|
|
175
170
|
= '«' chars:guillemetStringChar* '»' {
|
|
@@ -179,18 +174,19 @@ guillemetString "guillemet string"
|
|
|
179
174
|
guillemetStringChar
|
|
180
175
|
= !('»' / newLine) @textChar
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
// The user's home directory: `~`
|
|
178
|
+
homeDirectory
|
|
183
179
|
= "~" {
|
|
184
|
-
return annotate([ops.
|
|
180
|
+
return annotate([ops.homeDirectory], location());
|
|
185
181
|
}
|
|
186
182
|
|
|
187
183
|
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
188
184
|
// This is used as a special case at the head of a path, where we want to
|
|
189
185
|
// interpret a colon as part of a text identifier.
|
|
190
186
|
host "HTTP/HTTPS host"
|
|
191
|
-
= identifier:identifier port:(":" @
|
|
187
|
+
= identifier:identifier port:(":" @integerLiteral)? slashFollows:slashFollows? {
|
|
192
188
|
const portText = port ? `:${port[1]}` : "";
|
|
193
|
-
const slashText =
|
|
189
|
+
const slashText = slashFollows ? "/" : "";
|
|
194
190
|
const hostText = identifier + portText + slashText;
|
|
195
191
|
return annotate([ops.literal, hostText], location());
|
|
196
192
|
}
|
|
@@ -199,7 +195,7 @@ identifier "identifier"
|
|
|
199
195
|
= chars:identifierChar+ { return chars.join(""); }
|
|
200
196
|
|
|
201
197
|
identifierChar
|
|
202
|
-
= [^(){}\[\]
|
|
198
|
+
= [^(){}\[\]<>\?!&\|\-=,/:\`"'«»\\ →⇒\t\n\r] // No unescaped whitespace or special chars
|
|
203
199
|
/ @'-' !'>' // Accept a hyphen but not in a single arrow combination
|
|
204
200
|
/ escapedChar
|
|
205
201
|
|
|
@@ -208,33 +204,54 @@ identifierList
|
|
|
208
204
|
return annotate(list, location());
|
|
209
205
|
}
|
|
210
206
|
|
|
211
|
-
|
|
212
|
-
= inlineSpace+ @
|
|
207
|
+
implicitParenthesesCallExpression "function call with implicit parentheses"
|
|
208
|
+
= head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
|
|
209
|
+
return args ? makeCall(head, args) : head;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// A separated list of values for an implicit parens call. This differs from
|
|
213
|
+
// `list` in that the value term can't be a pipeline.
|
|
214
|
+
implicitParensthesesArguments
|
|
215
|
+
= values:shorthandFunction|1.., separator| separator? {
|
|
216
|
+
return annotate(values, location());
|
|
217
|
+
}
|
|
213
218
|
|
|
214
219
|
inlineSpace
|
|
215
220
|
= [ \t]
|
|
216
221
|
|
|
217
|
-
|
|
222
|
+
integerLiteral "integer"
|
|
218
223
|
= sign? digits {
|
|
219
224
|
return annotate([ops.literal, parseInt(text())], location());
|
|
220
225
|
}
|
|
221
|
-
|
|
222
|
-
// A
|
|
223
|
-
|
|
224
|
-
=
|
|
225
|
-
return annotate(
|
|
226
|
+
|
|
227
|
+
// A separated list of values
|
|
228
|
+
list "list"
|
|
229
|
+
= values:pipelineExpression|1.., separator| separator? {
|
|
230
|
+
return annotate(values, location());
|
|
226
231
|
}
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
literal
|
|
234
|
+
= numericLiteral
|
|
235
|
+
/ stringLiteral
|
|
236
|
+
|
|
237
|
+
logicalAndExpression
|
|
238
|
+
= head:equalityExpression tail:(__ "&&" __ @equalityExpression)* {
|
|
239
|
+
return tail.length === 0
|
|
240
|
+
? head
|
|
241
|
+
: annotate(
|
|
242
|
+
[ops.logicalAnd, downgradeReference(head), ...makeDeferredArguments(tail)],
|
|
243
|
+
location()
|
|
244
|
+
);
|
|
232
245
|
}
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
logicalOrExpression
|
|
248
|
+
= head:nullishCoalescingExpression tail:(__ "||" __ @nullishCoalescingExpression)* {
|
|
249
|
+
return tail.length === 0
|
|
250
|
+
? head
|
|
251
|
+
: annotate(
|
|
252
|
+
[ops.logicalOr, downgradeReference(head), ...makeDeferredArguments(tail)],
|
|
253
|
+
location()
|
|
254
|
+
);
|
|
238
255
|
}
|
|
239
256
|
|
|
240
257
|
multiLineComment
|
|
@@ -247,27 +264,28 @@ namespace
|
|
|
247
264
|
return annotate([ops.builtin, (at ?? "") + chars.join("") + ":"], location());
|
|
248
265
|
}
|
|
249
266
|
|
|
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
267
|
newLine
|
|
260
268
|
= "\n"
|
|
261
269
|
/ "\r\n"
|
|
262
270
|
/ "\r"
|
|
263
271
|
|
|
264
272
|
// A number
|
|
265
|
-
|
|
266
|
-
=
|
|
267
|
-
/
|
|
273
|
+
numericLiteral "number"
|
|
274
|
+
= floatLiteral
|
|
275
|
+
/ integerLiteral
|
|
276
|
+
|
|
277
|
+
nullishCoalescingExpression
|
|
278
|
+
= head:logicalAndExpression tail:(__ "??" __ @logicalAndExpression)* {
|
|
279
|
+
return tail.length === 0
|
|
280
|
+
? head
|
|
281
|
+
: annotate(
|
|
282
|
+
[ops.nullishCoalescing, downgradeReference(head), ...makeDeferredArguments(tail)],
|
|
283
|
+
location()
|
|
284
|
+
);
|
|
285
|
+
}
|
|
268
286
|
|
|
269
287
|
// An object literal: `{foo: 1, bar: 2}`
|
|
270
|
-
|
|
288
|
+
objectLiteral "object literal"
|
|
271
289
|
= "{" __ entries:objectEntries? __ closingBrace {
|
|
272
290
|
return annotate(makeObject(entries ?? [], ops.object), location());
|
|
273
291
|
}
|
|
@@ -286,7 +304,7 @@ objectEntry
|
|
|
286
304
|
|
|
287
305
|
// A getter definition inside an object literal: `foo = 1`
|
|
288
306
|
objectGetter "object getter"
|
|
289
|
-
= key:objectKey __ "=" __ pipeline:
|
|
307
|
+
= key:objectKey __ "=" __ pipeline:pipelineExpression {
|
|
290
308
|
return annotate(
|
|
291
309
|
makeProperty(key, annotate([ops.getter, pipeline], location())),
|
|
292
310
|
location()
|
|
@@ -302,7 +320,7 @@ objectKey "object key"
|
|
|
302
320
|
|
|
303
321
|
// A property definition in an object literal: `x: 1`
|
|
304
322
|
objectProperty "object property"
|
|
305
|
-
= key:objectKey __ ":" __ pipeline:
|
|
323
|
+
= key:objectKey __ ":" __ pipeline:pipelineExpression {
|
|
306
324
|
return annotate(makeProperty(key, pipeline), location());
|
|
307
325
|
}
|
|
308
326
|
|
|
@@ -316,96 +334,129 @@ objectPublicKey
|
|
|
316
334
|
= identifier:identifier slash:"/"? {
|
|
317
335
|
return identifier + (slash ?? "");
|
|
318
336
|
}
|
|
319
|
-
/ string:
|
|
337
|
+
/ string:stringLiteral {
|
|
320
338
|
// Remove `ops.literal` from the string code
|
|
321
339
|
return string[1];
|
|
322
340
|
}
|
|
323
341
|
|
|
324
|
-
parameterizedLambda
|
|
325
|
-
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipeline {
|
|
326
|
-
return annotate([ops.lambda, parameters ?? [], pipeline], location());
|
|
327
|
-
}
|
|
328
|
-
|
|
329
342
|
// Function arguments in parentheses
|
|
330
|
-
|
|
343
|
+
parenthesesArguments "function arguments in parentheses"
|
|
331
344
|
= "(" __ list:list? __ ")" {
|
|
332
345
|
return annotate(list ?? [undefined], location());
|
|
333
346
|
}
|
|
334
347
|
|
|
335
|
-
// A slash-separated path of keys
|
|
348
|
+
// A slash-separated path of keys: `a/b/c`
|
|
336
349
|
path "slash-separated path"
|
|
337
350
|
// Path with at least a tail
|
|
338
|
-
=
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return annotate(path, location());
|
|
350
|
-
}
|
|
351
|
+
= segments:pathSegment|1..| {
|
|
352
|
+
// Drop empty segments that represent consecutive or final slashes
|
|
353
|
+
segments = segments.filter(segment => segment);
|
|
354
|
+
return annotate(segments, location());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// A slash-separated path of keys that follows a call target
|
|
358
|
+
pathArguments
|
|
359
|
+
= path:path {
|
|
360
|
+
return annotate([ops.traverse, ...path], location());
|
|
361
|
+
}
|
|
351
362
|
|
|
352
|
-
// A
|
|
353
|
-
|
|
354
|
-
= chars:
|
|
355
|
-
|
|
363
|
+
// A single key in a slash-separated path: `/a`
|
|
364
|
+
pathKey
|
|
365
|
+
= chars:pathSegmentChar+ slashFollows:slashFollows? {
|
|
366
|
+
// Append a trailing slash if one follows (but don't consume it)
|
|
367
|
+
const key = chars.join("") + (slashFollows ? "/" : "");
|
|
368
|
+
return annotate([ops.literal, key], location());
|
|
356
369
|
}
|
|
357
370
|
|
|
358
|
-
|
|
359
|
-
|
|
371
|
+
pathSegment
|
|
372
|
+
= "/" @pathKey?
|
|
373
|
+
|
|
374
|
+
// A single character in a slash-separated path segment
|
|
375
|
+
pathSegmentChar
|
|
360
376
|
// This is more permissive than an identifier. It allows some characters like
|
|
361
377
|
// brackets or quotes that are not allowed in identifiers.
|
|
362
378
|
= [^(){}\[\],:/\\ \t\n\r]
|
|
363
379
|
/ escapedChar
|
|
364
380
|
|
|
365
|
-
// A path key without a slash
|
|
366
|
-
pathTail
|
|
367
|
-
= chars:pathKeyChar+ {
|
|
368
|
-
return annotate([ops.literal, chars.join("")], location());
|
|
369
|
-
}
|
|
370
|
-
|
|
371
381
|
// A pipeline that starts with a value and optionally applies a series of
|
|
372
382
|
// functions to it.
|
|
373
|
-
|
|
374
|
-
= head:
|
|
375
|
-
return tail.
|
|
376
|
-
? head
|
|
377
|
-
: annotate(makePipeline([head, ...tail]), location());
|
|
383
|
+
pipelineExpression
|
|
384
|
+
= head:shorthandFunction tail:(__ singleArrow __ @shorthandFunction)* {
|
|
385
|
+
return tail.reduce(makePipeline, downgradeReference(head));
|
|
378
386
|
}
|
|
379
387
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
/
|
|
384
|
-
/
|
|
388
|
+
primary
|
|
389
|
+
= literal
|
|
390
|
+
/ arrayLiteral
|
|
391
|
+
/ objectLiteral
|
|
392
|
+
/ group
|
|
393
|
+
/ templateLiteral
|
|
394
|
+
/ reference
|
|
385
395
|
|
|
386
396
|
// Top-level Origami progam with possible shebang directive (which is ignored)
|
|
387
397
|
program "Origami program"
|
|
388
398
|
= shebang? @expression
|
|
389
399
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
400
|
+
// Protocol with double-slash path: `https://example.com/index.html`
|
|
401
|
+
protocolExpression
|
|
402
|
+
= fn:namespace "//" host:host path:path? {
|
|
403
|
+
const keys = annotate([host, ...(path ?? [])], location());
|
|
404
|
+
return annotate(makeCall(fn, keys), location());
|
|
393
405
|
}
|
|
406
|
+
/ primary
|
|
394
407
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return annotate(
|
|
408
|
+
// A namespace followed by a key: `foo:x`
|
|
409
|
+
qualifiedReference
|
|
410
|
+
= fn:namespace reference:scopeReference {
|
|
411
|
+
const literal = annotate([ops.literal, reference[1]], reference.location);
|
|
412
|
+
return annotate(makeCall(fn, [literal]), location());
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
reference
|
|
416
|
+
= rootDirectory
|
|
417
|
+
/ homeDirectory
|
|
418
|
+
/ qualifiedReference
|
|
419
|
+
/ namespace
|
|
420
|
+
/ scopeReference
|
|
421
|
+
|
|
422
|
+
// A top-level folder below the root: `/foo`
|
|
423
|
+
// or the root folder itself: `/`
|
|
424
|
+
rootDirectory
|
|
425
|
+
= "/" key:pathKey {
|
|
426
|
+
return annotate([ops.rootDirectory, key], location());
|
|
427
|
+
}
|
|
428
|
+
/ "/" !"/" {
|
|
429
|
+
return annotate([ops.rootDirectory], location());
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
scopeReference "scope reference"
|
|
433
|
+
= identifier:identifier slashFollows:slashFollows? {
|
|
434
|
+
const id = identifier + (slashFollows ? "/" : "");
|
|
435
|
+
return annotate(makeReference(id), location());
|
|
400
436
|
}
|
|
401
437
|
|
|
402
438
|
separator
|
|
403
439
|
= __ "," __
|
|
404
440
|
/ whitespaceWithNewLine
|
|
405
441
|
|
|
442
|
+
// Check whether next character is a slash without consuming input
|
|
443
|
+
slashFollows
|
|
444
|
+
// This expression returned `undefined` if successful; we convert to `true`
|
|
445
|
+
= &"/" {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
|
|
406
449
|
shebang
|
|
407
450
|
= "#!" [^\n\r]* { return null; }
|
|
408
451
|
|
|
452
|
+
// A shorthand lambda expression: `=foo(_)`
|
|
453
|
+
shorthandFunction "lambda function"
|
|
454
|
+
// Avoid a following equal sign (for an equality)
|
|
455
|
+
= "=" !"=" __ definition:implicitParenthesesCallExpression {
|
|
456
|
+
return annotate([ops.lambda, ["_"], definition], location());
|
|
457
|
+
}
|
|
458
|
+
/ implicitParenthesesCallExpression
|
|
459
|
+
|
|
409
460
|
sign
|
|
410
461
|
= [+\-]
|
|
411
462
|
|
|
@@ -425,23 +476,15 @@ singleQuoteStringChar
|
|
|
425
476
|
= !("'" / newLine) @textChar
|
|
426
477
|
|
|
427
478
|
spread
|
|
428
|
-
= ellipsis value:
|
|
479
|
+
= ellipsis __ value:conditionalExpression {
|
|
429
480
|
return annotate([ops.spread, value], location());
|
|
430
481
|
}
|
|
431
482
|
|
|
432
|
-
|
|
433
|
-
= number
|
|
434
|
-
|
|
435
|
-
string "string"
|
|
483
|
+
stringLiteral "string"
|
|
436
484
|
= doubleQuoteString
|
|
437
485
|
/ singleQuoteString
|
|
438
486
|
/ guillemetString
|
|
439
487
|
|
|
440
|
-
taggedTemplate
|
|
441
|
-
= tag:callTarget "`" contents:templateLiteralContents "`" {
|
|
442
|
-
return annotate(makeTemplate(tag, contents[0], contents[1]), location());
|
|
443
|
-
}
|
|
444
|
-
|
|
445
488
|
// A top-level document defining a template. This is the same as a template
|
|
446
489
|
// literal, but can contain backticks at the top level.
|
|
447
490
|
templateDocument "template"
|
|
@@ -485,37 +528,23 @@ templateLiteralText
|
|
|
485
528
|
|
|
486
529
|
// A substitution in a template literal: `${x}`
|
|
487
530
|
templateSubstitution "template substitution"
|
|
488
|
-
= "${"
|
|
531
|
+
= "${" expression:expression "}" {
|
|
532
|
+
return annotate(expression, location());
|
|
533
|
+
}
|
|
489
534
|
|
|
490
535
|
textChar
|
|
491
536
|
= escapedChar
|
|
492
537
|
/ .
|
|
493
538
|
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
539
|
+
// A unary prefix operator: `!x`
|
|
540
|
+
unaryExpression
|
|
541
|
+
= operator:unaryOperator __ expression:unaryExpression {
|
|
542
|
+
return annotate(makeUnaryOperatorCall(operator, expression), location());
|
|
543
|
+
}
|
|
544
|
+
/ callExpression
|
|
545
|
+
|
|
546
|
+
unaryOperator
|
|
547
|
+
= "!"
|
|
519
548
|
|
|
520
549
|
whitespaceWithNewLine
|
|
521
550
|
= inlineSpace* comment? newLine __
|