@weborigami/language 0.6.4 → 0.6.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 +1 -0
- package/package.json +2 -2
- package/src/compiler/optimize.js +3 -6
- package/src/compiler/origami.pegjs +184 -52
- package/src/compiler/parse.js +2156 -1149
- package/src/compiler/parserHelpers.js +172 -10
- package/src/runtime/ops.js +30 -9
- package/test/compiler/compile.test.js +39 -0
- package/test/compiler/optimize.test.js +2 -1
- package/test/compiler/parse.test.js +346 -93
- package/test/runtime/ops.test.js +32 -4
package/main.js
CHANGED
|
@@ -12,6 +12,7 @@ export { formatError, highlightError } from "./src/runtime/errors.js";
|
|
|
12
12
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
13
13
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
|
14
14
|
export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
15
|
+
export { default as expressionObject } from "./src/runtime/expressionObject.js";
|
|
15
16
|
export * from "./src/runtime/handleExtension.js";
|
|
16
17
|
export { default as handleExtension } from "./src/runtime/handleExtension.js";
|
|
17
18
|
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"typescript": "5.9.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.6.
|
|
14
|
+
"@weborigami/async-tree": "0.6.5",
|
|
15
15
|
"exif-parser": "0.1.12",
|
|
16
16
|
"watcher": "2.3.1",
|
|
17
17
|
"yaml": "2.8.1"
|
package/src/compiler/optimize.js
CHANGED
|
@@ -47,9 +47,9 @@ export default function optimize(code, options = {}) {
|
|
|
47
47
|
return resolvePath(code, globals, parent, locals, cache);
|
|
48
48
|
|
|
49
49
|
case ops.lambda:
|
|
50
|
-
const parameters = args[
|
|
50
|
+
const parameters = args[1];
|
|
51
51
|
if (parameters.length > 0) {
|
|
52
|
-
const paramNames = parameters.map((param) => param[
|
|
52
|
+
const paramNames = parameters.map((param) => param[0]);
|
|
53
53
|
locals.push({
|
|
54
54
|
type: REFERENCE_PARAM,
|
|
55
55
|
names: paramNames,
|
|
@@ -76,10 +76,7 @@ export default function optimize(code, options = {}) {
|
|
|
76
76
|
// Optimize children
|
|
77
77
|
const optimized = annotate(
|
|
78
78
|
code.map((child, index) => {
|
|
79
|
-
|
|
80
|
-
if (op === ops.lambda && index === 1) {
|
|
81
|
-
return child;
|
|
82
|
-
} else if (op === ops.object && index > 0) {
|
|
79
|
+
if (op === ops.object && index > 0) {
|
|
83
80
|
const [key, value] = child;
|
|
84
81
|
const adjustedLocals = avoidLocalRecursion(locals, key);
|
|
85
82
|
const optimizedKey =
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
makeCallChain,
|
|
22
22
|
makeDeferredArguments,
|
|
23
23
|
makeDocument,
|
|
24
|
+
makeLambda,
|
|
24
25
|
makeObject,
|
|
25
26
|
makePath,
|
|
26
27
|
makePipeline,
|
|
@@ -101,18 +102,18 @@ arrayEntries
|
|
|
101
102
|
arrayEntry
|
|
102
103
|
= spreadElement
|
|
103
104
|
/ pipelineExpression
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
/ &separator {
|
|
106
|
+
// Missing value is allowed
|
|
106
107
|
return annotate([ops.literal, undefined], location());
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
arrowFunction
|
|
110
|
-
= ("async" __)? "(" __ parameters:
|
|
111
|
-
|
|
112
|
-
return
|
|
111
|
+
= ("async" __)? "(" __ parameters:paramList? __ ")" __ doubleArrow __ body:expectPipelineExpression {
|
|
112
|
+
parameters ??= annotate([], location());
|
|
113
|
+
return makeLambda(parameters, body, location());
|
|
113
114
|
}
|
|
114
|
-
/
|
|
115
|
-
return
|
|
115
|
+
/ parameters:paramSingleton __ doubleArrow __ body:expectPipelineExpression {
|
|
116
|
+
return makeLambda(parameters, body, location());
|
|
116
117
|
}
|
|
117
118
|
/ conditionalExpression
|
|
118
119
|
|
|
@@ -189,9 +190,6 @@ conditionalExpression
|
|
|
189
190
|
deferred[1]
|
|
190
191
|
], location());
|
|
191
192
|
}
|
|
192
|
-
|
|
193
|
-
digits
|
|
194
|
-
= @[0-9]+
|
|
195
193
|
|
|
196
194
|
doubleArrow = "⇒" / "=>"
|
|
197
195
|
|
|
@@ -269,7 +267,13 @@ expectFrontDelimiter
|
|
|
269
267
|
error("Expected \"---\"");
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
|
|
270
|
+
expectLeftGuillemet
|
|
271
|
+
= '«'
|
|
272
|
+
/ .? {
|
|
273
|
+
error("Expected closing guillemet");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
expectRightGuillemet
|
|
273
277
|
= '»'
|
|
274
278
|
/ .? {
|
|
275
279
|
error("Expected closing guillemet");
|
|
@@ -303,11 +307,6 @@ exponentiationExpression
|
|
|
303
307
|
expression
|
|
304
308
|
= __ @commaExpression __
|
|
305
309
|
|
|
306
|
-
floatLiteral "floating-point number"
|
|
307
|
-
= digits? "." digits {
|
|
308
|
-
return annotate([ops.literal, parseFloat(text())], location());
|
|
309
|
-
}
|
|
310
|
-
|
|
311
310
|
// Marker for the beginning or end of front matter
|
|
312
311
|
frontDelimiter
|
|
313
312
|
= "---\n"
|
|
@@ -336,19 +335,22 @@ group "parenthetical group"
|
|
|
336
335
|
}
|
|
337
336
|
|
|
338
337
|
guillemetString "guillemet string"
|
|
339
|
-
=
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
= "«" chars:guillemetStringChar* expectRightGuillemet {
|
|
339
|
+
return annotate([ops.literal, chars.join("")], location());
|
|
340
|
+
}
|
|
341
|
+
/ "»" chars:guillemetStringChar* expectLeftGuillemet {
|
|
342
|
+
return annotate([ops.literal, chars.join("")], location());
|
|
343
|
+
}
|
|
342
344
|
|
|
343
345
|
guillemetStringChar
|
|
344
|
-
= !(
|
|
346
|
+
= !("«" / "»" / newLine) @textChar
|
|
345
347
|
|
|
346
348
|
// A host identifier that may include a colon and port number: `example.com:80`.
|
|
347
349
|
// This is used as a special case at the head of a path, where we want to
|
|
348
350
|
// interpret a colon as part of a text identifier.
|
|
349
351
|
host "HTTP/HTTPS host"
|
|
350
|
-
= name:hostname port:
|
|
351
|
-
const portText = port
|
|
352
|
+
= name:hostname port:port? slashFollows:slashFollows? {
|
|
353
|
+
const portText = port ?? "";
|
|
352
354
|
const slashText = slashFollows ? "/" : "";
|
|
353
355
|
const host = name + portText + slashText;
|
|
354
356
|
return annotate([ops.literal, host], location());
|
|
@@ -407,11 +409,6 @@ implicitParenthesesCallExpression "function call with implicit parentheses"
|
|
|
407
409
|
inlineSpace
|
|
408
410
|
= [ \t]
|
|
409
411
|
|
|
410
|
-
integerLiteral "integer"
|
|
411
|
-
= digits {
|
|
412
|
-
return annotate([ops.literal, parseInt(text())], location());
|
|
413
|
-
}
|
|
414
|
-
|
|
415
412
|
// A key in a path or an expression that looks like one
|
|
416
413
|
key
|
|
417
414
|
= keyCharStart keyChar* {
|
|
@@ -435,7 +432,7 @@ keyChar
|
|
|
435
432
|
keyCharStart
|
|
436
433
|
// All JS identifier characters
|
|
437
434
|
= char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
|
|
438
|
-
/ "."
|
|
435
|
+
/ "." !".." // a dot, but not a spread/rest operator
|
|
439
436
|
/ "@"
|
|
440
437
|
/ "~"
|
|
441
438
|
|
|
@@ -494,10 +491,52 @@ newLine
|
|
|
494
491
|
/ "\r\n"
|
|
495
492
|
/ "\r"
|
|
496
493
|
|
|
494
|
+
number
|
|
495
|
+
= numberBigInt
|
|
496
|
+
/ numberStandard {
|
|
497
|
+
const stripped = text().replace("_", ""); // remove underscores
|
|
498
|
+
return Number(stripped);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
numberBigInt
|
|
502
|
+
= digits:numberDecimalDigits "n" {
|
|
503
|
+
const stripped = digits.replace("_", ""); // remove underscores
|
|
504
|
+
return BigInt(stripped);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
numberBinary
|
|
508
|
+
= "0" [bB] digits:[01_]+
|
|
509
|
+
|
|
510
|
+
numberDecimal
|
|
511
|
+
= numberDecimalDigits? "." numberDecimalDigits numberExponent?
|
|
512
|
+
/ numberDecimalDigits numberExponent?
|
|
513
|
+
|
|
514
|
+
numberDecimalDigits
|
|
515
|
+
= [0-9]([_]*[0-9])* {
|
|
516
|
+
return text();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
numberExponent
|
|
520
|
+
= [eE] [+\-]? digits:numberDecimalDigits
|
|
521
|
+
|
|
522
|
+
numberHex
|
|
523
|
+
= "0" [xX] digits:[0-9a-fA-F_]+
|
|
524
|
+
|
|
497
525
|
// A number
|
|
498
|
-
|
|
499
|
-
=
|
|
500
|
-
|
|
526
|
+
numberLiteral "number"
|
|
527
|
+
= number:number {
|
|
528
|
+
return annotate([ops.literal, number], location());
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
numberOctal
|
|
532
|
+
= "0" [oO] digits:[0-7]+
|
|
533
|
+
|
|
534
|
+
// Any number that can be parsed with Number(); i.e., not a bigint
|
|
535
|
+
numberStandard
|
|
536
|
+
= numberBinary
|
|
537
|
+
/ numberOctal
|
|
538
|
+
/ numberHex
|
|
539
|
+
/ numberDecimal
|
|
501
540
|
|
|
502
541
|
nullishCoalescingExpression
|
|
503
542
|
= head:logicalAndExpression tail:(__ "??" __ @logicalAndExpression)* {
|
|
@@ -586,20 +625,112 @@ optional
|
|
|
586
625
|
return annotate([ops.optional, propertyAccess], location());
|
|
587
626
|
}
|
|
588
627
|
|
|
589
|
-
|
|
590
|
-
|
|
628
|
+
paramArray
|
|
629
|
+
= "[" __ entries:paramArrayEntries __ "]" {
|
|
630
|
+
return annotate([markers.paramArray, ...entries], location());
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// A list of parameters inside array destructuring: `a, b` in `([a, b]) => a + b`
|
|
634
|
+
// Different than top-level parameters because elements can be elided: `a, , b`
|
|
635
|
+
paramArrayEntries
|
|
636
|
+
= entries:paramArrayEntry|1.., separator| rest:(separator @paramRest?)? {
|
|
637
|
+
if (rest) {
|
|
638
|
+
entries.push(rest);
|
|
639
|
+
}
|
|
640
|
+
return annotate(entries, location());
|
|
641
|
+
}
|
|
642
|
+
/ rest:paramRest {
|
|
643
|
+
return annotate([rest], location());
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Single parameter in a function's parameter list
|
|
647
|
+
param
|
|
648
|
+
= paramNameWithInitilializer
|
|
649
|
+
/ pattern:paramBindingPattern initializer:paramInitializer? {
|
|
650
|
+
return initializer
|
|
651
|
+
? annotate([markers.paramInitializer, pattern, initializer], location())
|
|
652
|
+
: pattern;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
paramArrayEntry
|
|
656
|
+
= param
|
|
657
|
+
/ &separator {
|
|
658
|
+
// Missing value is allowed
|
|
659
|
+
return undefined;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
paramBindingPattern
|
|
663
|
+
= paramArray
|
|
664
|
+
/ paramObject
|
|
665
|
+
|
|
666
|
+
paramInitializer
|
|
667
|
+
= __ "=" __ @pipelineExpression
|
|
668
|
+
|
|
669
|
+
// A list of lambda parameters inside the parentheses: `a, b` in `(a, b) => a + b`
|
|
670
|
+
paramList
|
|
671
|
+
= entries:param|1.., separator| rest:(separator @paramRest?)? {
|
|
672
|
+
if (rest) {
|
|
673
|
+
entries.push(rest);
|
|
674
|
+
}
|
|
675
|
+
return annotate(entries, location());
|
|
676
|
+
}
|
|
677
|
+
/ rest:paramRest {
|
|
678
|
+
return annotate([rest], location());
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// A single name in a parameter list: `a` in `a, b`
|
|
682
|
+
paramName
|
|
591
683
|
= key:key {
|
|
592
|
-
return annotate([
|
|
684
|
+
return annotate([markers.paramName, key], location());
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
paramNameWithInitilializer
|
|
688
|
+
= name:paramName initializer:paramInitializer? {
|
|
689
|
+
return initializer
|
|
690
|
+
? annotate([markers.paramInitializer, name, initializer], location())
|
|
691
|
+
: name;
|
|
593
692
|
}
|
|
594
693
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
694
|
+
// Object binding pattern in function parameter: `{ a, b: c }`
|
|
695
|
+
paramObject
|
|
696
|
+
= "{" __ entries:paramObjectEntries? __ "}" {
|
|
697
|
+
return annotate([markers.paramObject, ...(entries ?? [])], location());
|
|
598
698
|
}
|
|
599
699
|
|
|
600
|
-
// A list
|
|
601
|
-
|
|
602
|
-
=
|
|
700
|
+
// A separated list of parameter object entries inside the curly braces
|
|
701
|
+
paramObjectEntries
|
|
702
|
+
= entries:paramObjectEntry|1.., separator| rest:(separator @paramRest)? {
|
|
703
|
+
if (rest) {
|
|
704
|
+
entries.push(rest);
|
|
705
|
+
}
|
|
706
|
+
return annotate(entries, location());
|
|
707
|
+
}
|
|
708
|
+
/ rest:paramRest {
|
|
709
|
+
return annotate([rest], location());
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// An entry in a parameter object: `a: b` in `{ a: b }`
|
|
713
|
+
paramObjectEntry
|
|
714
|
+
= key:objectPublicKey __ ":" __ param:param {
|
|
715
|
+
return annotate([key, param], location());
|
|
716
|
+
}
|
|
717
|
+
/ name:paramName initializer:paramInitializer? {
|
|
718
|
+
const binding = initializer
|
|
719
|
+
? annotate([markers.paramInitializer, name, initializer], location())
|
|
720
|
+
: name;
|
|
721
|
+
return annotate([name[1], binding], location());
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Optional rest parameter for param array or object
|
|
725
|
+
paramRest
|
|
726
|
+
= "..." __ param:param {
|
|
727
|
+
return annotate([markers.paramRest, param], location());
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// A lambda parameter list with a single identifier with no parentheses:
|
|
731
|
+
// `x` in `x => x + 1`
|
|
732
|
+
paramSingleton
|
|
733
|
+
= param:paramName {
|
|
603
734
|
return annotate([param], location());
|
|
604
735
|
}
|
|
605
736
|
|
|
@@ -660,6 +791,11 @@ pipelineExpression
|
|
|
660
791
|
);
|
|
661
792
|
}
|
|
662
793
|
|
|
794
|
+
port
|
|
795
|
+
= ":" [0-9]+ {
|
|
796
|
+
return text();
|
|
797
|
+
}
|
|
798
|
+
|
|
663
799
|
primary
|
|
664
800
|
// The following start with distinct characters
|
|
665
801
|
= stringLiteral
|
|
@@ -671,7 +807,7 @@ primary
|
|
|
671
807
|
/ templateLiteral
|
|
672
808
|
|
|
673
809
|
// These are more ambiguous
|
|
674
|
-
/ @
|
|
810
|
+
/ @numberLiteral !keyChar // numbers + chars would be a key
|
|
675
811
|
/ pathLiteral
|
|
676
812
|
|
|
677
813
|
// Top-level Origami progam with possible shebang directive (which is ignored)
|
|
@@ -741,15 +877,13 @@ shiftOperator
|
|
|
741
877
|
// A shorthand lambda expression: `=foo(_)`
|
|
742
878
|
shorthandFunction "lambda function"
|
|
743
879
|
// Avoid a following equal sign (for an equality)
|
|
744
|
-
= (shellMode / programMode) "=" !"=" __
|
|
880
|
+
= (shellMode / programMode) "=" !"=" __ body:implicitParenthesesCallExpression {
|
|
745
881
|
if (options.mode === "program") {
|
|
746
882
|
throw new Error("Parse error: shorthand function syntax isn't allowed in Origami programs. Use arrow syntax instead.");
|
|
747
883
|
}
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
);
|
|
752
|
-
return annotate([ops.lambda, lambdaParameters, definition], location());
|
|
884
|
+
const underscore = annotate([markers.paramName, "_"], location());
|
|
885
|
+
const parameters = annotate([underscore], location());
|
|
886
|
+
return makeLambda(parameters, body, location());
|
|
753
887
|
}
|
|
754
888
|
/ implicitParenthesesCallExpression
|
|
755
889
|
|
|
@@ -824,11 +958,9 @@ templateDocument "template document"
|
|
|
824
958
|
if (options.front) {
|
|
825
959
|
return makeDocument(options.front, body, location());
|
|
826
960
|
}
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
);
|
|
831
|
-
return annotate([ops.lambda, lambdaParameters, body], location());
|
|
961
|
+
const underscore = annotate([markers.paramName, "_"], location());
|
|
962
|
+
const parameters = annotate([underscore], location());
|
|
963
|
+
return makeLambda(parameters, body, location());
|
|
832
964
|
}
|
|
833
965
|
|
|
834
966
|
// A backtick-quoted template literal
|