@weborigami/language 0.2.6 → 0.2.8
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 +17 -9
- package/package.json +3 -3
- package/src/compiler/optimize.js +36 -20
- package/src/compiler/origami.pegjs +48 -37
- package/src/compiler/parse.d.ts +2 -2
- package/src/compiler/parse.js +299 -284
- package/src/compiler/parserHelpers.js +132 -89
- package/src/runtime/errors.js +5 -2
- package/src/runtime/evaluate.js +8 -11
- package/src/runtime/expressionFunction.js +1 -1
- package/src/runtime/expressionObject.js +12 -4
- package/src/runtime/mergeTrees.js +2 -0
- package/src/runtime/ops.js +63 -18
- package/src/runtime/typos.js +6 -0
- package/test/compiler/{stripCodeLocations.js → codeHelpers.js} +24 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/optimize.test.js +9 -11
- package/test/compiler/parse.test.js +27 -8
- package/test/runtime/OrigamiFiles.test.js +0 -2
- package/test/runtime/evaluate.test.js +1 -13
- package/test/runtime/mergeTrees.test.js +0 -1
- package/test/runtime/ops.test.js +40 -13
- package/test/runtime/typos.test.js +1 -0
package/index.ts
CHANGED
|
@@ -3,16 +3,24 @@ import { Packed } from "@weborigami/async-tree";
|
|
|
3
3
|
export * from "./main.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* `location` property.
|
|
6
|
+
* Code annotated to track the original source that produced the code. If the
|
|
7
|
+
* code is an array, it must have a `location` property.
|
|
8
8
|
*/
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
export type AnnotatedCodeItem<T = any> = T extends any[] ? never : T;
|
|
10
|
+
export type AnnotatedCode = (AnnotatedCode | AnnotatedCodeItem)[] & {
|
|
11
|
+
location: CodeLocation
|
|
12
|
+
source?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A chunk of compiled Origami code. This is just an array.
|
|
17
|
+
*/
|
|
18
|
+
export type Code = Array<any>;
|
|
19
|
+
|
|
20
|
+
export type CodeLocation = {
|
|
21
|
+
end: Position;
|
|
22
|
+
source: Source;
|
|
23
|
+
start: Position;
|
|
16
24
|
};
|
|
17
25
|
|
|
18
26
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"yaml": "2.6.1"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@weborigami/async-tree": "0.2.
|
|
16
|
-
"@weborigami/types": "0.2.
|
|
15
|
+
"@weborigami/async-tree": "0.2.8",
|
|
16
|
+
"@weborigami/types": "0.2.8",
|
|
17
17
|
"watcher": "2.3.1"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
package/src/compiler/optimize.js
CHANGED
|
@@ -10,6 +10,16 @@ import { annotate, undetermined } from "./parserHelpers.js";
|
|
|
10
10
|
* they refer to local variables (variables defined by object literals or
|
|
11
11
|
* lambda parameters).
|
|
12
12
|
* - Apply any macros to the code.
|
|
13
|
+
*
|
|
14
|
+
* @typedef {import("./parserHelpers.js").AnnotatedCode} AnnotatedCode
|
|
15
|
+
* @typedef {import("./parserHelpers.js").Code} Code
|
|
16
|
+
*
|
|
17
|
+
* @param {AnnotatedCode} code
|
|
18
|
+
* @param {boolean} enableCaching
|
|
19
|
+
* @param {Record<string, AnnotatedCode>} macros
|
|
20
|
+
* @param {Record<string, AnnotatedCode>} cache
|
|
21
|
+
* @param {Record<string, boolean>} locals
|
|
22
|
+
* @returns {AnnotatedCode}
|
|
13
23
|
*/
|
|
14
24
|
export default function optimize(
|
|
15
25
|
code,
|
|
@@ -27,6 +37,13 @@ export default function optimize(
|
|
|
27
37
|
additionalLocalNames = parameters;
|
|
28
38
|
break;
|
|
29
39
|
|
|
40
|
+
case ops.literal:
|
|
41
|
+
const value = args[0];
|
|
42
|
+
if (!(value instanceof Array)) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
|
|
30
47
|
case ops.object:
|
|
31
48
|
const entries = args;
|
|
32
49
|
additionalLocalNames = entries.map(([key]) => trailingSlash.remove(key));
|
|
@@ -43,16 +60,13 @@ export default function optimize(
|
|
|
43
60
|
return applyMacro(macro, code, enableCaching, macros, cache, locals);
|
|
44
61
|
} else if (enableCaching && !locals[normalizedKey]) {
|
|
45
62
|
// Upgrade to cached external scope reference
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
return annotate(
|
|
64
|
+
[ops.external, key, [ops.scope, key], cache],
|
|
65
|
+
code.location
|
|
66
|
+
);
|
|
50
67
|
} else if (fn === undetermined) {
|
|
51
68
|
// Transform undetermined reference to regular scope call
|
|
52
|
-
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
annotate(optimized, code.location);
|
|
55
|
-
return optimized;
|
|
69
|
+
return annotate([ops.scope, key], code.location);
|
|
56
70
|
} else {
|
|
57
71
|
// Internal ops.scope call; leave as is
|
|
58
72
|
return code;
|
|
@@ -74,10 +88,9 @@ export default function optimize(
|
|
|
74
88
|
// Convert to ops.external
|
|
75
89
|
const keys = args.map((arg) => arg[1]);
|
|
76
90
|
const path = pathFromKeys(keys);
|
|
91
|
+
/** @type {Code} */
|
|
77
92
|
const optimized = [ops.external, path, code, cache];
|
|
78
|
-
|
|
79
|
-
annotate(optimized, code.location);
|
|
80
|
-
return optimized;
|
|
93
|
+
return annotate(optimized, code.location);
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
}
|
|
@@ -98,27 +111,30 @@ export default function optimize(
|
|
|
98
111
|
|
|
99
112
|
// Optimize children
|
|
100
113
|
const optimized = code.map((child) => {
|
|
101
|
-
if (Array.isArray(child)) {
|
|
114
|
+
if (Array.isArray(child) && "location" in child) {
|
|
102
115
|
// Review: This currently descends into arrays that are not instructions,
|
|
103
116
|
// such as the parameters of a lambda. This should be harmless, but it'd
|
|
104
117
|
// be preferable to only descend into instructions. This would require
|
|
105
118
|
// surrounding ops.lambda parameters with ops.literal, and ops.object
|
|
106
119
|
// entries with ops.array.
|
|
107
|
-
return optimize(
|
|
120
|
+
return optimize(
|
|
121
|
+
/** @type {AnnotatedCode} */ (child),
|
|
122
|
+
enableCaching,
|
|
123
|
+
macros,
|
|
124
|
+
cache,
|
|
125
|
+
updatedLocals
|
|
126
|
+
);
|
|
108
127
|
} else {
|
|
109
128
|
return child;
|
|
110
129
|
}
|
|
111
130
|
});
|
|
112
131
|
|
|
113
|
-
|
|
114
|
-
annotate(optimized, code.location);
|
|
115
|
-
}
|
|
116
|
-
return optimized;
|
|
132
|
+
return annotate(optimized, code.location);
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
function applyMacro(macro, code, enableCaching, macros, cache, locals) {
|
|
120
136
|
const optimized = optimize(macro, enableCaching, macros, cache, locals);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
return optimized instanceof Array
|
|
138
|
+
? annotate(optimized, code.location)
|
|
139
|
+
: optimized;
|
|
124
140
|
}
|
|
@@ -37,7 +37,7 @@ __
|
|
|
37
37
|
|
|
38
38
|
additiveExpression
|
|
39
39
|
= head:multiplicativeExpression tail:(whitespace @additiveOperator whitespace @multiplicativeExpression)* {
|
|
40
|
-
return
|
|
40
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
additiveOperator
|
|
@@ -51,7 +51,7 @@ arguments "function arguments"
|
|
|
51
51
|
|
|
52
52
|
arrayLiteral "array"
|
|
53
53
|
= "[" __ entries:arrayEntries? __ closingBracket {
|
|
54
|
-
return
|
|
54
|
+
return makeArray(entries ?? [], location());
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// A separated list of array entries
|
|
@@ -70,16 +70,18 @@ arrayEntry
|
|
|
70
70
|
|
|
71
71
|
arrowFunction
|
|
72
72
|
= "(" __ parameters:identifierList? __ ")" __ doubleArrow __ pipeline:pipelineExpression {
|
|
73
|
-
|
|
73
|
+
const lambdaParameters = annotate(parameters ?? [], location());
|
|
74
|
+
return annotate([ops.lambda, lambdaParameters, pipeline], location());
|
|
74
75
|
}
|
|
75
76
|
/ identifier:identifier __ doubleArrow __ pipeline:pipelineExpression {
|
|
76
|
-
|
|
77
|
+
const lambdaParameters = annotate([identifier], location())
|
|
78
|
+
return annotate([ops.lambda, lambdaParameters, pipeline], location());
|
|
77
79
|
}
|
|
78
80
|
/ conditionalExpression
|
|
79
81
|
|
|
80
82
|
bitwiseAndExpression
|
|
81
83
|
= head:equalityExpression tail:(__ @bitwiseAndOperator __ @equalityExpression)* {
|
|
82
|
-
return
|
|
84
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
bitwiseAndOperator
|
|
@@ -87,7 +89,7 @@ bitwiseAndOperator
|
|
|
87
89
|
|
|
88
90
|
bitwiseOrExpression
|
|
89
91
|
= head:bitwiseXorExpression tail:(__ @bitwiseOrOperator __ @bitwiseXorExpression)* {
|
|
90
|
-
return
|
|
92
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
bitwiseOrOperator
|
|
@@ -95,7 +97,7 @@ bitwiseOrOperator
|
|
|
95
97
|
|
|
96
98
|
bitwiseXorExpression
|
|
97
99
|
= head:bitwiseAndExpression tail:(__ @bitwiseXorOperator __ @bitwiseAndExpression)* {
|
|
98
|
-
return
|
|
100
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
bitwiseXorOperator
|
|
@@ -105,7 +107,7 @@ bitwiseXorOperator
|
|
|
105
107
|
// `fn(arg1)(arg2)(arg3)`.
|
|
106
108
|
callExpression "function call"
|
|
107
109
|
= head:protocolExpression tail:arguments* {
|
|
108
|
-
return
|
|
110
|
+
return tail.reduce(makeCall, head);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
// Required closing curly brace. We use this for the `object` term: if the
|
|
@@ -154,11 +156,12 @@ conditionalExpression
|
|
|
154
156
|
if (!tail) {
|
|
155
157
|
return condition;
|
|
156
158
|
}
|
|
159
|
+
const deferred = makeDeferredArguments(tail);
|
|
157
160
|
return annotate([
|
|
158
161
|
ops.conditional,
|
|
159
162
|
downgradeReference(condition),
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
downgradeReference(deferred[0]),
|
|
164
|
+
downgradeReference(deferred[1])
|
|
162
165
|
], location());
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -179,7 +182,7 @@ ellipsis = "..." / "…" // Unicode ellipsis
|
|
|
179
182
|
|
|
180
183
|
equalityExpression
|
|
181
184
|
= head:relationalExpression tail:(__ @equalityOperator __ @relationalExpression)* {
|
|
182
|
-
return
|
|
185
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
equalityOperator
|
|
@@ -311,7 +314,7 @@ multiLineComment
|
|
|
311
314
|
|
|
312
315
|
multiplicativeExpression
|
|
313
316
|
= head:exponentiationExpression tail:(whitespace @multiplicativeOperator whitespace @exponentiationExpression)* {
|
|
314
|
-
return
|
|
317
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
315
318
|
}
|
|
316
319
|
|
|
317
320
|
multiplicativeOperator
|
|
@@ -320,10 +323,9 @@ multiplicativeOperator
|
|
|
320
323
|
/ "%"
|
|
321
324
|
|
|
322
325
|
// A namespace reference is a string of letters only, followed by a colon.
|
|
323
|
-
// For the time being, we also allow a leading `@`, which is deprecated.
|
|
324
326
|
namespace
|
|
325
|
-
=
|
|
326
|
-
return annotate([ops.builtin,
|
|
327
|
+
= chars:[A-Za-z]+ ":" {
|
|
328
|
+
return annotate([ops.builtin, chars.join("") + ":"], location());
|
|
327
329
|
}
|
|
328
330
|
|
|
329
331
|
newLine
|
|
@@ -349,7 +351,7 @@ nullishCoalescingExpression
|
|
|
349
351
|
// An object literal: `{foo: 1, bar: 2}`
|
|
350
352
|
objectLiteral "object literal"
|
|
351
353
|
= "{" __ entries:objectEntries? __ closingBrace {
|
|
352
|
-
return
|
|
354
|
+
return makeObject(entries ?? [], location());
|
|
353
355
|
}
|
|
354
356
|
|
|
355
357
|
// A separated list of object entries
|
|
@@ -389,7 +391,8 @@ objectProperty "object property"
|
|
|
389
391
|
// A shorthand reference inside an object literal: `foo`
|
|
390
392
|
objectShorthandProperty "object identifier"
|
|
391
393
|
= key:objectPublicKey {
|
|
392
|
-
|
|
394
|
+
const inherited = annotate([ops.inherited, key], location());
|
|
395
|
+
return annotate([key, inherited], location());
|
|
393
396
|
}
|
|
394
397
|
|
|
395
398
|
objectPublicKey
|
|
@@ -456,7 +459,7 @@ primary
|
|
|
456
459
|
/ objectLiteral
|
|
457
460
|
/ group
|
|
458
461
|
/ templateLiteral
|
|
459
|
-
/
|
|
462
|
+
/ inherited
|
|
460
463
|
|
|
461
464
|
// Top-level Origami progam with possible shebang directive (which is ignored)
|
|
462
465
|
program "Origami program"
|
|
@@ -464,9 +467,9 @@ program "Origami program"
|
|
|
464
467
|
|
|
465
468
|
// Protocol with double-slash path: `https://example.com/index.html`
|
|
466
469
|
protocolExpression
|
|
467
|
-
= fn:namespace "//" host:host path:path? {
|
|
470
|
+
= fn:namespace "//" host:(host / slash) path:path? {
|
|
468
471
|
const keys = annotate([host, ...(path ?? [])], location());
|
|
469
|
-
return
|
|
472
|
+
return makeCall(fn, keys);
|
|
470
473
|
}
|
|
471
474
|
/ primary
|
|
472
475
|
|
|
@@ -474,10 +477,10 @@ protocolExpression
|
|
|
474
477
|
qualifiedReference
|
|
475
478
|
= fn:namespace reference:scopeReference {
|
|
476
479
|
const literal = annotate([ops.literal, reference[1]], reference.location);
|
|
477
|
-
return
|
|
480
|
+
return makeCall(fn, [literal]);
|
|
478
481
|
}
|
|
479
482
|
|
|
480
|
-
|
|
483
|
+
inherited
|
|
481
484
|
= rootDirectory
|
|
482
485
|
/ homeDirectory
|
|
483
486
|
/ qualifiedReference
|
|
@@ -486,7 +489,7 @@ reference
|
|
|
486
489
|
|
|
487
490
|
relationalExpression
|
|
488
491
|
= head:shiftExpression tail:(__ @relationalOperator __ @shiftExpression)* {
|
|
489
|
-
return
|
|
492
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
relationalOperator
|
|
@@ -515,19 +518,12 @@ separator
|
|
|
515
518
|
= __ "," __
|
|
516
519
|
/ whitespaceWithNewLine
|
|
517
520
|
|
|
518
|
-
// Check whether next character is a slash without consuming input
|
|
519
|
-
slashFollows
|
|
520
|
-
// This expression returned `undefined` if successful; we convert to `true`
|
|
521
|
-
= &"/" {
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
521
|
shebang
|
|
526
522
|
= "#!" [^\n\r]* { return null; }
|
|
527
523
|
|
|
528
524
|
shiftExpression
|
|
529
525
|
= head:additiveExpression tail:(__ @shiftOperator __ @additiveExpression)* {
|
|
530
|
-
return
|
|
526
|
+
return tail.reduce(makeBinaryOperation, head);
|
|
531
527
|
}
|
|
532
528
|
|
|
533
529
|
shiftOperator
|
|
@@ -539,7 +535,8 @@ shiftOperator
|
|
|
539
535
|
shorthandFunction "lambda function"
|
|
540
536
|
// Avoid a following equal sign (for an equality)
|
|
541
537
|
= "=" !"=" __ definition:implicitParenthesesCallExpression {
|
|
542
|
-
|
|
538
|
+
const lambdaParameters = annotate(["_"], location());
|
|
539
|
+
return annotate([ops.lambda, lambdaParameters, definition], location());
|
|
543
540
|
}
|
|
544
541
|
/ implicitParenthesesCallExpression
|
|
545
542
|
|
|
@@ -558,6 +555,19 @@ singleQuoteString "single quote string"
|
|
|
558
555
|
singleQuoteStringChar
|
|
559
556
|
= !("'" / newLine) @textChar
|
|
560
557
|
|
|
558
|
+
// Used for an initial slash in a protocol expression
|
|
559
|
+
slash
|
|
560
|
+
= slashFollows {
|
|
561
|
+
return annotate([ops.literal, "/"], location());
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Check whether next character is a slash without consuming input
|
|
565
|
+
slashFollows
|
|
566
|
+
// This expression returned `undefined` if successful; we convert to `true`
|
|
567
|
+
= &"/" {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
561
571
|
spreadElement
|
|
562
572
|
= ellipsis __ value:pipelineExpression {
|
|
563
573
|
return annotate([ops.spread, value], location());
|
|
@@ -572,8 +582,9 @@ stringLiteral "string"
|
|
|
572
582
|
// literal, but can contain backticks at the top level.
|
|
573
583
|
templateDocument "template"
|
|
574
584
|
= head:templateDocumentText tail:(templateSubstitution templateDocumentText)* {
|
|
585
|
+
const lambdaParameters = annotate(["_"], location());
|
|
575
586
|
return annotate(
|
|
576
|
-
[ops.lambda,
|
|
587
|
+
[ops.lambda, lambdaParameters, makeTemplate(ops.templateIndent, head, tail, location())],
|
|
577
588
|
location()
|
|
578
589
|
);
|
|
579
590
|
}
|
|
@@ -584,13 +595,13 @@ templateDocumentChar
|
|
|
584
595
|
|
|
585
596
|
templateDocumentText "template text"
|
|
586
597
|
= chars:templateDocumentChar* {
|
|
587
|
-
return chars.join("");
|
|
598
|
+
return annotate([ops.literal, chars.join("")], location());
|
|
588
599
|
}
|
|
589
600
|
|
|
590
601
|
// A backtick-quoted template literal
|
|
591
602
|
templateLiteral "template literal"
|
|
592
603
|
= "`" head:templateLiteralText tail:(templateSubstitution templateLiteralText)* "`" {
|
|
593
|
-
return
|
|
604
|
+
return makeTemplate(ops.template, head, tail, location());
|
|
594
605
|
}
|
|
595
606
|
|
|
596
607
|
templateLiteralChar
|
|
@@ -599,7 +610,7 @@ templateLiteralChar
|
|
|
599
610
|
// Plain text in a template literal
|
|
600
611
|
templateLiteralText
|
|
601
612
|
= chars:templateLiteralChar* {
|
|
602
|
-
return chars.join("");
|
|
613
|
+
return annotate([ops.literal, chars.join("")], location());
|
|
603
614
|
}
|
|
604
615
|
|
|
605
616
|
// A substitution in a template literal: `${x}`
|
|
@@ -615,7 +626,7 @@ textChar
|
|
|
615
626
|
// A unary prefix operator: `!x`
|
|
616
627
|
unaryExpression
|
|
617
628
|
= operator:unaryOperator __ expression:unaryExpression {
|
|
618
|
-
return
|
|
629
|
+
return makeUnaryOperation(operator, expression, location());
|
|
619
630
|
}
|
|
620
631
|
/ callExpression
|
|
621
632
|
|
package/src/compiler/parse.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnnotatedCode } from "../../index.ts";
|
|
2
2
|
|
|
3
|
-
export function parse(input: string, options: any):
|
|
3
|
+
export function parse(input: string, options: any): AnnotatedCode;
|