@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 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.4",
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.4",
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"
@@ -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[0];
50
+ const parameters = args[1];
51
51
  if (parameters.length > 0) {
52
- const paramNames = parameters.map((param) => param[1]);
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
- // Don't optimize lambda parameter names
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
- // 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
 
@@ -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
- expectGuillemet
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
- = '«' chars:guillemetStringChar* expectGuillemet {
340
- return annotate([ops.literal, chars.join("")], location());
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
- = !('»' / newLine) @textChar
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:(":" @integerLiteral)? slashFollows:slashFollows? {
351
- const portText = port ? `:${port[1]}` : "";
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
- numericLiteral "number"
499
- = floatLiteral
500
- / integerLiteral
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
- // Name of a function parameter
590
- parameter
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([ops.literal, key], location());
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
- parameterList
596
- = list:parameter|1.., separator| separator? {
597
- return annotate(list, location());
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 with a single identifier
601
- parameterSingleton
602
- = param:parameter {
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
- / @numericLiteral !keyChar // numbers + chars would be a key
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) "=" !"=" __ definition:implicitParenthesesCallExpression {
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 lambdaParameters = annotate(
749
- [annotate([ops.literal, "_"], location())],
750
- location()
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 lambdaParameters = annotate(
828
- [annotate([ops.literal, "_"], location())],
829
- location()
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