@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.
- package/index.ts +1 -0
- package/package.json +6 -4
- package/src/compiler/compile.js +33 -15
- package/src/compiler/origami.pegjs +309 -195
- package/src/compiler/parse.js +2753 -1658
- package/src/compiler/parserHelpers.js +187 -45
- package/src/runtime/ops.js +246 -54
- 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 +588 -359
- 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 +242 -20
|
@@ -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
|
+
makeBinaryOperation,
|
|
20
|
+
makeCall,
|
|
21
|
+
makeDeferredArguments,
|
|
14
22
|
makeObject,
|
|
15
23
|
makePipeline,
|
|
16
24
|
makeProperty,
|
|
17
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
37
|
-
=
|
|
38
|
-
/
|
|
39
|
-
return annotate([ops.traverse, ...path], location());
|
|
40
|
-
}
|
|
43
|
+
additiveOperator
|
|
44
|
+
= "+"
|
|
45
|
+
/ "-"
|
|
41
46
|
|
|
42
|
-
|
|
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
|
-
/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
= __ @
|
|
207
|
+
= __ @commaExpression __
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
=
|
|
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
|
-
= "("
|
|
171
|
-
|
|
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
|
-
|
|
228
|
+
// The user's home directory: `~`
|
|
229
|
+
homeDirectory
|
|
183
230
|
= "~" {
|
|
184
|
-
return annotate([ops.
|
|
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:(":" @
|
|
238
|
+
= identifier:identifier port:(":" @integerLiteral)? slashFollows:slashFollows? {
|
|
192
239
|
const portText = port ? `:${port[1]}` : "";
|
|
193
|
-
const slashText =
|
|
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
|
-
= [^(){}\[\]
|
|
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
|
-
|
|
212
|
-
= inlineSpace+ @
|
|
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
|
-
|
|
218
|
-
=
|
|
273
|
+
integerLiteral "integer"
|
|
274
|
+
= digits {
|
|
219
275
|
return annotate([ops.literal, parseInt(text())], location());
|
|
220
276
|
}
|
|
221
|
-
|
|
222
|
-
// A
|
|
223
|
-
|
|
224
|
-
=
|
|
225
|
-
return annotate(
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
266
|
-
=
|
|
267
|
-
/
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
=
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
353
|
-
|
|
354
|
-
= chars:
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
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
|
-
|
|
374
|
-
= head:
|
|
375
|
-
return
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
/
|
|
384
|
-
/
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
396
|
-
=
|
|
397
|
-
const
|
|
398
|
-
|
|
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
|
-
|
|
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:
|
|
561
|
+
= ellipsis __ value:conditionalExpression {
|
|
429
562
|
return annotate([ops.spread, value], location());
|
|
430
563
|
}
|
|
431
564
|
|
|
432
|
-
|
|
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
|
-
= "${"
|
|
613
|
+
= "${" expression:expression "}" {
|
|
614
|
+
return annotate(expression, location());
|
|
615
|
+
}
|
|
489
616
|
|
|
490
617
|
textChar
|
|
491
618
|
= escapedChar
|
|
492
619
|
/ .
|
|
493
620
|
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
/
|
|
504
|
-
|
|
505
|
-
|
|
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 __
|