@weborigami/language 0.6.4 → 0.6.6

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 CHANGED
@@ -4,14 +4,18 @@ export * as compile from "./src/compiler/compile.js";
4
4
  export { default as isOrigamiFrontMatter } from "./src/compiler/isOrigamiFrontMatter.js";
5
5
  export * as Handlers from "./src/handlers/handlers.js";
6
6
  export { default as builtins } from "./src/project/builtins.js";
7
+ export { default as coreGlobals } from "./src/project/coreGlobals.js";
7
8
  export { default as jsGlobals } from "./src/project/jsGlobals.js";
9
+ export { default as projectConfig } from "./src/project/projectConfig.js";
8
10
  export { default as projectGlobals } from "./src/project/projectGlobals.js";
9
11
  export { default as projectRoot } from "./src/project/projectRoot.js";
12
+ export { default as projectRootFromPath } from "./src/project/projectRootFromPath.js";
10
13
  export * as Protocols from "./src/protocols/protocols.js";
11
14
  export { formatError, highlightError } from "./src/runtime/errors.js";
12
15
  export { default as evaluate } from "./src/runtime/evaluate.js";
13
16
  export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
14
17
  export * as expressionFunction from "./src/runtime/expressionFunction.js";
18
+ export { default as expressionObject } from "./src/runtime/expressionObject.js";
15
19
  export * from "./src/runtime/handleExtension.js";
16
20
  export { default as handleExtension } from "./src/runtime/handleExtension.js";
17
21
  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.4",
3
+ "version": "0.6.6",
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.4",
14
+ "@weborigami/async-tree": "0.6.6",
15
15
  "exif-parser": "0.1.12",
16
16
  "watcher": "2.3.1",
17
17
  "yaml": "2.8.1"
@@ -1,6 +1,10 @@
1
1
  import { pathFromKeys, trailingSlash } from "@weborigami/async-tree";
2
2
  import jsGlobals from "../project/jsGlobals.js";
3
- import { entryKey } from "../runtime/expressionObject.js";
3
+ import {
4
+ KEY_TYPE,
5
+ normalizeKey,
6
+ propertyInfo,
7
+ } from "../runtime/expressionObject.js";
4
8
  import { ops } from "../runtime/internal.js";
5
9
  import { annotate, markers, spanLocations } from "./parserHelpers.js";
6
10
 
