@weborigami/language 0.6.3 → 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.3",
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.3",
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,
@@ -62,7 +62,10 @@ export default function optimize(code, options = {}) {
62
62
 
63
63
  case ops.object:
64
64
  const entries = args;
65
- const propertyNames = entries.map((entry) => entryKey(entry));
65
+ // Filter out computed property keys when determining local variables
66
+ const propertyNames = entries
67
+ .map((entry) => entryKey(entry))
68
+ .filter((key) => key !== null);
66
69
  locals.push({
67
70
  type: REFERENCE_INHERITED,
68
71
  names: propertyNames,
@@ -73,22 +76,21 @@ export default function optimize(code, options = {}) {
73
76
  // Optimize children
74
77
  const optimized = annotate(
75
78
  code.map((child, index) => {
76
- // Don't optimize lambda parameter names
77
- if (op === ops.lambda && index === 1) {
78
- return child;
79
- } else if (op === ops.object && index > 0) {
79
+ if (op === ops.object && index > 0) {
80
80
  const [key, value] = child;
81
81
  const adjustedLocals = avoidLocalRecursion(locals, key);
82
- return annotate(
83
- [
84
- key,
85
- optimize(/** @type {AnnotatedCode} */ (value), {
86
- ...options,
87
- locals: adjustedLocals,
88
- }),
89
- ],
90
- child.location
91
- );
82
+ const optimizedKey =
83
+ typeof key === "string"
84
+ ? key
85
+ : optimize(/** @type {AnnotatedCode} */ (key), {
86
+ ...options,
87
+ locals: adjustedLocals,
88
+ });
89
+ const optimizedValue = optimize(/** @type {AnnotatedCode} */ (value), {
90
+ ...options,
91
+ locals: adjustedLocals,
92
+ });
93
+ return annotate([optimizedKey, optimizedValue], child.location);
92
94
  } else if (Array.isArray(child) && "location" in child) {
93
95
  // Review: Aside from ops.object (above), what non-instruction arrays
94
96
  // does this descend into?
@@ -18,8 +18,10 @@ import {
18
18
  makeArray,
19
19
  makeBinaryOperation,
20
20
  makeCall,
21
+ makeCallChain,
21
22
  makeDeferredArguments,
22
23
  makeDocument,
24
+ makeLambda,
23
25
  makeObject,
24
26
  makePath,
25
27
  makePipeline,
@@ -83,7 +85,7 @@ arguments "function arguments"
83
85
  / pathArguments
84
86
  / propertyAccess
85
87
  / computedPropertyAccess
86
- // / optionalChaining
88
+ / optional
87
89
  / templateLiteral
88
90
 
89
91
  arrayLiteral "array"
@@ -100,18 +102,18 @@ arrayEntries
100
102
  arrayEntry
101
103
  = spreadElement
102
104
  / pipelineExpression
103
- // JavaScript treats a missing value as `undefined`
104
- / __ !"]" {
105
+ / &separator {
106
+ // Missing value is allowed
105
107
  return annotate([ops.literal, undefined], location());
106
108
  }
107
109
 
108
110
  arrowFunction
109
- = ("async" __)? "(" __ parameters:parameterList? __ ")" __ doubleArrow __ pipeline:expectPipelineExpression {
110
- const lambdaParameters = parameters ?? annotate([], location());
111
- return annotate([ops.lambda, lambdaParameters, pipeline], location());
111
+ = ("async" __)? "(" __ parameters:paramList? __ ")" __ doubleArrow __ body:expectPipelineExpression {
112
+ parameters ??= annotate([], location());
113
+ return makeLambda(parameters, body, location());
112
114
  }
113
- / parameter:parameterSingleton __ doubleArrow __ pipeline:expectPipelineExpression {
114
- return annotate([ops.lambda, parameter, pipeline], location());
115
+ / parameters:paramSingleton __ doubleArrow __ body:expectPipelineExpression {
116
+ return makeLambda(parameters, body, location());
115
117
  }
116
118
  / conditionalExpression
117
119
 
@@ -143,10 +145,7 @@ bitwiseXorOperator
143
145
  // `fn(arg1)(arg2)(arg3)`.
144
146
  callExpression "function call"
145
147
  = head:uriExpression tail:arguments* {
146
- return tail.reduce(
147
- (target, args) => makeCall(target, args, location()),
148
- head
149
- );
148
+ return makeCallChain(head, tail, location());
150
149
  }
151
150
 
152
151
  // A comma-separated list of expressions: `x, y, z`
@@ -168,9 +167,9 @@ computedPropertyAccess
168
167
  return annotate([markers.property, expression], location());
169
168
  }
170
169
 
171
- // A space before a computed property access. This is allowed when in not in
172
- // shell mode, but not in shell mode. In shell mode `foo [bar]` should parse as
173
- // a function call with a single argument of an array, not as a property access.
170
+ // A space before a computed property access. This is allowed when not in shell
171
+ // mode. In shell mode `foo [bar]` should parse as a function call with a single
172
+ // argument of an array, not as a property access.
174
173
  computedPropertySpace
175
174
  = shellMode
176
175
  / !shellMode __
@@ -191,9 +190,6 @@ conditionalExpression
191
190
  deferred[1]
192
191
  ], location());
193
192
  }
194
-
195
- digits
196
- = @[0-9]+
197
193
 
198
194
  doubleArrow = "⇒" / "=>"
199
195
 
@@ -271,7 +267,13 @@ expectFrontDelimiter
271
267
  error("Expected \"---\"");
272
268
  }
273
269
 
274
- expectGuillemet
270
+ expectLeftGuillemet
271
+ = '«'
272
+ / .? {
273
+ error("Expected closing guillemet");
274
+ }
275
+
276
+ expectRightGuillemet
275
277
  = '»'
276
278
  / .? {
277
279
  error("Expected closing guillemet");
@@ -305,11 +307,6 @@ exponentiationExpression
305
307
  expression
306
308
  = __ @commaExpression __
307
309
 
308
- floatLiteral "floating-point number"
309
- = digits? "." digits {
310
- return annotate([ops.literal, parseFloat(text())], location());
311
- }
312
-
313
310
  // Marker for the beginning or end of front matter
314
311
  frontDelimiter
315
312
  = "---\n"
@@ -338,19 +335,22 @@ group "parenthetical group"
338
335
  }
339
336
 
340
337
  guillemetString "guillemet string"
341
- = '«' chars:guillemetStringChar* expectGuillemet {
342
- return annotate([ops.literal, chars.join("")], location());
343
- }
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
+ }
344
344
 
345
345
  guillemetStringChar
346
- = !('»' / newLine) @textChar
346
+ = !("«" / "»" / newLine) @textChar
347
347
 
348
348
  // A host identifier that may include a colon and port number: `example.com:80`.
349
349
  // This is used as a special case at the head of a path, where we want to
350
350
  // interpret a colon as part of a text identifier.
351
351
  host "HTTP/HTTPS host"
352
- = name:hostname port:(":" @integerLiteral)? slashFollows:slashFollows? {
353
- const portText = port ? `:${port[1]}` : "";
352
+ = name:hostname port:port? slashFollows:slashFollows? {
353
+ const portText = port ?? "";
354
354
  const slashText = slashFollows ? "/" : "";
355
355
  const host = name + portText + slashText;
356
356
  return annotate([ops.literal, host], location());
@@ -383,26 +383,32 @@ identifierPart "JavaScript identifier continuation"
383
383
  identifierStart "JavaScript identifier start"
384
384
  = char:. &{ return char.match(/[$_\p{ID_Start}]/u) }
385
385
 
386
- implicitParenthesesCallExpression "function call with implicit parentheses"
387
- = head:arrowFunction args:(inlineSpace+ @implicitParensthesesArguments)? {
388
- return args ? makeCall(head, args, location()) : head;
386
+ // A single argument for an implicit parens call. This differs from
387
+ // `parenthesesArgument` in that the term can't be a pipeline.
388
+ implicitParenthesesArgument
389
+ = "..." arg:shorthandFunction {
390
+ return annotate([markers.spread, arg], location());
391
+ }
392
+ / shorthandFunction
393
+
394
+ // A separated list of arguments for an implicit parens call.
395
+ implicitParenthesesArgumentList "list"
396
+ = args:implicitParenthesesArgument|1.., separator| separator? {
397
+ return annotate(args, location());
389
398
  }
390
399
 
391
- // A separated list of values for an implicit parens call. This differs from
392
- // `list` in that the value term can't be a pipeline.
393
- implicitParensthesesArguments
394
- = shellMode values:shorthandFunction|1.., separator| separator? {
395
- return annotate(values, location());
400
+ // A separated list of values for an implicit parens call.
401
+ implicitParenthesesArguments
402
+ = shellMode inlineSpace+ @implicitParenthesesArgumentList
403
+
404
+ implicitParenthesesCallExpression "function call with implicit parentheses"
405
+ = head:arrowFunction args:implicitParenthesesArguments? {
406
+ return args ? makeCall(head, args, location()) : head;
396
407
  }
397
408
 
398
409
  inlineSpace
399
410
  = [ \t]
400
411
 
401
- integerLiteral "integer"
402
- = digits {
403
- return annotate([ops.literal, parseInt(text())], location());
404
- }
405
-
406
412
  // A key in a path or an expression that looks like one
407
413
  key
408
414
  = keyCharStart keyChar* {
@@ -426,16 +432,10 @@ keyChar
426
432
  keyCharStart
427
433
  // All JS identifier characters
428
434
  = char:. &{ return char.match(/[$_\p{ID_Continue}]/u) }
429
- / "."
435
+ / "." !".." // a dot, but not a spread/rest operator
430
436
  / "@"
431
437
  / "~"
432
438
 
433
- // A separated list of values
434
- list "list"
435
- = values:pipelineExpression|1.., separator| separator? {
436
- return annotate(values, location());
437
- }
438
-
439
439
  logicalAndExpression
440
440
  = head:bitwiseOrExpression tail:(__ "&&" __ @bitwiseOrExpression)* {
441
441
  return tail.length === 0
@@ -491,10 +491,52 @@ newLine
491
491
  / "\r\n"
492
492
  / "\r"
493
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
+
494
525
  // A number
495
- numericLiteral "number"
496
- = floatLiteral
497
- / 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
498
540
 
499
541
  nullishCoalescingExpression
500
542
  = head:logicalAndExpression tail:(__ "??" __ @logicalAndExpression)* {
@@ -561,7 +603,8 @@ objectShorthandProperty "object identifier"
561
603
  }
562
604
 
563
605
  objectPublicKey
564
- = key:key slash:"/"? {
606
+ = "[" __ @pipelineExpression __ expectClosingBracket
607
+ / key:key slash:"/"? {
565
608
  return text();
566
609
  }
567
610
  / string:stringLiteral {
@@ -569,31 +612,144 @@ objectPublicKey
569
612
  return string[1];
570
613
  }
571
614
 
572
- optionalChaining
573
- = __ "?." __ property:identifier {
574
- return annotate([ops.optionalTraverse, property], location());
615
+ // Optional chaining
616
+ optional
617
+ = __ "?." args:parenthesesArguments {
618
+ return annotate([ops.optional, args], location());
619
+ }
620
+ / __ "?." access:computedPropertyAccess {
621
+ return annotate([ops.optional, access], location());
622
+ }
623
+ / __ "?." whitespaceOptionalForProgram property:identifierLiteral {
624
+ const propertyAccess = annotate([markers.property, property], location());
625
+ return annotate([ops.optional, propertyAccess], location());
626
+ }
627
+
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());
575
644
  }
576
645
 
577
- // Name of a unction parameter
578
- parameter
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
579
683
  = key:key {
580
- 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;
692
+ }
693
+
694
+ // Object binding pattern in function parameter: `{ a, b: c }`
695
+ paramObject
696
+ = "{" __ entries:paramObjectEntries? __ "}" {
697
+ return annotate([markers.paramObject, ...(entries ?? [])], location());
581
698
  }
582
699
 
583
- parameterList
584
- = list:parameter|1.., separator| separator? {
585
- return annotate(list, location());
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());
586
710
  }
587
711
 
588
- // A list with a single identifier
589
- parameterSingleton
590
- = param:parameter {
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 {
591
734
  return annotate([param], location());
592
735
  }
593
736
 
737
+ // A single argument inside parentheses
738
+ parenthesesArgument
739
+ = "..." arg:pipelineExpression {
740
+ return annotate([markers.spread, arg], location());
741
+ }
742
+ / pipelineExpression
743
+
744
+ // A separated list of arguments inside parentheses
745
+ parenthesesArgumentList "list"
746
+ = args:parenthesesArgument|1.., separator| separator? {
747
+ return annotate(args, location());
748
+ }
749
+
594
750
  // Function arguments in parentheses
595
751
  parenthesesArguments "function arguments in parentheses"
596
- = "(" __ list:list? __ expectClosingParenthesis {
752
+ = inlineSpace* "(" __ list:parenthesesArgumentList? __ expectClosingParenthesis {
597
753
  return annotate(list ?? [undefined], location());
598
754
  }
599
755
 
@@ -635,6 +791,11 @@ pipelineExpression
635
791
  );
636
792
  }
637
793
 
794
+ port
795
+ = ":" [0-9]+ {
796
+ return text();
797
+ }
798
+
638
799
  primary
639
800
  // The following start with distinct characters
640
801
  = stringLiteral
@@ -646,7 +807,7 @@ primary
646
807
  / templateLiteral
647
808
 
648
809
  // These are more ambiguous
649
- / @numericLiteral !keyChar // numbers + chars would be a key
810
+ / @numberLiteral !keyChar // numbers + chars would be a key
650
811
  / pathLiteral
651
812
 
652
813
  // Top-level Origami progam with possible shebang directive (which is ignored)
@@ -716,15 +877,13 @@ shiftOperator
716
877
  // A shorthand lambda expression: `=foo(_)`
717
878
  shorthandFunction "lambda function"
718
879
  // Avoid a following equal sign (for an equality)
719
- = (shellMode / programMode) "=" !"=" __ definition:implicitParenthesesCallExpression {
880
+ = (shellMode / programMode) "=" !"=" __ body:implicitParenthesesCallExpression {
720
881
  if (options.mode === "program") {
721
882
  throw new Error("Parse error: shorthand function syntax isn't allowed in Origami programs. Use arrow syntax instead.");
722
883
  }
723
- const lambdaParameters = annotate(
724
- [annotate([ops.literal, "_"], location())],
725
- location()
726
- );
727
- 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());
728
887
  }
729
888
  / implicitParenthesesCallExpression
730
889
 
@@ -764,7 +923,7 @@ slashFollows
764
923
 
765
924
  spreadElement
766
925
  = ellipsis __ value:expectPipelineExpression {
767
- return annotate([ops.spread, value], location());
926
+ return annotate([markers.spread, value], location());
768
927
  }
769
928
 
770
929
  stringLiteral "string"
@@ -799,11 +958,9 @@ templateDocument "template document"
799
958
  if (options.front) {
800
959
  return makeDocument(options.front, body, location());
801
960
  }
802
- const lambdaParameters = annotate(
803
- [annotate([ops.literal, "_"], location())],
804
- location()
805
- );
806
- 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());
807
964
  }
808
965
 
809
966
  // A backtick-quoted template literal