@weborigami/language 0.3.3 → 0.3.4-jse.5
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/main.js +3 -1
- package/package.json +3 -3
- package/src/compiler/compile.js +13 -3
- package/src/compiler/isOrigamiFrontMatter.js +4 -3
- package/src/compiler/optimize.js +273 -106
- package/src/compiler/origami.pegjs +286 -169
- package/src/compiler/parse.js +2069 -1275
- package/src/compiler/parserHelpers.js +154 -145
- package/src/runtime/HandleExtensionsTransform.js +10 -1
- package/src/runtime/evaluate.js +28 -35
- package/src/runtime/expressionObject.js +17 -11
- package/src/runtime/getHandlers.js +10 -0
- package/src/runtime/handlers.js +18 -54
- package/src/runtime/jsGlobals.js +106 -0
- package/src/runtime/mergeTrees.js +0 -5
- package/src/runtime/ops.js +92 -161
- package/src/runtime/symbols.js +1 -0
- package/src/runtime/{taggedTemplateIndent.js → templateIndent.js} +2 -2
- package/test/compiler/codeHelpers.js +3 -1
- package/test/compiler/compile.test.js +60 -30
- package/test/compiler/optimize.test.js +263 -24
- package/test/compiler/parse.test.js +895 -521
- package/test/runtime/evaluate.test.js +4 -20
- package/test/runtime/expressionObject.test.js +6 -5
- package/test/runtime/handlers.test.js +19 -10
- package/test/runtime/mergeTrees.test.js +0 -5
- package/test/runtime/ops.test.js +103 -82
- package/test/runtime/taggedTemplateIndent.test.js +1 -1
|
@@ -15,19 +15,18 @@ import * as ops from "../runtime/ops.js";
|
|
|
15
15
|
import {
|
|
16
16
|
annotate,
|
|
17
17
|
applyMacro,
|
|
18
|
-
downgradeReference,
|
|
19
18
|
makeArray,
|
|
20
19
|
makeBinaryOperation,
|
|
21
20
|
makeCall,
|
|
22
21
|
makeDeferredArguments,
|
|
23
|
-
|
|
22
|
+
makeDocument,
|
|
24
23
|
makeObject,
|
|
24
|
+
makePath,
|
|
25
25
|
makePipeline,
|
|
26
|
-
makeProperty,
|
|
27
|
-
makeReference,
|
|
28
26
|
makeTemplate,
|
|
29
27
|
makeUnaryOperation,
|
|
30
|
-
makeYamlObject
|
|
28
|
+
makeYamlObject,
|
|
29
|
+
markers,
|
|
31
30
|
} from "./parserHelpers.js";
|
|
32
31
|
import isOrigamiFrontMatter from "./isOrigamiFrontMatter.js";
|
|
33
32
|
|
|
@@ -46,13 +45,45 @@ additiveExpression
|
|
|
46
45
|
|
|
47
46
|
additiveOperator
|
|
48
47
|
= "+"
|
|
49
|
-
/
|
|
48
|
+
/ minus
|
|
49
|
+
|
|
50
|
+
angleBracketLiteral
|
|
51
|
+
= "<" scheme:uriScheme "//"? path:angleBracketPath ">" {
|
|
52
|
+
return annotate([scheme, ...path], location());
|
|
53
|
+
}
|
|
54
|
+
/ "</" path:angleBracketPath ">" {
|
|
55
|
+
const external = annotate([markers.external, "/"], location());
|
|
56
|
+
return annotate([markers.traverse, external, ...path], location());
|
|
57
|
+
}
|
|
58
|
+
/ "<" path:angleBracketPath ">" {
|
|
59
|
+
const [head, ...tail] = path;
|
|
60
|
+
const external = annotate([markers.external, head[1]], location());
|
|
61
|
+
return annotate([markers.traverse, external, ...tail], location());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
angleBracketPath
|
|
65
|
+
= @angleBracketKey|0.., "/"| "/"?
|
|
66
|
+
|
|
67
|
+
// Single key in an angle bracket path, possibly with a trailing slash
|
|
68
|
+
angleBracketKey
|
|
69
|
+
= chars:angleBracketPathChar+ slashFollows:slashFollows? {
|
|
70
|
+
// Append a trailing slash if one follows (but don't consume it)
|
|
71
|
+
const key = chars.join("") + (slashFollows ? "/" : "");
|
|
72
|
+
return annotate([ops.literal, key], location());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A single character in an angle bracket key
|
|
76
|
+
angleBracketPathChar
|
|
77
|
+
// Accept anything that doesn't end the angle bracket key or path
|
|
78
|
+
= [^/>\t\n\r]
|
|
79
|
+
/ escapedChar
|
|
50
80
|
|
|
51
81
|
arguments "function arguments"
|
|
52
82
|
= parenthesesArguments
|
|
53
83
|
/ pathArguments
|
|
54
|
-
/
|
|
84
|
+
/ propertyAccess
|
|
55
85
|
/ computedPropertyAccess
|
|
86
|
+
// / optionalChaining
|
|
56
87
|
/ templateLiteral
|
|
57
88
|
|
|
58
89
|
arrayLiteral "array"
|
|
@@ -85,7 +116,7 @@ arrowFunction
|
|
|
85
116
|
/ conditionalExpression
|
|
86
117
|
|
|
87
118
|
bitwiseAndExpression
|
|
88
|
-
= head:equalityExpression tail:(
|
|
119
|
+
= head:equalityExpression tail:(whitespace @bitwiseAndOperator whitespace @equalityExpression)* {
|
|
89
120
|
return tail.reduce(makeBinaryOperation, head);
|
|
90
121
|
}
|
|
91
122
|
|
|
@@ -93,7 +124,7 @@ bitwiseAndOperator
|
|
|
93
124
|
= @"&" !"&"
|
|
94
125
|
|
|
95
126
|
bitwiseOrExpression
|
|
96
|
-
= head:bitwiseXorExpression tail:(
|
|
127
|
+
= head:bitwiseXorExpression tail:(whitespace @bitwiseOrOperator whitespace @bitwiseXorExpression)* {
|
|
97
128
|
return tail.reduce(makeBinaryOperation, head);
|
|
98
129
|
}
|
|
99
130
|
|
|
@@ -101,7 +132,7 @@ bitwiseOrOperator
|
|
|
101
132
|
= @"|" !"|"
|
|
102
133
|
|
|
103
134
|
bitwiseXorExpression
|
|
104
|
-
= head:bitwiseAndExpression tail:(
|
|
135
|
+
= head:bitwiseAndExpression tail:(whitespace @bitwiseXorOperator whitespace @bitwiseAndExpression)* {
|
|
105
136
|
return tail.reduce(makeBinaryOperation, head);
|
|
106
137
|
}
|
|
107
138
|
|
|
@@ -111,8 +142,11 @@ bitwiseXorOperator
|
|
|
111
142
|
// A function call: `fn(arg)`, possibly part of a chain of function calls, like
|
|
112
143
|
// `fn(arg1)(arg2)(arg3)`.
|
|
113
144
|
callExpression "function call"
|
|
114
|
-
= head:
|
|
115
|
-
return tail.reduce(
|
|
145
|
+
= head:uriExpression tail:arguments* {
|
|
146
|
+
return tail.reduce(
|
|
147
|
+
(target, args) => makeCall(target, args, location()),
|
|
148
|
+
head
|
|
149
|
+
);
|
|
116
150
|
}
|
|
117
151
|
|
|
118
152
|
// A comma-separated list of expressions: `x, y, z`
|
|
@@ -131,7 +165,7 @@ comment "comment"
|
|
|
131
165
|
|
|
132
166
|
computedPropertyAccess
|
|
133
167
|
= __ "[" expression:expression expectClosingBracket {
|
|
134
|
-
return annotate([
|
|
168
|
+
return annotate([markers.property, expression], location());
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
conditionalExpression
|
|
@@ -145,9 +179,9 @@ conditionalExpression
|
|
|
145
179
|
const deferred = makeDeferredArguments(tail);
|
|
146
180
|
return annotate([
|
|
147
181
|
ops.conditional,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
182
|
+
condition,
|
|
183
|
+
deferred[0],
|
|
184
|
+
deferred[1]
|
|
151
185
|
], location());
|
|
152
186
|
}
|
|
153
187
|
|
|
@@ -249,7 +283,7 @@ expectPipelineExpression
|
|
|
249
283
|
}
|
|
250
284
|
|
|
251
285
|
exponentiationExpression
|
|
252
|
-
= left:unaryExpression right:(
|
|
286
|
+
= left:unaryExpression right:(whitespace "**" whitespace @exponentiationExpression)? {
|
|
253
287
|
return right ? annotate([ops.exponentiation, left, right], location()) : left;
|
|
254
288
|
}
|
|
255
289
|
|
|
@@ -286,7 +320,7 @@ frontMatterYaml "YAML front matter"
|
|
|
286
320
|
// An expression in parentheses: `(foo)`
|
|
287
321
|
group "parenthetical group"
|
|
288
322
|
= "(" expression:expression expectClosingParenthesis {
|
|
289
|
-
return annotate(
|
|
323
|
+
return annotate(expression, location());
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
guillemetString "guillemet string"
|
|
@@ -297,50 +331,56 @@ guillemetString "guillemet string"
|
|
|
297
331
|
guillemetStringChar
|
|
298
332
|
= !('»' / newLine) @textChar
|
|
299
333
|
|
|
300
|
-
// The user's home directory: `~`
|
|
301
|
-
homeDirectory
|
|
302
|
-
= "~" {
|
|
303
|
-
return annotate([ops.homeDirectory], location());
|
|
304
|
-
}
|
|
305
|
-
|
|
306
334
|
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
307
335
|
// This is used as a special case at the head of a path, where we want to
|
|
308
336
|
// interpret a colon as part of a text identifier.
|
|
309
337
|
host "HTTP/HTTPS host"
|
|
310
|
-
=
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
338
|
+
= name:hostname port:(":" @integerLiteral)? slashFollows:slashFollows? {
|
|
339
|
+
const portText = port ? `:${port[1]}` : "";
|
|
340
|
+
const slashText = slashFollows ? "/" : "";
|
|
341
|
+
const host = name + portText + slashText;
|
|
342
|
+
return annotate([ops.literal, host], location());
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
hostname
|
|
346
|
+
= key {
|
|
347
|
+
return text();
|
|
315
348
|
}
|
|
316
349
|
|
|
317
|
-
|
|
318
|
-
|
|
350
|
+
// JavaScript-compatible identifier
|
|
351
|
+
identifier
|
|
352
|
+
= id:$( identifierStart identifierPart* ) {
|
|
353
|
+
return id;
|
|
354
|
+
}
|
|
319
355
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
356
|
+
// Identifier as a literal
|
|
357
|
+
identifierLiteral
|
|
358
|
+
= id:identifier {
|
|
359
|
+
return annotate([ops.literal, id], location());
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Continuation of a JavaScript identifier
|
|
363
|
+
// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#prod-IdentifierPart
|
|
364
|
+
identifierPart "JavaScript identifier continuation"
|
|
365
|
+
= char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
|
|
366
|
+
|
|
367
|
+
// Start of a JavaScript identifier
|
|
368
|
+
// https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#prod-IdentifierStart
|
|
369
|
+
identifierStart "JavaScript identifier start"
|
|
370
|
+
= char:. &{ return char.match(/[$_\p{ID_Start}]/u) }
|
|
324
371
|
|
|
325
372
|
implicitParenthesesCallExpression "function call with implicit parentheses"
|
|
326
373
|
= head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
|
|
327
|
-
return args ? makeCall(head, args) : head;
|
|
374
|
+
return args ? makeCall(head, args, location()) : head;
|
|
328
375
|
}
|
|
329
376
|
|
|
330
377
|
// A separated list of values for an implicit parens call. This differs from
|
|
331
378
|
// `list` in that the value term can't be a pipeline.
|
|
332
379
|
implicitParensthesesArguments
|
|
333
|
-
= values:shorthandFunction|1.., separator| separator? {
|
|
380
|
+
= shellMode values:shorthandFunction|1.., separator| separator? {
|
|
334
381
|
return annotate(values, location());
|
|
335
382
|
}
|
|
336
383
|
|
|
337
|
-
inherited
|
|
338
|
-
= rootDirectory
|
|
339
|
-
/ homeDirectory
|
|
340
|
-
/ qualifiedReference
|
|
341
|
-
/ namespace
|
|
342
|
-
/ scopeReference
|
|
343
|
-
|
|
344
384
|
inlineSpace
|
|
345
385
|
= [ \t]
|
|
346
386
|
|
|
@@ -349,24 +389,32 @@ integerLiteral "integer"
|
|
|
349
389
|
return annotate([ops.literal, parseInt(text())], location());
|
|
350
390
|
}
|
|
351
391
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
jsIdentifierPart "JavaScript identifier continuation"
|
|
358
|
-
= char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
|
|
392
|
+
// A key in a path or an expression that looks like one
|
|
393
|
+
key
|
|
394
|
+
= keyCharStart keyChar* {
|
|
395
|
+
return text();
|
|
396
|
+
}
|
|
359
397
|
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
398
|
+
// Character after the first in a key
|
|
399
|
+
keyChar
|
|
400
|
+
= keyCharStart
|
|
401
|
+
// Also allow some math operators (not slash)
|
|
402
|
+
/ "!"
|
|
403
|
+
/ "+"
|
|
404
|
+
/ minus
|
|
405
|
+
/ "*"
|
|
406
|
+
/ "%"
|
|
407
|
+
/ "&"
|
|
408
|
+
/ "|"
|
|
409
|
+
/ "^"
|
|
364
410
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
411
|
+
// First character in a key
|
|
412
|
+
keyCharStart
|
|
413
|
+
// All JS identifier characters
|
|
414
|
+
= char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
|
|
415
|
+
/ "."
|
|
416
|
+
/ "~"
|
|
417
|
+
/ "@"
|
|
370
418
|
|
|
371
419
|
// A separated list of values
|
|
372
420
|
list "list"
|
|
@@ -374,16 +422,12 @@ list "list"
|
|
|
374
422
|
return annotate(values, location());
|
|
375
423
|
}
|
|
376
424
|
|
|
377
|
-
literal
|
|
378
|
-
= numericLiteral
|
|
379
|
-
/ stringLiteral
|
|
380
|
-
|
|
381
425
|
logicalAndExpression
|
|
382
426
|
= head:bitwiseOrExpression tail:(__ "&&" __ @bitwiseOrExpression)* {
|
|
383
427
|
return tail.length === 0
|
|
384
428
|
? head
|
|
385
429
|
: annotate(
|
|
386
|
-
[ops.logicalAnd,
|
|
430
|
+
[ops.logicalAnd, head, ...makeDeferredArguments(tail)],
|
|
387
431
|
location()
|
|
388
432
|
);
|
|
389
433
|
}
|
|
@@ -393,11 +437,17 @@ logicalOrExpression
|
|
|
393
437
|
return tail.length === 0
|
|
394
438
|
? head
|
|
395
439
|
: annotate(
|
|
396
|
-
[ops.logicalOr,
|
|
440
|
+
[ops.logicalOr, head, ...makeDeferredArguments(tail)],
|
|
397
441
|
location()
|
|
398
442
|
);
|
|
399
443
|
}
|
|
400
444
|
|
|
445
|
+
// Unary or binary minus operator
|
|
446
|
+
minus
|
|
447
|
+
// Don't match a front matter delimiter or pipeline operator. For some reason,
|
|
448
|
+
// the negative lookahead !"--\n" doesn't work.
|
|
449
|
+
= @"-" !"-\n" !">"
|
|
450
|
+
|
|
401
451
|
multiLineComment
|
|
402
452
|
= "/*" (!"*/" .)* "*/" { return null; }
|
|
403
453
|
|
|
@@ -411,11 +461,16 @@ multiplicativeOperator
|
|
|
411
461
|
/ "/"
|
|
412
462
|
/ "%"
|
|
413
463
|
|
|
414
|
-
// A
|
|
415
|
-
|
|
416
|
-
=
|
|
417
|
-
|
|
418
|
-
|
|
464
|
+
// A new expression: `new Foo()`
|
|
465
|
+
newExpression
|
|
466
|
+
= "new" __ head:pathLiteral tail:parenthesesArguments? {
|
|
467
|
+
const args = tail?.[0] !== undefined ? tail : [];
|
|
468
|
+
return annotate([ops.construct, head, ...args], location());
|
|
469
|
+
}
|
|
470
|
+
/ "new:" head:pathLiteral tail:parenthesesArguments {
|
|
471
|
+
const args = tail?.[0] !== undefined ? tail : [];
|
|
472
|
+
return annotate([ops.construct, head, ...args], location());
|
|
473
|
+
}
|
|
419
474
|
|
|
420
475
|
newLine
|
|
421
476
|
= "\n"
|
|
@@ -432,7 +487,7 @@ nullishCoalescingExpression
|
|
|
432
487
|
return tail.length === 0
|
|
433
488
|
? head
|
|
434
489
|
: annotate(
|
|
435
|
-
[ops.nullishCoalescing,
|
|
490
|
+
[ops.nullishCoalescing, head, ...makeDeferredArguments(tail)],
|
|
436
491
|
location()
|
|
437
492
|
);
|
|
438
493
|
}
|
|
@@ -458,10 +513,8 @@ objectEntry
|
|
|
458
513
|
// A getter definition inside an object literal: `foo = 1`
|
|
459
514
|
objectGetter "object getter"
|
|
460
515
|
= key:objectKey __ "=" __ pipeline:expectPipelineExpression {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
location()
|
|
464
|
-
);
|
|
516
|
+
const getter = annotate([ops.getter, pipeline], location());
|
|
517
|
+
return annotate([key, getter], location());
|
|
465
518
|
}
|
|
466
519
|
|
|
467
520
|
objectHiddenKey
|
|
@@ -474,28 +527,42 @@ objectKey "object key"
|
|
|
474
527
|
// A property definition in an object literal: `x: 1`
|
|
475
528
|
objectProperty "object property"
|
|
476
529
|
= key:objectKey __ ":" __ pipeline:expectPipelineExpression {
|
|
477
|
-
return annotate(
|
|
530
|
+
return annotate([key, pipeline], location());
|
|
478
531
|
}
|
|
479
532
|
|
|
480
533
|
// A shorthand reference inside an object literal: `foo`
|
|
481
534
|
objectShorthandProperty "object identifier"
|
|
482
535
|
= key:objectPublicKey {
|
|
483
|
-
const
|
|
484
|
-
|
|
536
|
+
const reference = annotate([markers.reference, key], location());
|
|
537
|
+
const traverse = annotate([markers.traverse, reference], location());
|
|
538
|
+
return annotate([key, traverse], location());
|
|
539
|
+
}
|
|
540
|
+
/ path:angleBracketLiteral {
|
|
541
|
+
let lastKey = path.at(-1);
|
|
542
|
+
if (lastKey instanceof Array) {
|
|
543
|
+
lastKey = lastKey[1]; // get scope identifier or literal
|
|
485
544
|
}
|
|
545
|
+
return annotate([lastKey, path], location());
|
|
546
|
+
}
|
|
486
547
|
|
|
487
548
|
objectPublicKey
|
|
488
|
-
=
|
|
489
|
-
return
|
|
549
|
+
= key:key slash:"/"? {
|
|
550
|
+
return text();
|
|
490
551
|
}
|
|
491
552
|
/ string:stringLiteral {
|
|
492
|
-
|
|
493
|
-
|
|
553
|
+
// Remove `ops.literal` from the string code
|
|
554
|
+
return string[1];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
optionalChaining
|
|
558
|
+
= __ "?." __ property:identifier {
|
|
559
|
+
return annotate([ops.optionalTraverse, property], location());
|
|
494
560
|
}
|
|
495
561
|
|
|
562
|
+
// Name of a unction parameter
|
|
496
563
|
parameter
|
|
497
|
-
=
|
|
498
|
-
return annotate([ops.literal,
|
|
564
|
+
= key:key {
|
|
565
|
+
return annotate([ops.literal, key], location());
|
|
499
566
|
}
|
|
500
567
|
|
|
501
568
|
parameterList
|
|
@@ -505,11 +572,8 @@ parameterList
|
|
|
505
572
|
|
|
506
573
|
// A list with a single identifier
|
|
507
574
|
parameterSingleton
|
|
508
|
-
=
|
|
509
|
-
return annotate(
|
|
510
|
-
[annotate([ops.literal, identifier], location())],
|
|
511
|
-
location()
|
|
512
|
-
);
|
|
575
|
+
= param:parameter {
|
|
576
|
+
return annotate([param], location());
|
|
513
577
|
}
|
|
514
578
|
|
|
515
579
|
// Function arguments in parentheses
|
|
@@ -518,78 +582,88 @@ parenthesesArguments "function arguments in parentheses"
|
|
|
518
582
|
return annotate(list ?? [undefined], location());
|
|
519
583
|
}
|
|
520
584
|
|
|
521
|
-
// A slash-separated path of keys
|
|
522
|
-
|
|
523
|
-
// Path with at least a tail
|
|
524
|
-
= segments:pathSegment|1..| {
|
|
525
|
-
// Drop empty segments that represent consecutive or final slashes
|
|
526
|
-
segments = segments.filter(segment => segment);
|
|
527
|
-
return annotate(segments, location());
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// A slash-separated path of keys that follows a call target
|
|
585
|
+
// A slash-separated path of keys that follows a call target, such as the path
|
|
586
|
+
// after the slash in `(x)/y/z`
|
|
531
587
|
pathArguments
|
|
532
|
-
=
|
|
533
|
-
|
|
588
|
+
= "/" keys:pathKeys? {
|
|
589
|
+
const args = keys ?? [];
|
|
590
|
+
return annotate([markers.traverse, ...args], location());
|
|
534
591
|
}
|
|
535
592
|
|
|
536
|
-
//
|
|
537
|
-
|
|
538
|
-
=
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
593
|
+
// Sequence of keys that may each have trailing slashes
|
|
594
|
+
pathKeys
|
|
595
|
+
= pathSegment|1..|
|
|
596
|
+
|
|
597
|
+
// A path without angle brackets
|
|
598
|
+
pathLiteral
|
|
599
|
+
= keys:pathKeys {
|
|
600
|
+
return makePath(keys);
|
|
601
|
+
}
|
|
543
602
|
|
|
603
|
+
// A path key with an optional trailing slash
|
|
544
604
|
pathSegment
|
|
545
|
-
= "/"
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
/ escapedChar
|
|
605
|
+
= key:key "/"? {
|
|
606
|
+
return annotate([ops.literal, text()], location());
|
|
607
|
+
}
|
|
608
|
+
// A single slash is a path key
|
|
609
|
+
/ "/" {
|
|
610
|
+
return annotate([ops.literal, text()], location());
|
|
611
|
+
}
|
|
553
612
|
|
|
554
613
|
// A pipeline that starts with a value and optionally applies a series of
|
|
555
614
|
// functions to it.
|
|
556
615
|
pipelineExpression
|
|
557
616
|
= head:shorthandFunction tail:(__ singleArrow __ @shorthandFunction)* {
|
|
558
617
|
return annotate(
|
|
559
|
-
tail.reduce(makePipeline,
|
|
618
|
+
tail.reduce((arg, fn) => makePipeline(arg, fn, location()), head),
|
|
560
619
|
location()
|
|
561
620
|
);
|
|
562
621
|
}
|
|
563
622
|
|
|
564
623
|
primary
|
|
565
|
-
|
|
624
|
+
// The following start with distinct characters
|
|
625
|
+
= stringLiteral
|
|
566
626
|
/ arrayLiteral
|
|
567
627
|
/ objectLiteral
|
|
568
628
|
/ group
|
|
629
|
+
/ angleBracketLiteral
|
|
630
|
+
/ regexLiteral
|
|
569
631
|
/ templateLiteral
|
|
570
|
-
|
|
632
|
+
|
|
633
|
+
// These are more ambiguous
|
|
634
|
+
/ @numericLiteral !keyChar // numbers + chars would be a key
|
|
635
|
+
/ pathLiteral
|
|
571
636
|
|
|
572
637
|
// Top-level Origami progam with possible shebang directive (which is ignored)
|
|
573
638
|
program "Origami program"
|
|
574
639
|
= shebang? @expression
|
|
575
640
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
641
|
+
propertyAccess
|
|
642
|
+
= __ "." __ property:identifierLiteral {
|
|
643
|
+
return annotate([markers.property, property], location());
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
regexFlags
|
|
647
|
+
= flags:[gimuy]* {
|
|
648
|
+
return flags.join("");
|
|
581
649
|
}
|
|
582
|
-
/ primary
|
|
583
650
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
return makeCall(fn, [literal]);
|
|
651
|
+
regexLiteral
|
|
652
|
+
= "/" chars:regexLiteralChar* "/" flags:regexFlags? {
|
|
653
|
+
const regex = new RegExp(chars.join(""), flags);
|
|
654
|
+
return annotate([ops.literal, regex], location());
|
|
589
655
|
}
|
|
590
656
|
|
|
657
|
+
regexLiteralChar
|
|
658
|
+
= [^/\n\r] // No unescaped slashes or newlines
|
|
659
|
+
/ escapedChar
|
|
660
|
+
|
|
591
661
|
relationalExpression
|
|
592
|
-
|
|
662
|
+
// We disallow a newline before the relational operator to support a newline
|
|
663
|
+
// as a separator in an object literal that has an object shorthand property
|
|
664
|
+
// with an angle bracket path. Otherwise the opening angle bracket would be
|
|
665
|
+
// interpreted as a relational operator.
|
|
666
|
+
= head:shiftExpression tail:(inlineSpace @relationalOperator __ @shiftExpression)* {
|
|
593
667
|
return tail.reduce(makeBinaryOperation, head);
|
|
594
668
|
}
|
|
595
669
|
|
|
@@ -599,29 +673,16 @@ relationalOperator
|
|
|
599
673
|
/ ">="
|
|
600
674
|
/ ">"
|
|
601
675
|
|
|
602
|
-
// A top-level folder below the root: `/foo`
|
|
603
|
-
// or the root folder itself: `/`
|
|
604
|
-
rootDirectory
|
|
605
|
-
= "/" key:pathKey {
|
|
606
|
-
return annotate([ops.rootDirectory, key], location());
|
|
607
|
-
}
|
|
608
|
-
/ "/" !"/" {
|
|
609
|
-
return annotate([ops.rootDirectory], location());
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
scopeReference "scope reference"
|
|
613
|
-
= identifier:identifier slashFollows:slashFollows? {
|
|
614
|
-
const id = identifier + (slashFollows ? "/" : "");
|
|
615
|
-
return annotate(makeReference(id), location());
|
|
616
|
-
}
|
|
617
|
-
|
|
618
676
|
separator
|
|
619
677
|
= __ "," __
|
|
620
|
-
/ whitespaceWithNewLine
|
|
678
|
+
/ @whitespaceWithNewLine
|
|
621
679
|
|
|
622
680
|
shebang
|
|
623
681
|
= "#!" [^\n\r]* { return null; }
|
|
624
682
|
|
|
683
|
+
shellMode
|
|
684
|
+
= &{ return options.mode === "shell" }
|
|
685
|
+
|
|
625
686
|
shiftExpression
|
|
626
687
|
= head:additiveExpression tail:(__ @shiftOperator __ @additiveExpression)* {
|
|
627
688
|
return tail.reduce(makeBinaryOperation, head);
|
|
@@ -635,7 +696,7 @@ shiftOperator
|
|
|
635
696
|
// A shorthand lambda expression: `=foo(_)`
|
|
636
697
|
shorthandFunction "lambda function"
|
|
637
698
|
// Avoid a following equal sign (for an equality)
|
|
638
|
-
= "=" !"=" __ definition:implicitParenthesesCallExpression {
|
|
699
|
+
= shellMode "=" !"=" __ definition:implicitParenthesesCallExpression {
|
|
639
700
|
const lambdaParameters = annotate(
|
|
640
701
|
[annotate([ops.literal, "_"], location())],
|
|
641
702
|
location()
|
|
@@ -665,6 +726,12 @@ slash
|
|
|
665
726
|
return annotate([ops.literal, "/"], location());
|
|
666
727
|
}
|
|
667
728
|
|
|
729
|
+
// One or more consecutive slashes
|
|
730
|
+
slashes
|
|
731
|
+
= "/"+ {
|
|
732
|
+
return annotate([ops.literal, "/"], location());
|
|
733
|
+
}
|
|
734
|
+
|
|
668
735
|
// Check whether next character is a slash without consuming input
|
|
669
736
|
slashFollows
|
|
670
737
|
// This expression returned `undefined` if successful; we convert to `true`
|
|
@@ -673,27 +740,20 @@ slashFollows
|
|
|
673
740
|
}
|
|
674
741
|
|
|
675
742
|
spreadElement
|
|
676
|
-
= ellipsis __ value:
|
|
743
|
+
= ellipsis __ value:expectPipelineExpression {
|
|
677
744
|
return annotate([ops.spread, value], location());
|
|
678
745
|
}
|
|
679
746
|
|
|
680
747
|
stringLiteral "string"
|
|
681
748
|
= doubleQuoteString
|
|
682
749
|
/ singleQuoteString
|
|
683
|
-
/ guillemetString
|
|
750
|
+
/ shellMode @guillemetString
|
|
684
751
|
|
|
685
752
|
// The body of a template document is a kind of template literal that can
|
|
686
753
|
// contain backticks at the top level.
|
|
687
754
|
templateBody "template"
|
|
688
755
|
= head:templateBodyText tail:(templateSubstitution templateBodyText)* {
|
|
689
|
-
|
|
690
|
-
[annotate([ops.literal, "_"], location())],
|
|
691
|
-
location()
|
|
692
|
-
);
|
|
693
|
-
return annotate(
|
|
694
|
-
[ops.lambda, lambdaParameters, makeTemplate(ops.templateIndent, head, tail, location())],
|
|
695
|
-
location()
|
|
696
|
-
);
|
|
756
|
+
return makeTemplate(ops.templateIndent, head, tail, location());
|
|
697
757
|
}
|
|
698
758
|
|
|
699
759
|
// Template document bodies can contain backticks at the top level
|
|
@@ -707,18 +767,27 @@ templateBodyText "template text"
|
|
|
707
767
|
|
|
708
768
|
templateDocument "template document"
|
|
709
769
|
= front:frontMatterExpression __ body:templateBody {
|
|
710
|
-
|
|
770
|
+
const macroName = text().includes("@template") ? "@template" : "_template";
|
|
771
|
+
return annotate(applyMacro(front, macroName, body), location());
|
|
711
772
|
}
|
|
712
|
-
/ front:frontMatterYaml
|
|
713
|
-
return front
|
|
714
|
-
|
|
715
|
-
|
|
773
|
+
/ front:frontMatterYaml body:templateBody {
|
|
774
|
+
return makeDocument(front, body, location());
|
|
775
|
+
}
|
|
776
|
+
/ body:templateBody {
|
|
777
|
+
if (options.front) {
|
|
778
|
+
return makeDocument(options.front, body, location());
|
|
779
|
+
}
|
|
780
|
+
const lambdaParameters = annotate(
|
|
781
|
+
[annotate([ops.literal, "_"], location())],
|
|
782
|
+
location()
|
|
783
|
+
);
|
|
784
|
+
return annotate([ops.lambda, lambdaParameters, body], location());
|
|
716
785
|
}
|
|
717
786
|
|
|
718
787
|
// A backtick-quoted template literal
|
|
719
788
|
templateLiteral "template literal"
|
|
720
789
|
= "`" head:templateLiteralText tail:(templateSubstitution templateLiteralText)* expectBacktick {
|
|
721
|
-
return makeTemplate(ops.
|
|
790
|
+
return makeTemplate(ops.templateTree, head, tail, location());
|
|
722
791
|
}
|
|
723
792
|
|
|
724
793
|
templateLiteralChar
|
|
@@ -742,16 +811,64 @@ textChar
|
|
|
742
811
|
|
|
743
812
|
// A unary prefix operator: `!x`
|
|
744
813
|
unaryExpression
|
|
745
|
-
= operator:unaryOperator __ expression:
|
|
814
|
+
= operator:unaryOperator __ expression:expectExpression {
|
|
746
815
|
return makeUnaryOperation(operator, expression, location());
|
|
747
816
|
}
|
|
748
817
|
/ callExpression
|
|
749
818
|
|
|
819
|
+
// URI
|
|
820
|
+
uri
|
|
821
|
+
// Double slashes after colon: `https://example.com/index.html`
|
|
822
|
+
= scheme:uriScheme "//" host:host path:("/" uriPath)? {
|
|
823
|
+
const rest = path ? path[1] : [];
|
|
824
|
+
const keys = annotate([host, ...rest], location());
|
|
825
|
+
return makeCall(scheme, keys, location());
|
|
826
|
+
}
|
|
827
|
+
// No slashes after colon: `files:assets`
|
|
828
|
+
/ scheme:uriScheme keys:pathKeys {
|
|
829
|
+
return makeCall(scheme, keys, location());
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// URI expression
|
|
833
|
+
uriExpression
|
|
834
|
+
= uri
|
|
835
|
+
/ newExpression
|
|
836
|
+
/ primary
|
|
837
|
+
|
|
838
|
+
// A single key in a path, possibly with trailing slash: `a/`, `b`
|
|
839
|
+
uriKey
|
|
840
|
+
= chars:uriKeyChar+ "/"? {
|
|
841
|
+
return annotate([ops.literal, text()], location());
|
|
842
|
+
}
|
|
843
|
+
/ "/" {
|
|
844
|
+
// A single slash is a path key
|
|
845
|
+
return annotate([ops.literal, ""], location());
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// A single character in a URI key
|
|
849
|
+
uriKeyChar
|
|
850
|
+
// Accept anything that doesn't end the URI key or path
|
|
851
|
+
= [^/,\)\]\}\s]
|
|
852
|
+
/ escapedChar
|
|
853
|
+
|
|
854
|
+
// A slash-separated path of keys: `a/b/c`
|
|
855
|
+
uriPath "slash-separated path"
|
|
856
|
+
= keys:uriKey|1..| {
|
|
857
|
+
return annotate(keys, location());
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// URI scheme, commonly called a protocol
|
|
861
|
+
// See https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
|
|
862
|
+
uriScheme
|
|
863
|
+
= [a-z][a-z0-9+-.]*[:] {
|
|
864
|
+
return annotate([markers.global, text()], location());
|
|
865
|
+
}
|
|
866
|
+
|
|
750
867
|
unaryOperator
|
|
751
868
|
= "!"
|
|
752
869
|
/ "+"
|
|
753
|
-
/ "
|
|
754
|
-
/
|
|
870
|
+
/ @"~" ![\/\)\]\}] // don't match `~/` or end of term
|
|
871
|
+
/ minus
|
|
755
872
|
|
|
756
873
|
whitespace
|
|
757
874
|
= inlineSpace
|