@@ -47,9 +51,9 @@ export default function optimize(code, options = {}) {
47
51
  return resolvePath(code, globals, parent, locals, cache);
48
52
 
49
53
  case ops.lambda:
50
- const parameters = args[0];
54
+ const parameters = args[1];
51
55
  if (parameters.length > 0) {
52
- const paramNames = parameters.map((param) => param[1]);
56
+ const paramNames = parameters.map((param) => param[0]);
53
57
  locals.push({
54
58
  type: REFERENCE_PARAM,
55
59
  names: paramNames,
@@ -62,10 +66,7 @@ export default function optimize(code, options = {}) {
62
66
 
63
67
  case ops.object:
64
68
  const entries = args;
65
- // Filter out computed property keys when determining local variables
66
- const propertyNames = entries
67
- .map((entry) => entryKey(entry))
68
- .filter((key) => key !== null);
69
+ const propertyNames = getPropertyNames(entries);
69
70
  locals.push({
70
71
  type: REFERENCE_INHERITED,
71
72
  names: propertyNames,
@@ -76,10 +77,7 @@ export default function optimize(code, options = {}) {
76
77
  // Optimize children
77
78
  const optimized = annotate(
78
79
  code.map((child, index) => {
79
- // Don't optimize lambda parameter names
80
- if (op === ops.lambda && index === 1) {
81
- return child;
82
- } else if (op === ops.object && index > 0) {
80
+ if (op === ops.object && index > 0) {
83
81
  const [key, value] = child;
84
82
  const adjustedLocals = avoidLocalRecursion(locals, key);
85
83
  const optimizedKey =
@@ -210,6 +208,14 @@ function findLocalDetails(key, locals) {
210
208
  return null;
211
209
  }
212
210
 
211
+ function getPropertyNames(entries) {
212
+ const infos = entries.map(([key, value]) => propertyInfo(key, value));
213
+ // Filter out computed property keys when determining local variables
214
+ return infos
215
+ .filter((info) => info.keyType !== KEY_TYPE.COMPUTED)
216
+ .map((info) => normalizeKey(info));
217
+ }
218
+
213
219
  function globalReference(key, globals) {
214
220
  const normalized = trailingSlash.remove(key);
215
221
  return globals[normalized];
@@ -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
- // JavaScript treats a missing value as `undefined`
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:parameterList? __ ")" __ doubleArrow __ pipeline:expectPipelineExpression {
111
- const lambdaParameters = parameters ?? annotate([], location());
112
- return annotate([ops.lambda, lambdaParameters, pipeline], location());
111
+ = ("async" __)? "(" __ parameters:paramList? __ ")" __ doubleArrow __ body:expectPipelineExpression {
112
+ parameters ??= annotate([], location());
113
+ return makeLambda(parameters, body, location());
113
114
  }
114
- / parameter:parameterSingleton __ doubleArrow __ pipeline:expectPipelineExpression {
115
- return annotate([ops.lambda, parameter, pipeline], location());
115
+ / parameters:paramSingleton __ doubleArrow __ body:expectPipelineExpression {
116
+ return makeLambda(parameters, body, location());
116
117
  }
117
118
  / conditionalExpression
118
119
 
@@ -162,16 +163,18 @@ comment "comment"
162
163
  / singleLineComment
163
164
 
164
165
  computedPropertyAccess
165
- = computedPropertySpace "[" expression:expectExpression expectClosingBracket {
166
+ = computedPropertySpace? "[" expression:expectExpression expectClosingBracket {
166
167
  return annotate([markers.property, expression], location());
167
168
  }
168
169
 
169
- // A space before a computed property access. This is allowed when not in shell
170
- // mode. In shell mode `foo [bar]` should parse as a function call with a single
171
- // argument of an array, not as a property access.
170
+ // An inline space before a computed property access. This is allowed when not
171
+ // in shell mode. In shell mode `foo [bar]` should parse as a function call with
172
+ // a single argument of an array, not as a property access. In program made, we
173
+ // allow an inline space per JavaScript. JavaScript also allows newlines, but we
174
+ // disallow those to avoid confusion with array/list/object entry separators.
172
175
  computedPropertySpace
173
176
  = shellMode
174
- / !shellMode __
177
+ / !shellMode inlineSpace
175
178
 
176
179
  conditionalExpression
177
180
  = condition:logicalOrExpression tail:(__
@@ -189,9 +192,6 @@ conditionalExpression
189
192
  deferred[1]
190
193
  ], location());
191
194
  }
192
-
193
- digits
194
- = @[0-9]+
195
195
 
196
196
  doubleArrow = "⇒" / "=>"
197
197
 
@@ -269,7 +269,13 @@ expectFrontDelimiter
269
269
  error("Expected \"---\"");
270
270
  }
271
271
 
272
- expectGuillemet
272
+ expectLeftGuillemet
273
+ = '«'
274
+ / .? {
275
+ error("Expected closing guillemet");
276
+ }
277
+
278
+ expectRightGuillemet
273
279
  = '»'
274
280
  / .? {
275
281
  error("Expected closing guillemet");
@@ -303,11 +309,6 @@ exponentiationExpression
303
309
  expression
304
310
  = __ @commaExpression __
305
311
 
306
- floatLiteral "floating-point number"
307
- = digits? "." digits {
308
- return annotate([ops.literal, parseFloat(text())], location());
309
- }
310
-
311
312
  // Marker for the beginning or end of front matter
312
313
  frontDelimiter
313
314
  = "---\n"
@@ -336,19 +337,22 @@ group "parenthetical group"
336
337
  }
337
338
 
338
339
  guillemetString "guillemet string"
339
- = '«' chars:guillemetStringChar* expectGuillemet {
340
- return annotate([ops.literal, chars.join("")], location());
341
- }
340
+ = "«" chars:guillemetStringChar* expectRightGuillemet {
341
+ return annotate([ops.literal, chars.join("")], location());
342
+ }
343
+ / "»" chars:guillemetStringChar* expectLeftGuillemet {
344
+ return annotate([ops.literal, chars.join("")], location());
345
+ }
342
346
 
343
347
  guillemetStringChar
344
- = !('»' / newLine) @textChar
348
+ = !("«" / "»" / newLine) @textChar
345
349
 
346
350
  // A host identifier that may include a colon and port number: `example.com:80`.
347
351
  // This is used as a special case at the head of a path, where we want to
348
352
  // interpret a colon as part of a text identifier.
349
353
  host "HTTP/HTTPS host"
350
- = name:hostname port:(":" @integerLiteral)? slashFollows:slashFollows? {
351
- const portText = port ? `:${port[1]}` : "";
354
+ = name:hostname port:port? slashFollows:slashFollows? {
355
+ const portText = port ?? "";
352
356
  const slashText = slashFollows ? "/" : "";
353
357
  const host = name + portText + slashText;
354
358
  return annotate([ops.literal, host], location());
@@ -407,11 +411,6 @@ implicitParenthesesCallExpression "function call with implicit parentheses"
407
411
  inlineSpace
408
412
  = [ \t]
409
413
 
410
- integerLiteral "integer"
411
- = digits {
412
- return annotate([ops.literal, parseInt(text())], location());
413
- }
414
-
415
414
  // A key in a path or an expression that looks like one
416
415
  key
417
416
  = keyCharStart keyChar* {
@@ -435,7 +434,7 @@ keyChar
435
434
  keyCharStart
436
435
  // All JS identifier characters
437
436
  = char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
438
- / "."
437
+ / "." !".." // a dot, but not a spread/rest operator
439
438
  / "@"
440
439
  / "~"
441
440
 
@@ -494,10 +493,52 @@ newLine
494
493
  / "\r\n"
495
494
  / "\r"
496
495
 
496
+ number
497
+ = numberBigInt
498
+ / numberStandard {
499
+ const stripped = text().replace("_", ""); // remove underscores
500
+ return Number(stripped);
501
+ }
502
+
503
+ numberBigInt
504
+ = digits:numberDecimalDigits "n" {
505
+ const stripped = digits.replace("_", ""); // remove underscores
506
+ return BigInt(stripped);
507
+ }
508
+
509
+ numberBinary
510
+ = "0" [bB] digits:[01_]+
511
+
512
+ numberDecimal
513
+ = numberDecimalDigits? "." numberDecimalDigits numberExponent?
514
+ / numberDecimalDigits numberExponent?
515
+
516
+ numberDecimalDigits
517
+ = [0-9]([_]*[0-9])* {
518
+ return text();
519
+ }
520
+
521
+ numberExponent
522
+ = [eE] [+\-]? digits:numberDecimalDigits
523
+
524
+ numberHex
525
+ = "0" [xX] digits:[0-9a-fA-F_]+
526
+
497
527
  // A number
498
- numericLiteral "number"
499
- = floatLiteral
500
- / integerLiteral
528
+ numberLiteral "number"
529
+ = number:number {
530
+ return annotate([ops.literal, number], location());
531
+ }
532
+
533
+ numberOctal
534
+ = "0" [oO] digits:[0-7]+
535
+
536
+ // Any number that can be parsed with Number(); i.e., not a bigint
537
+ numberStandard
538
+ = numberBinary
539
+ / numberOctal
540
+ / numberHex
541
+ / numberDecimal
501
542
 
502
543
  nullishCoalescingExpression
503
544
  = head:logicalAndExpression tail:(__ "??" __ @logicalAndExpression)* {
@@ -586,20 +627,112 @@ optional
586
627
  return annotate([ops.optional, propertyAccess], location());
587
628
  }
588
629
 
589
- // Name of a function parameter
590
- parameter
630
+ paramArray
631
+ = "[" __ entries:paramArrayEntries __ "]" {
632
+ return annotate([markers.paramArray, ...entries], location());
633
+ }
634
+
635
+ // A list of parameters inside array destructuring: `a, b` in `([a, b]) => a + b`
636
+ // Different than top-level parameters because elements can be elided: `a, , b`
637
+ paramArrayEntries
638
+ = entries:paramArrayEntry|1.., separator| rest:(separator @paramRest?)? {
639
+ if (rest) {
640
+ entries.push(rest);
641
+ }
642
+ return annotate(entries, location());
643
+ }
644
+ / rest:paramRest {
645
+ return annotate([rest], location());
646
+ }
647
+
648
+ // Single parameter in a function's parameter list
649
+ param
650
+ = paramNameWithInitilializer
651
+ / pattern:paramBindingPattern initializer:paramInitializer? {
652
+ return initializer
653
+ ? annotate([markers.paramInitializer, pattern, initializer], location())
654
+ : pattern;
655
+ }
656
+
657
+ paramArrayEntry
658
+ = param
659
+ / &separator {
660
+ // Missing value is allowed
661
+ return undefined;
662
+ }
663
+
664
+ paramBindingPattern
665
+ = paramArray
666
+ / paramObject
667
+
668
+ paramInitializer
669
+ = __ "=" __ @pipelineExpression
670
+
671
+ // A list of lambda parameters inside the parentheses: `a, b` in `(a, b) => a + b`
672
+ paramList
673
+ = entries:param|1.., separator| rest:(separator @paramRest?)? {
674
+ if (rest) {
675
+ entries.push(rest);
676
+ }
677
+ return annotate(entries, location());
678
+ }
679
+ / rest:paramRest {
680
+ return annotate([rest], location());
681
+ }
682
+
683
+ // A single name in a parameter list: `a` in `a, b`
684
+ paramName
591
685
  = key:key {
592
- return annotate([ops.literal, key], location());
686
+ return annotate([markers.paramName, key], location());
687
+ }
688
+
689
+ paramNameWithInitilializer
690
+ = name:paramName initializer:paramInitializer? {
691
+ return initializer
692
+ ? annotate([markers.paramInitializer, name, initializer], location())
693
+ : name;
593
694
  }
594
695
 
595
- parameterList
596
- = list:parameter|1.., separator| separator? {
597
- return annotate(list, location());
696
+ // Object binding pattern in function parameter: `{ a, b: c }`
697
+ paramObject
698
+ = "{" __ entries:paramObjectEntries? __ "}" {
699
+ return annotate([markers.paramObject, ...(entries ?? [])], location());
598
700
  }
599
701
 
600
- // A list with a single identifier
601
- parameterSingleton
602
- = param:parameter {
702
+ // A separated list of parameter object entries inside the curly braces
703
+ paramObjectEntries
704
+ = entries:paramObjectEntry|1.., separator| rest:(separator @paramRest)? {
705
+ if (rest) {
706
+ entries.push(rest);
707
+ }
708
+ return annotate(entries, location());
709
+ }
710
+ / rest:paramRest {
711
+ return annotate([rest], location());
712
+ }
713
+
714
+ // An entry in a parameter object: `a: b` in `{ a: b }`
715
+ paramObjectEntry
716
+ = key:objectPublicKey __ ":" __ param:param {
717
+ return annotate([key, param], location());
718
+ }
719
+ / name:paramName initializer:paramInitializer? {
720
+ const binding = initializer
721
+ ? annotate([markers.paramInitializer, name, initializer], location())
722
+ : name;
723
+ return annotate([name[1], binding], location());
724
+ }
725
+
726
+ // Optional rest parameter for param array or object
727
+ paramRest
728
+ = "..." __ param:param {
729
+ return annotate([markers.paramRest, param], location());
730
+ }
731
+
732
+ // A lambda parameter list with a single identifier with no parentheses:
733
+ // `x` in `x => x + 1`
734
+ paramSingleton
735
+ = param:paramName {
603
736
  return annotate([param], location());
604
737
  }
605
738
 
@@ -619,7 +752,7 @@ parenthesesArgumentList "list"
619
752
  // Function arguments in parentheses
620
753
  parenthesesArguments "function arguments in parentheses"
621
754
  = inlineSpace* "(" __ list:parenthesesArgumentList? __ expectClosingParenthesis {
622
- return annotate(list ?? [undefined], location());
755
+ return annotate(list ?? [], location());
623
756
  }
624
757
 
625
758
  // A slash-separated path of keys that follows a call target, such as the path
@@ -660,6 +793,11 @@ pipelineExpression
660
793
  );
661
794
  }
662
795
 
796
+ port
797
+ = ":" [0-9]+ {
798
+ return text();
799
+ }
800
+
663
801
  primary
664
802
  // The following start with distinct characters
665
803
  = stringLiteral
@@ -671,7 +809,7 @@ primary
671
809
  / templateLiteral
672
810
 
673
811
  // These are more ambiguous
674
- / @numericLiteral !keyChar // numbers + chars would be a key
812
+ / @numberLiteral !keyChar // numbers + chars would be a key
675
813
  / pathLiteral
676
814
 
677
815
  // Top-level Origami progam with possible shebang directive (which is ignored)
@@ -741,15 +879,13 @@ shiftOperator
741
879
  // A shorthand lambda expression: `=foo(_)`
742
880
  shorthandFunction "lambda function"
743
881
  // Avoid a following equal sign (for an equality)
744
- = (shellMode / programMode) "=" !"=" __ definition:implicitParenthesesCallExpression {
882
+ = (shellMode / programMode) "=" !"=" __ body:implicitParenthesesCallExpression {
745
883
  if (options.mode === "program") {
746
884
  throw new Error("Parse error: shorthand function syntax isn't allowed in Origami programs. Use arrow syntax instead.");
747
885
  }
748
- const lambdaParameters = annotate(
749
- [annotate([ops.literal, "_"], location())],
750
- location()
751
- );
752
- return annotate([ops.lambda, lambdaParameters, definition], location());
886
+ const underscore = annotate([markers.paramName, "_"], location());
887
+ const parameters = annotate([underscore], location());
888
+ return makeLambda(parameters, body, location());
753
889
  }
754
890
  / implicitParenthesesCallExpression
755
891
 
@@ -824,11 +960,9 @@ templateDocument "template document"
824
960
  if (options.front) {
825
961
  return makeDocument(options.front, body, location());
826
962
  }
827
- const lambdaParameters = annotate(
828
- [annotate([ops.literal, "_"], location())],
829
- location()
830
- );
831
- return annotate([ops.lambda, lambdaParameters, body], location());
963
+ const underscore = annotate([markers.paramName, "_"], location());
964
+ const parameters = annotate([underscore], location());
965
+ return makeLambda(parameters, body, location());
832
966
  }
833
967
 
834
968
  // A backtick-quoted template literal