@weborigami/language 0.0.73 → 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/main.js +2 -2
- package/package.json +6 -4
- package/src/compiler/compile.js +42 -17
- package/src/compiler/origami.pegjs +248 -182
- package/src/compiler/parse.js +1569 -1231
- package/src/compiler/parserHelpers.js +180 -48
- package/src/runtime/HandleExtensionsTransform.js +1 -1
- package/src/runtime/ImportModulesMixin.js +1 -1
- package/src/runtime/codeFragment.js +2 -2
- package/src/runtime/errors.js +104 -0
- package/src/runtime/evaluate.js +3 -3
- package/src/runtime/expressionObject.js +8 -5
- package/src/runtime/{extensions.js → handlers.js} +6 -24
- package/src/runtime/internal.js +1 -0
- package/src/runtime/ops.js +156 -185
- package/src/runtime/typos.js +71 -0
- 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 +7 -7
- package/test/compiler/parse.test.js +506 -294
- 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/fixtures/templates/greet.orit +1 -1
- package/test/runtime/{extensions.test.js → handlers.test.js} +2 -2
- package/test/runtime/ops.test.js +129 -26
- package/test/runtime/typos.test.js +21 -0
- package/src/runtime/formatError.js +0 -56
|
@@ -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,20 +53,22 @@ arrayEntries
|
|
|
52
53
|
|
|
53
54
|
arrayEntry
|
|
54
55
|
= spread
|
|
55
|
-
/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|
|
69
72
|
|
|
70
73
|
// Required closing curly brace. We use this for the `object` term: if the
|
|
71
74
|
// parser sees a left curly brace, here we must see a right curly brace.
|
|
@@ -85,7 +88,7 @@ closingBracket
|
|
|
85
88
|
// Required closing parenthesis. We use this for the `group` term: it's the last
|
|
86
89
|
// term in the `step` parser that starts with a parenthesis, so if that parser
|
|
87
90
|
// sees a left parenthesis, here we must see a right parenthesis.
|
|
88
|
-
|
|
91
|
+
closingParenthesis
|
|
89
92
|
= ")"
|
|
90
93
|
/ .? {
|
|
91
94
|
error("Expected right parenthesis");
|
|
@@ -96,6 +99,20 @@ comment "comment"
|
|
|
96
99
|
= multiLineComment
|
|
97
100
|
/ singleLineComment
|
|
98
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
|
+
|
|
99
116
|
digits
|
|
100
117
|
= @[0-9]+
|
|
101
118
|
|
|
@@ -111,6 +128,19 @@ doubleQuoteStringChar
|
|
|
111
128
|
|
|
112
129
|
ellipsis = "..." / "…" // Unicode ellipsis
|
|
113
130
|
|
|
131
|
+
equalityExpression
|
|
132
|
+
= head:unaryExpression tail:(__ @equalityOperator __ @unaryExpression)* {
|
|
133
|
+
return tail.length === 0
|
|
134
|
+
? head
|
|
135
|
+
: annotate(makeBinaryOperatorChain(head, tail), location());
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
equalityOperator
|
|
139
|
+
= "==="
|
|
140
|
+
/ "!=="
|
|
141
|
+
/ "=="
|
|
142
|
+
/ "!="
|
|
143
|
+
|
|
114
144
|
escapedChar "backslash-escaped character"
|
|
115
145
|
= "\\0" { return "\0"; }
|
|
116
146
|
/ "\\b" { return "\b"; }
|
|
@@ -121,42 +151,19 @@ escapedChar "backslash-escaped character"
|
|
|
121
151
|
/ "\\v" { return "\v"; }
|
|
122
152
|
/ "\\" @.
|
|
123
153
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
=
|
|
127
|
-
|
|
128
|
-
// Top-level Origami expression, possible shebang directive and leading/trailing
|
|
129
|
-
// whitepsace.
|
|
130
|
-
expression "Origami expression"
|
|
131
|
-
= shebang? __ @expr __
|
|
154
|
+
// A top-level expression, possibly with leading/trailing whitespace
|
|
155
|
+
expression
|
|
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
163
|
// An expression in parentheses: `(foo)`
|
|
157
164
|
group "parenthetical group"
|
|
158
|
-
= "("
|
|
159
|
-
return annotate(
|
|
165
|
+
= "(" expression:expression closingParenthesis {
|
|
166
|
+
return annotate(downgradeReference(expression), location());
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
guillemetString "guillemet string"
|
|
@@ -167,13 +174,20 @@ guillemetString "guillemet string"
|
|
|
167
174
|
guillemetStringChar
|
|
168
175
|
= !('»' / newLine) @textChar
|
|
169
176
|
|
|
177
|
+
// The user's home directory: `~`
|
|
178
|
+
homeDirectory
|
|
179
|
+
= "~" {
|
|
180
|
+
return annotate([ops.homeDirectory], location());
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
171
184
|
// This is used as a special case at the head of a path, where we want to
|
|
172
185
|
// interpret a colon as part of a text identifier.
|
|
173
186
|
host "HTTP/HTTPS host"
|
|
174
|
-
= identifier:identifier port:(":" @
|
|
187
|
+
= identifier:identifier port:(":" @integerLiteral)? slashFollows:slashFollows? {
|
|
175
188
|
const portText = port ? `:${port[1]}` : "";
|
|
176
|
-
const
|
|
189
|
+
const slashText = slashFollows ? "/" : "";
|
|
190
|
+
const hostText = identifier + portText + slashText;
|
|
177
191
|
return annotate([ops.literal, hostText], location());
|
|
178
192
|
}
|
|
179
193
|
|
|
@@ -181,7 +195,7 @@ identifier "identifier"
|
|
|
181
195
|
= chars:identifierChar+ { return chars.join(""); }
|
|
182
196
|
|
|
183
197
|
identifierChar
|
|
184
|
-
= [^(){}\[\]
|
|
198
|
+
= [^(){}\[\]<>\?!&\|\-=,/:\`"'«»\\ →⇒\t\n\r] // No unescaped whitespace or special chars
|
|
185
199
|
/ @'-' !'>' // Accept a hyphen but not in a single arrow combination
|
|
186
200
|
/ escapedChar
|
|
187
201
|
|
|
@@ -190,55 +204,88 @@ identifierList
|
|
|
190
204
|
return annotate(list, location());
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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());
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
inlineSpace
|
|
202
220
|
= [ \t]
|
|
203
221
|
|
|
204
|
-
|
|
222
|
+
integerLiteral "integer"
|
|
205
223
|
= sign? digits {
|
|
206
224
|
return annotate([ops.literal, parseInt(text())], location());
|
|
207
225
|
}
|
|
208
|
-
|
|
209
|
-
// A
|
|
210
|
-
|
|
211
|
-
=
|
|
212
|
-
return annotate(
|
|
226
|
+
|
|
227
|
+
// A separated list of values
|
|
228
|
+
list "list"
|
|
229
|
+
= values:pipelineExpression|1.., separator| separator? {
|
|
230
|
+
return annotate(values, location());
|
|
213
231
|
}
|
|
214
232
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
+
);
|
|
219
245
|
}
|
|
220
246
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
);
|
|
225
255
|
}
|
|
226
256
|
|
|
227
257
|
multiLineComment
|
|
228
258
|
= "/*" (!"*/" .)* "*/" { return null; }
|
|
229
259
|
|
|
260
|
+
// A namespace reference is a string of letters only, followed by a colon.
|
|
261
|
+
// For the time being, we also allow a leading `@`, which is deprecated.
|
|
262
|
+
namespace
|
|
263
|
+
= at:"@"? chars:[A-Za-z]+ ":" {
|
|
264
|
+
return annotate([ops.builtin, (at ?? "") + chars.join("") + ":"], location());
|
|
265
|
+
}
|
|
266
|
+
|
|
230
267
|
newLine
|
|
231
268
|
= "\n"
|
|
232
269
|
/ "\r\n"
|
|
233
270
|
/ "\r"
|
|
234
271
|
|
|
235
272
|
// A number
|
|
236
|
-
|
|
237
|
-
=
|
|
238
|
-
/
|
|
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
|
+
}
|
|
239
286
|
|
|
240
287
|
// An object literal: `{foo: 1, bar: 2}`
|
|
241
|
-
|
|
288
|
+
objectLiteral "object literal"
|
|
242
289
|
= "{" __ entries:objectEntries? __ closingBrace {
|
|
243
290
|
return annotate(makeObject(entries ?? [], ops.object), location());
|
|
244
291
|
}
|
|
@@ -257,8 +304,11 @@ objectEntry
|
|
|
257
304
|
|
|
258
305
|
// A getter definition inside an object literal: `foo = 1`
|
|
259
306
|
objectGetter "object getter"
|
|
260
|
-
= key:objectKey __ "=" __
|
|
261
|
-
return annotate(
|
|
307
|
+
= key:objectKey __ "=" __ pipeline:pipelineExpression {
|
|
308
|
+
return annotate(
|
|
309
|
+
makeProperty(key, annotate([ops.getter, pipeline], location())),
|
|
310
|
+
location()
|
|
311
|
+
);
|
|
262
312
|
}
|
|
263
313
|
|
|
264
314
|
objectHiddenKey
|
|
@@ -270,8 +320,8 @@ objectKey "object key"
|
|
|
270
320
|
|
|
271
321
|
// A property definition in an object literal: `x: 1`
|
|
272
322
|
objectProperty "object property"
|
|
273
|
-
= key:objectKey __ ":" __
|
|
274
|
-
return annotate(makeProperty(key,
|
|
323
|
+
= key:objectKey __ ":" __ pipeline:pipelineExpression {
|
|
324
|
+
return annotate(makeProperty(key, pipeline), location());
|
|
275
325
|
}
|
|
276
326
|
|
|
277
327
|
// A shorthand reference inside an object literal: `foo`
|
|
@@ -284,99 +334,135 @@ objectPublicKey
|
|
|
284
334
|
= identifier:identifier slash:"/"? {
|
|
285
335
|
return identifier + (slash ?? "");
|
|
286
336
|
}
|
|
287
|
-
/ string:
|
|
337
|
+
/ string:stringLiteral {
|
|
288
338
|
// Remove `ops.literal` from the string code
|
|
289
339
|
return string[1];
|
|
290
340
|
}
|
|
291
341
|
|
|
292
|
-
parameterizedLambda
|
|
293
|
-
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ expr:expr {
|
|
294
|
-
return annotate([ops.lambda, parameters ?? [], expr], location());
|
|
295
|
-
}
|
|
296
|
-
|
|
297
342
|
// Function arguments in parentheses
|
|
298
|
-
|
|
343
|
+
parenthesesArguments "function arguments in parentheses"
|
|
299
344
|
= "(" __ list:list? __ ")" {
|
|
300
345
|
return annotate(list ?? [undefined], location());
|
|
301
346
|
}
|
|
302
347
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
348
|
+
// A slash-separated path of keys: `a/b/c`
|
|
349
|
+
path "slash-separated path"
|
|
350
|
+
// Path with at least a tail
|
|
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());
|
|
306
355
|
}
|
|
307
356
|
|
|
308
|
-
// A slash-separated path of keys
|
|
309
|
-
|
|
310
|
-
=
|
|
311
|
-
|
|
312
|
-
// Remove parts for consecutive slashes
|
|
313
|
-
path = path.filter((part) => part[1] !== "/");
|
|
314
|
-
return annotate(path, location());
|
|
357
|
+
// A slash-separated path of keys that follows a call target
|
|
358
|
+
pathArguments
|
|
359
|
+
= path:path {
|
|
360
|
+
return annotate([ops.traverse, ...path], location());
|
|
315
361
|
}
|
|
316
362
|
|
|
317
|
-
// A
|
|
318
|
-
|
|
319
|
-
= chars:
|
|
320
|
-
|
|
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());
|
|
321
369
|
}
|
|
322
370
|
|
|
323
|
-
|
|
324
|
-
|
|
371
|
+
pathSegment
|
|
372
|
+
= "/" @pathKey?
|
|
373
|
+
|
|
374
|
+
// A single character in a slash-separated path segment
|
|
375
|
+
pathSegmentChar
|
|
325
376
|
// This is more permissive than an identifier. It allows some characters like
|
|
326
377
|
// brackets or quotes that are not allowed in identifiers.
|
|
327
378
|
= [^(){}\[\],:/\\ \t\n\r]
|
|
328
379
|
/ escapedChar
|
|
329
380
|
|
|
330
|
-
// A
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
381
|
+
// A pipeline that starts with a value and optionally applies a series of
|
|
382
|
+
// functions to it.
|
|
383
|
+
pipelineExpression
|
|
384
|
+
= head:shorthandFunction tail:(__ singleArrow __ @shorthandFunction)* {
|
|
385
|
+
return tail.reduce(makePipeline, downgradeReference(head));
|
|
386
|
+
}
|
|
335
387
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
388
|
+
primary
|
|
389
|
+
= literal
|
|
390
|
+
/ arrayLiteral
|
|
391
|
+
/ objectLiteral
|
|
392
|
+
/ group
|
|
393
|
+
/ templateLiteral
|
|
394
|
+
/ reference
|
|
395
|
+
|
|
396
|
+
// Top-level Origami progam with possible shebang directive (which is ignored)
|
|
397
|
+
program "Origami program"
|
|
398
|
+
= shebang? @expression
|
|
399
|
+
|
|
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());
|
|
341
405
|
}
|
|
406
|
+
/ primary
|
|
342
407
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
}
|
|
346
414
|
|
|
347
|
-
|
|
348
|
-
=
|
|
349
|
-
/
|
|
350
|
-
/
|
|
351
|
-
/
|
|
352
|
-
/
|
|
353
|
-
/ "treehttps" { return ops.treeHttps; } // Must come before `treehttp`
|
|
354
|
-
/ "treehttp" { return ops.treeHttp; } // Must come before `tree`
|
|
355
|
-
/ "tree" { return ops.treeHttps; }
|
|
415
|
+
reference
|
|
416
|
+
= rootDirectory
|
|
417
|
+
/ homeDirectory
|
|
418
|
+
/ qualifiedReference
|
|
419
|
+
/ namespace
|
|
420
|
+
/ scopeReference
|
|
356
421
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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());
|
|
360
430
|
}
|
|
361
431
|
|
|
362
|
-
|
|
363
|
-
=
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
return annotate([ops.traverse, head, ...path], location());
|
|
432
|
+
scopeReference "scope reference"
|
|
433
|
+
= identifier:identifier slashFollows:slashFollows? {
|
|
434
|
+
const id = identifier + (slashFollows ? "/" : "");
|
|
435
|
+
return annotate(makeReference(id), location());
|
|
367
436
|
}
|
|
368
437
|
|
|
369
438
|
separator
|
|
370
439
|
= __ "," __
|
|
371
440
|
/ whitespaceWithNewLine
|
|
372
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
|
+
|
|
373
449
|
shebang
|
|
374
450
|
= "#!" [^\n\r]* { return null; }
|
|
375
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
|
+
|
|
376
460
|
sign
|
|
377
461
|
= [+\-]
|
|
378
462
|
|
|
379
|
-
singleArrow
|
|
463
|
+
singleArrow
|
|
464
|
+
= "→"
|
|
465
|
+
/ "->"
|
|
380
466
|
|
|
381
467
|
singleLineComment
|
|
382
468
|
= "//" [^\n\r]* { return null; }
|
|
@@ -390,47 +476,15 @@ singleQuoteStringChar
|
|
|
390
476
|
= !("'" / newLine) @textChar
|
|
391
477
|
|
|
392
478
|
spread
|
|
393
|
-
= ellipsis
|
|
394
|
-
return annotate([ops.spread,
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// A single step in a pipeline, or a top-level expression
|
|
398
|
-
step
|
|
399
|
-
// Literals that can't start a function call
|
|
400
|
-
= number
|
|
401
|
-
// Try functions next; they can start with expression types that follow
|
|
402
|
-
// (array, object, etc.), and we want to parse the larger thing first.
|
|
403
|
-
/ functionComposition
|
|
404
|
-
// Then try parsers that look for a distinctive token at the start: an opening
|
|
405
|
-
// slash, bracket, curly brace, etc.
|
|
406
|
-
/ absoluteFilePath
|
|
407
|
-
/ array
|
|
408
|
-
/ object
|
|
409
|
-
/ lambda
|
|
410
|
-
/ parameterizedLambda
|
|
411
|
-
/ templateLiteral
|
|
412
|
-
/ string
|
|
413
|
-
/ group
|
|
414
|
-
// Things that have a distinctive character, but not at the start
|
|
415
|
-
/ protocolCall
|
|
416
|
-
/ taggedTemplate
|
|
417
|
-
/ scopeTraverse
|
|
418
|
-
// Least distinctive option is a simple scope reference, so it comes last.
|
|
419
|
-
/ scopeReference
|
|
420
|
-
|
|
421
|
-
start
|
|
422
|
-
= number
|
|
479
|
+
= ellipsis __ value:conditionalExpression {
|
|
480
|
+
return annotate([ops.spread, value], location());
|
|
481
|
+
}
|
|
423
482
|
|
|
424
|
-
|
|
483
|
+
stringLiteral "string"
|
|
425
484
|
= doubleQuoteString
|
|
426
485
|
/ singleQuoteString
|
|
427
486
|
/ guillemetString
|
|
428
487
|
|
|
429
|
-
taggedTemplate
|
|
430
|
-
= tag:callTarget "`" contents:templateLiteralContents "`" {
|
|
431
|
-
return annotate(makeTemplate(tag, contents[0], contents[1]), location());
|
|
432
|
-
}
|
|
433
|
-
|
|
434
488
|
// A top-level document defining a template. This is the same as a template
|
|
435
489
|
// literal, but can contain backticks at the top level.
|
|
436
490
|
templateDocument "template"
|
|
@@ -474,11 +528,23 @@ templateLiteralText
|
|
|
474
528
|
|
|
475
529
|
// A substitution in a template literal: `${x}`
|
|
476
530
|
templateSubstitution "template substitution"
|
|
477
|
-
= "${"
|
|
531
|
+
= "${" expression:expression "}" {
|
|
532
|
+
return annotate(expression, location());
|
|
533
|
+
}
|
|
478
534
|
|
|
479
535
|
textChar
|
|
480
536
|
= escapedChar
|
|
481
537
|
/ .
|
|
482
538
|
|
|
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
|
+
= "!"
|
|
548
|
+
|
|
483
549
|
whitespaceWithNewLine
|
|
484
550
|
= inlineSpace* comment? newLine __
|