@weborigami/language 0.2.9 → 0.2.11
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 +7 -7
- package/src/compiler/isOrigamiFrontMatter.js +26 -0
- package/src/compiler/optimize.js +9 -7
- package/src/compiler/origami.pegjs +146 -56
- package/src/compiler/parse.js +1224 -772
- package/src/compiler/parserHelpers.js +85 -1
- package/src/runtime/EventTargetMixin.d.ts +1 -5
- package/src/runtime/errors.js +19 -2
- package/src/runtime/evaluate.js +1 -9
- package/src/runtime/ops.js +31 -1
- package/test/compiler/codeHelpers.js +1 -1
- package/test/compiler/optimize.test.js +1 -1
- package/test/compiler/parse.test.js +152 -30
- package/test/runtime/ops.test.js +27 -1
package/main.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./src/runtime/internal.js";
|
|
2
2
|
|
|
3
3
|
export * as compile from "./src/compiler/compile.js";
|
|
4
|
+
export { default as isOrigamiFrontMatter } from "./src/compiler/isOrigamiFrontMatter.js";
|
|
4
5
|
export * from "./src/runtime/errors.js";
|
|
5
6
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
6
7
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"types": "./index.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@types/node": "22.13.
|
|
9
|
+
"@types/node": "22.13.13",
|
|
10
10
|
"peggy": "4.2.0.",
|
|
11
|
-
"typescript": "5.8.2"
|
|
12
|
-
"yaml": "2.7.0"
|
|
11
|
+
"typescript": "5.8.2"
|
|
13
12
|
},
|
|
14
13
|
"dependencies": {
|
|
15
|
-
"@weborigami/async-tree": "0.2.
|
|
16
|
-
"@weborigami/types": "0.2.
|
|
17
|
-
"watcher": "2.3.1"
|
|
14
|
+
"@weborigami/async-tree": "0.2.11",
|
|
15
|
+
"@weborigami/types": "0.2.11",
|
|
16
|
+
"watcher": "2.3.1",
|
|
17
|
+
"yaml": "2.7.0"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "peggy --allowed-start-rules=\"*\" --format es src/compiler/origami.pegjs --output src/compiler/parse.js",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return true if the given text is Origami front matter
|
|
3
|
+
*
|
|
4
|
+
* Our heurstic is to see skip any initial alphanumeric or underscore
|
|
5
|
+
* characters, then see if the next character is a parenthesis, dot, slash,
|
|
6
|
+
* curly brace, or equals sign. If so, we assume this is an Origami expression.
|
|
7
|
+
*
|
|
8
|
+
* The goal is to identify Origami front matter like:
|
|
9
|
+
*
|
|
10
|
+
* ```
|
|
11
|
+
* fn(x) function call
|
|
12
|
+
* index.ori() file extension
|
|
13
|
+
* src/data.json file path
|
|
14
|
+
* // Hello comment
|
|
15
|
+
* { a: 1 } object literal
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* These are generally invalid ways to start YAML. The last is valid YAML, but
|
|
19
|
+
* Origami supports the same syntax for basic object literals so interpreting as
|
|
20
|
+
* Origami should generally be acceptable.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} text
|
|
23
|
+
*/
|
|
24
|
+
export default function isOrigamiFrontMatter(text) {
|
|
25
|
+
return /^[ \t\r\n]*[A-Za-z0-9_]*[\(\.\/\{=]/.test(text);
|
|
26
|
+
}
|
package/src/compiler/optimize.js
CHANGED
|
@@ -34,7 +34,7 @@ export default function optimize(
|
|
|
34
34
|
switch (fn) {
|
|
35
35
|
case ops.lambda:
|
|
36
36
|
const parameters = args[0];
|
|
37
|
-
additionalLocalNames = parameters;
|
|
37
|
+
additionalLocalNames = parameters.map((param) => param[1]);
|
|
38
38
|
break;
|
|
39
39
|
|
|
40
40
|
case ops.literal:
|
|
@@ -61,7 +61,7 @@ export default function optimize(
|
|
|
61
61
|
} else if (enableCaching && !locals[normalizedKey]) {
|
|
62
62
|
// Upgrade to cached external scope reference
|
|
63
63
|
return annotate(
|
|
64
|
-
[ops.external, key, [ops.scope, key], cache],
|
|
64
|
+
[ops.external, key, annotate([ops.scope, key], code.location), cache],
|
|
65
65
|
code.location
|
|
66
66
|
);
|
|
67
67
|
} else if (fn === undetermined) {
|
|
@@ -110,13 +110,15 @@ export default function optimize(
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// Optimize children
|
|
113
|
-
const optimized = code.map((child) => {
|
|
114
|
-
|
|
113
|
+
const optimized = code.map((child, index) => {
|
|
114
|
+
// Don't optimize lambda parameter names
|
|
115
|
+
if (fn === ops.lambda && index === 1) {
|
|
116
|
+
return child;
|
|
117
|
+
} else if (Array.isArray(child) && "location" in child) {
|
|
115
118
|
// Review: This currently descends into arrays that are not instructions,
|
|
116
|
-
// such as the
|
|
119
|
+
// such as the entries of an ops.object. This should be harmless, but it'd
|
|
117
120
|
// be preferable to only descend into instructions. This would require
|
|
118
|
-
// surrounding ops.
|
|
119
|
-
// entries with ops.array.
|
|
121
|
+
// surrounding ops.object entries with ops.array.
|
|
120
122
|
return optimize(
|
|
121
123
|
/** @type {AnnotatedCode} */ (child),
|
|
122
124
|
enableCaching,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as ops from "../runtime/ops.js";
|
|
15
15
|
import {
|
|
16
16
|
annotate,
|
|
17
|
+
applyMacro,
|
|
17
18
|
downgradeReference,
|
|
18
19
|
makeArray,
|
|
19
20
|
makeBinaryOperation,
|
|
@@ -24,8 +25,10 @@ import {
|
|
|
24
25
|
makeProperty,
|
|
25
26
|
makeReference,
|
|
26
27
|
makeTemplate,
|
|
27
|
-
makeUnaryOperation
|
|
28
|
+
makeUnaryOperation,
|
|
29
|
+
makeYamlObject
|
|
28
30
|
} from "./parserHelpers.js";
|
|
31
|
+
import isOrigamiFrontMatter from "./isOrigamiFrontMatter.js";
|
|
29
32
|
|
|
30
33
|
}}
|
|
31
34
|
|
|
@@ -50,7 +53,7 @@ arguments "function arguments"
|
|
|
50
53
|
/ templateLiteral
|
|
51
54
|
|
|
52
55
|
arrayLiteral "array"
|
|
53
|
-
= "[" __ entries:arrayEntries? __
|
|
56
|
+
= "[" __ entries:arrayEntries? __ expectClosingBracket {
|
|
54
57
|
return makeArray(entries ?? [], location());
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -69,13 +72,12 @@ arrayEntry
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
arrowFunction
|
|
72
|
-
= "(" __ parameters:
|
|
73
|
-
const lambdaParameters =
|
|
75
|
+
= "(" __ parameters:parameterList? __ ")" __ doubleArrow __ pipeline:expectPipelineExpression {
|
|
76
|
+
const lambdaParameters = parameters ?? annotate([], location());
|
|
74
77
|
return annotate([ops.lambda, lambdaParameters, pipeline], location());
|
|
75
78
|
}
|
|
76
|
-
/
|
|
77
|
-
|
|
78
|
-
return annotate([ops.lambda, lambdaParameters, pipeline], location());
|
|
79
|
+
/ parameter:parameterSingleton __ doubleArrow __ pipeline:expectPipelineExpression {
|
|
80
|
+
return annotate([ops.lambda, parameter, pipeline], location());
|
|
79
81
|
}
|
|
80
82
|
/ conditionalExpression
|
|
81
83
|
|
|
@@ -110,30 +112,6 @@ callExpression "function call"
|
|
|
110
112
|
return tail.reduce(makeCall, head);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
// Required closing curly brace. We use this for the `object` term: if the
|
|
114
|
-
// parser sees a left curly brace, here we must see a right curly brace.
|
|
115
|
-
closingBrace
|
|
116
|
-
= "}"
|
|
117
|
-
/ .? {
|
|
118
|
-
error(`An object ended without a closing brace, or contained something that wasn't expected.\nThe top level of an object can only contain definitions ("a: b" or "a = b") or spreads ("...a").`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Required closing bracket
|
|
122
|
-
closingBracket
|
|
123
|
-
= "]"
|
|
124
|
-
/ .? {
|
|
125
|
-
error("Expected right bracket");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Required closing parenthesis. We use this for the `group` term: it's the last
|
|
129
|
-
// term in the `step` parser that starts with a parenthesis, so if that parser
|
|
130
|
-
// sees a left parenthesis, here we must see a right parenthesis.
|
|
131
|
-
closingParenthesis
|
|
132
|
-
= ")"
|
|
133
|
-
/ .? {
|
|
134
|
-
error("Expected right parenthesis");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
115
|
// A comma-separated list of expressions: `x, y, z`
|
|
138
116
|
commaExpression
|
|
139
117
|
// The commas are required, but the list can have a single item.
|
|
@@ -171,7 +149,7 @@ digits
|
|
|
171
149
|
doubleArrow = "⇒" / "=>"
|
|
172
150
|
|
|
173
151
|
doubleQuoteString "double quote string"
|
|
174
|
-
= '"' chars:doubleQuoteStringChar*
|
|
152
|
+
= '"' chars:doubleQuoteStringChar* expectDoubleQuote {
|
|
175
153
|
return annotate([ops.literal, chars.join("")], location());
|
|
176
154
|
}
|
|
177
155
|
|
|
@@ -201,6 +179,67 @@ escapedChar "backslash-escaped character"
|
|
|
201
179
|
/ "\\v" { return "\v"; }
|
|
202
180
|
/ "\\" @.
|
|
203
181
|
|
|
182
|
+
expectBacktick
|
|
183
|
+
= "`"
|
|
184
|
+
/ .? {
|
|
185
|
+
error("Expected closing backtick");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
expectClosingBrace
|
|
189
|
+
= "}"
|
|
190
|
+
/ .? {
|
|
191
|
+
error(`An object ended without a closing brace, or contained something that wasn't expected.\nThe top level of an object can only contain definitions ("a: b" or "a = b") or spreads ("...a").`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
expectClosingBracket
|
|
195
|
+
= "]"
|
|
196
|
+
/ .? {
|
|
197
|
+
error("Expected right bracket");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
expectClosingParenthesis
|
|
201
|
+
= ")"
|
|
202
|
+
/ .? {
|
|
203
|
+
error("Expected right parenthesis");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
expectDoubleQuote
|
|
207
|
+
= '"'
|
|
208
|
+
/ .? {
|
|
209
|
+
error("Expected closing quote");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
expectExpression
|
|
213
|
+
= expression
|
|
214
|
+
/ .? {
|
|
215
|
+
error("Expected an Origami expression");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
expectFrontDelimiter
|
|
219
|
+
= frontDelimiter
|
|
220
|
+
/ .? {
|
|
221
|
+
error("Expected \"---\"");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
expectGuillemet
|
|
225
|
+
= '»'
|
|
226
|
+
/ .? {
|
|
227
|
+
error("Expected closing guillemet");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
expectSingleQuote
|
|
231
|
+
= "'"
|
|
232
|
+
/ .? {
|
|
233
|
+
error("Expected closing quote");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Required expression
|
|
237
|
+
expectPipelineExpression
|
|
238
|
+
= pipelineExpression
|
|
239
|
+
/ .? {
|
|
240
|
+
error("Expected an expression");
|
|
241
|
+
}
|
|
242
|
+
|
|
204
243
|
exponentiationExpression
|
|
205
244
|
= left:unaryExpression right:(__ "**" __ @exponentiationExpression)? {
|
|
206
245
|
return right ? annotate([ops.exponentiation, left, right], location()) : left;
|
|
@@ -215,14 +254,35 @@ floatLiteral "floating-point number"
|
|
|
215
254
|
return annotate([ops.literal, parseFloat(text())], location());
|
|
216
255
|
}
|
|
217
256
|
|
|
257
|
+
// Marker for the beginning or end of front matter
|
|
258
|
+
frontDelimiter
|
|
259
|
+
= "---\n"
|
|
260
|
+
|
|
261
|
+
// Origami front matter
|
|
262
|
+
frontMatterExpression
|
|
263
|
+
// If we detect Origami front matter, we need to see an expression
|
|
264
|
+
= frontDelimiter &{
|
|
265
|
+
return isOrigamiFrontMatter(input.slice(location().end.offset))
|
|
266
|
+
} @expectExpression expectFrontDelimiter
|
|
267
|
+
|
|
268
|
+
frontMatterText
|
|
269
|
+
= chars:( !frontDelimiter @. )* {
|
|
270
|
+
return chars.join("");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
frontMatterYaml "YAML front matter"
|
|
274
|
+
= frontDelimiter yaml:frontMatterText frontDelimiter {
|
|
275
|
+
return makeYamlObject(yaml, location());
|
|
276
|
+
}
|
|
277
|
+
|
|
218
278
|
// An expression in parentheses: `(foo)`
|
|
219
279
|
group "parenthetical group"
|
|
220
|
-
= "(" expression:expression
|
|
280
|
+
= "(" expression:expression expectClosingParenthesis {
|
|
221
281
|
return annotate(downgradeReference(expression), location());
|
|
222
282
|
}
|
|
223
283
|
|
|
224
284
|
guillemetString "guillemet string"
|
|
225
|
-
= '«' chars:guillemetStringChar*
|
|
285
|
+
= '«' chars:guillemetStringChar* expectGuillemet {
|
|
226
286
|
return annotate([ops.literal, chars.join("")], location());
|
|
227
287
|
}
|
|
228
288
|
|
|
@@ -254,11 +314,6 @@ identifierChar
|
|
|
254
314
|
/ @'-' !'>' // Accept a hyphen but not in a single arrow combination
|
|
255
315
|
/ escapedChar
|
|
256
316
|
|
|
257
|
-
identifierList
|
|
258
|
-
= list:identifier|1.., separator| separator? {
|
|
259
|
-
return annotate(list, location());
|
|
260
|
-
}
|
|
261
|
-
|
|
262
317
|
implicitParenthesesCallExpression "function call with implicit parentheses"
|
|
263
318
|
= head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
|
|
264
319
|
return args ? makeCall(head, args) : head;
|
|
@@ -350,7 +405,7 @@ nullishCoalescingExpression
|
|
|
350
405
|
|
|
351
406
|
// An object literal: `{foo: 1, bar: 2}`
|
|
352
407
|
objectLiteral "object literal"
|
|
353
|
-
= "{" __ entries:objectEntries? __
|
|
408
|
+
= "{" __ entries:objectEntries? __ expectClosingBrace {
|
|
354
409
|
return makeObject(entries ?? [], location());
|
|
355
410
|
}
|
|
356
411
|
|
|
@@ -368,7 +423,7 @@ objectEntry
|
|
|
368
423
|
|
|
369
424
|
// A getter definition inside an object literal: `foo = 1`
|
|
370
425
|
objectGetter "object getter"
|
|
371
|
-
= key:objectKey __ "=" __ pipeline:
|
|
426
|
+
= key:objectKey __ "=" __ pipeline:expectPipelineExpression {
|
|
372
427
|
return annotate(
|
|
373
428
|
makeProperty(key, annotate([ops.getter, pipeline], location())),
|
|
374
429
|
location()
|
|
@@ -384,7 +439,7 @@ objectKey "object key"
|
|
|
384
439
|
|
|
385
440
|
// A property definition in an object literal: `x: 1`
|
|
386
441
|
objectProperty "object property"
|
|
387
|
-
= key:objectKey __ ":" __ pipeline:
|
|
442
|
+
= key:objectKey __ ":" __ pipeline:expectPipelineExpression {
|
|
388
443
|
return annotate(makeProperty(key, pipeline), location());
|
|
389
444
|
}
|
|
390
445
|
|
|
@@ -404,9 +459,28 @@ objectPublicKey
|
|
|
404
459
|
return string[1];
|
|
405
460
|
}
|
|
406
461
|
|
|
462
|
+
parameter
|
|
463
|
+
= identifier:identifier {
|
|
464
|
+
return annotate([ops.literal, identifier], location());
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
parameterList
|
|
468
|
+
= list:parameter|1.., separator| separator? {
|
|
469
|
+
return annotate(list, location());
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// A list with a single identifier
|
|
473
|
+
parameterSingleton
|
|
474
|
+
= identifier:identifier {
|
|
475
|
+
return annotate(
|
|
476
|
+
[annotate([ops.literal, identifier], location())],
|
|
477
|
+
location()
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
407
481
|
// Function arguments in parentheses
|
|
408
482
|
parenthesesArguments "function arguments in parentheses"
|
|
409
|
-
= "(" __ list:list? __
|
|
483
|
+
= "(" __ list:list? __ expectClosingParenthesis {
|
|
410
484
|
return annotate(list ?? [undefined], location());
|
|
411
485
|
}
|
|
412
486
|
|
|
@@ -535,7 +609,10 @@ shiftOperator
|
|
|
535
609
|
shorthandFunction "lambda function"
|
|
536
610
|
// Avoid a following equal sign (for an equality)
|
|
537
611
|
= "=" !"=" __ definition:implicitParenthesesCallExpression {
|
|
538
|
-
const lambdaParameters = annotate(
|
|
612
|
+
const lambdaParameters = annotate(
|
|
613
|
+
[annotate([ops.literal, "_"], location())],
|
|
614
|
+
location()
|
|
615
|
+
);
|
|
539
616
|
return annotate([ops.lambda, lambdaParameters, definition], location());
|
|
540
617
|
}
|
|
541
618
|
/ implicitParenthesesCallExpression
|
|
@@ -548,7 +625,7 @@ singleLineComment
|
|
|
548
625
|
= "//" [^\n\r]* { return null; }
|
|
549
626
|
|
|
550
627
|
singleQuoteString "single quote string"
|
|
551
|
-
= "'" chars:singleQuoteStringChar*
|
|
628
|
+
= "'" chars:singleQuoteStringChar* expectSingleQuote {
|
|
552
629
|
return annotate([ops.literal, chars.join("")], location());
|
|
553
630
|
}
|
|
554
631
|
|
|
@@ -578,29 +655,42 @@ stringLiteral "string"
|
|
|
578
655
|
/ singleQuoteString
|
|
579
656
|
/ guillemetString
|
|
580
657
|
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
= head:
|
|
585
|
-
const lambdaParameters = annotate(
|
|
658
|
+
// The body of a template document is a kind of template literal that can
|
|
659
|
+
// contain backticks at the top level.
|
|
660
|
+
templateBody "template"
|
|
661
|
+
= head:templateBodyText tail:(templateSubstitution templateBodyText)* {
|
|
662
|
+
const lambdaParameters = annotate(
|
|
663
|
+
[annotate([ops.literal, "_"], location())],
|
|
664
|
+
location()
|
|
665
|
+
);
|
|
586
666
|
return annotate(
|
|
587
667
|
[ops.lambda, lambdaParameters, makeTemplate(ops.templateIndent, head, tail, location())],
|
|
588
668
|
location()
|
|
589
669
|
);
|
|
590
670
|
}
|
|
591
671
|
|
|
592
|
-
// Template
|
|
593
|
-
|
|
672
|
+
// Template document bodies can contain backticks at the top level
|
|
673
|
+
templateBodyChar
|
|
594
674
|
= !("${") @textChar
|
|
595
675
|
|
|
596
|
-
|
|
597
|
-
= chars:
|
|
676
|
+
templateBodyText "template text"
|
|
677
|
+
= chars:templateBodyChar* {
|
|
598
678
|
return annotate([ops.literal, chars.join("")], location());
|
|
599
679
|
}
|
|
600
680
|
|
|
681
|
+
templateDocument "template document"
|
|
682
|
+
= front:frontMatterExpression __ body:templateBody {
|
|
683
|
+
return annotate(applyMacro(front, "@template", body), location());
|
|
684
|
+
}
|
|
685
|
+
/ front:frontMatterYaml? body:templateBody {
|
|
686
|
+
return front
|
|
687
|
+
? annotate([ops.document, front, body], location())
|
|
688
|
+
: annotate(body, location());
|
|
689
|
+
}
|
|
690
|
+
|
|
601
691
|
// A backtick-quoted template literal
|
|
602
692
|
templateLiteral "template literal"
|
|
603
|
-
= "`" head:templateLiteralText tail:(templateSubstitution templateLiteralText)*
|
|
693
|
+
= "`" head:templateLiteralText tail:(templateSubstitution templateLiteralText)* expectBacktick {
|
|
604
694
|
return makeTemplate(ops.template, head, tail, location());
|
|
605
695
|
}
|
|
606
696
|
|
|
@@ -615,7 +705,7 @@ templateLiteralText
|
|
|
615
705
|
|
|
616
706
|
// A substitution in a template literal: `${x}`
|
|
617
707
|
templateSubstitution "template substitution"
|
|
618
|
-
= "${" expression:
|
|
708
|
+
= "${" expression:expectExpression "}" {
|
|
619
709
|
return annotate(expression, location());
|
|
620
710
|
}
|
|
621
711
|
|