@weborigami/language 0.2.7 → 0.2.9

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 CHANGED
@@ -3,16 +3,24 @@ import { Packed } from "@weborigami/async-tree";
3
3
  export * from "./main.js";
4
4
 
5
5
  /**
6
- * A chunk of compiled Origami code. This is just an array with an additional
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 Code = Array<any> & {
10
- location: {
11
- source: Source;
12
- start: Position;
13
- end: Position;
14
- };
15
- source: string;
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,19 +1,19 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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.10.2",
9
+ "@types/node": "22.13.5",
10
10
  "peggy": "4.2.0.",
11
- "typescript": "5.7.2",
12
- "yaml": "2.6.1"
11
+ "typescript": "5.8.2",
12
+ "yaml": "2.7.0"
13
13
  },
14
14
  "dependencies": {
15
- "@weborigami/async-tree": "0.2.7",
16
- "@weborigami/types": "0.2.7",
15
+ "@weborigami/async-tree": "0.2.9",
16
+ "@weborigami/types": "0.2.9",
17
17
  "watcher": "2.3.1"
18
18
  },
19
19
  "scripts": {
@@ -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
- const optimized = [ops.external, key, [ops.scope, key], cache];
47
- // @ts-ignore
48
- annotate(optimized, code.location);
49
- return optimized;
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
- const optimized = [ops.scope, key];
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
- // @ts-ignore
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(child, enableCaching, macros, cache, updatedLocals);
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
- if (code.location) {
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
- // @ts-ignore
122
- annotate(optimized, code.location);
123
- return optimized;
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(makeArray(entries ?? []), location());
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
- return annotate([ops.lambda, parameters ?? [], pipeline], location());
73
+ const lambdaParameters = annotate(parameters ?? [], location());
74
+ return annotate([ops.lambda, lambdaParameters, pipeline], location());
74
75
  }
75
76
  / identifier:identifier __ doubleArrow __ pipeline:pipelineExpression {
76
- return annotate([ops.lambda, [identifier], pipeline], location());
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(tail.reduce(makeCall, head), location());
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
- [ops.lambda, [], downgradeReference(tail[0])],
161
- [ops.lambda, [], downgradeReference(tail[1])]
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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
- = at:"@"? chars:[A-Za-z]+ ":" {
326
- return annotate([ops.builtin, (at ?? "") + chars.join("") + ":"], location());
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 annotate(makeObject(entries ?? [], ops.object), location());
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
- return annotate([key, [ops.inherited, key]], location());
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
- / reference
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 annotate(makeCall(fn, keys), location());
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 annotate(makeCall(fn, [literal]), location());
480
+ return makeCall(fn, [literal]);
478
481
  }
479
482
 
480
- reference
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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 annotate(tail.reduce(makeBinaryOperation, head), location());
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
- return annotate([ops.lambda, ["_"], definition], location());
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, ["_"], makeTemplate(ops.templateIndent, head, tail)],
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 annotate(makeTemplate(ops.template, head, tail), location());
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 annotate(makeUnaryOperation(operator, expression), location());
629
+ return makeUnaryOperation(operator, expression, location());
619
630
  }
620
631
  / callExpression
621
632
 
@@ -1,3 +1,3 @@
1
- import { Code } from "../../index.ts";
1
+ import { AnnotatedCode } from "../../index.ts";
2
2
 
3
- export function parse(input: string, options: any): Code;
3
+ export function parse(input: string, options: any): AnnotatedCode;