@weborigami/language 0.3.4-jse.7 → 0.3.4-jse.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.3.4-jse.7",
3
+ "version": "0.3.4-jse.9",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,8 +11,8 @@
11
11
  "typescript": "5.8.2"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/async-tree": "0.3.4-jse.7",
15
- "@weborigami/types": "0.3.4-jse.7",
14
+ "@weborigami/async-tree": "0.3.4-jse.9",
15
+ "@weborigami/types": "0.3.4-jse.9",
16
16
  "watcher": "2.3.1",
17
17
  "yaml": "2.7.0"
18
18
  },
@@ -37,8 +37,8 @@ export default function optimize(code, options = {}) {
37
37
  const [op, ...args] = code;
38
38
  switch (op) {
39
39
  case markers.global:
40
- // Replace global op with the globals
41
- return annotate([globals, args[0]], code.location);
40
+ // Replace with the indicated global
41
+ return globals[args[0]];
42
42
 
43
43
  case markers.traverse:
44
44
  return resolvePath(code, globals, locals, cache);
@@ -56,7 +56,7 @@ export default function optimize(code, options = {}) {
56
56
 
57
57
  case ops.object:
58
58
  const entries = args;
59
- const keys = entries.map(entryKey);
59
+ const keys = entries.map((entry) => entryKey(entry));
60
60
  locals.push(keys);
61
61
  break;
62
62
  }
@@ -70,13 +70,16 @@ export default function optimize(code, options = {}) {
70
70
  } else if (op === ops.object && index > 0) {
71
71
  const [key, value] = child;
72
72
  const adjustedLocals = avoidLocalRecursion(locals, key);
73
- return [
74
- key,
75
- optimize(/** @type {AnnotatedCode} */ (value), {
76
- ...options,
77
- locals: adjustedLocals,
78
- }),
79
- ];
73
+ return annotate(
74
+ [
75
+ key,
76
+ optimize(/** @type {AnnotatedCode} */ (value), {
77
+ ...options,
78
+ locals: adjustedLocals,
79
+ }),
80
+ ],
81
+ child.location
82
+ );
80
83
  } else if (Array.isArray(child) && "location" in child) {
81
84
  // Review: Aside from ops.object (above), what non-instruction arrays
82
85
  // does this descend into?
@@ -140,7 +143,7 @@ function compoundReference(key, globals, locals, location) {
140
143
  const type = referenceType(head, globals, locals);
141
144
  let result;
142
145
  if (type === REFERENCE_GLOBAL) {
143
- result = globalReference(head, globals, location);
146
+ result = globalReference(head, globals);
144
147
  } else if (type === REFERENCE_LOCAL) {
145
148
  result = localReference(head, locals, location);
146
149
  } else {
@@ -177,9 +180,9 @@ function getLocalReferenceDepth(locals, key) {
177
180
  return depth;
178
181
  }
179
182
 
180
- function globalReference(key, globals, location) {
183
+ function globalReference(key, globals) {
181
184
  const normalized = trailingSlash.remove(key);
182
- return annotate([globals, normalized], location);
185
+ return globals[normalized];
183
186
  }
184
187
 
185
188
  function inlineLiteral(code) {
@@ -191,6 +194,7 @@ function inlineLiteral(code) {
191
194
  function localReference(key, locals, location) {
192
195
  const normalized = trailingSlash.remove(key);
193
196
  const depth = getLocalReferenceDepth(locals, normalized);
197
+ /** @type {any[]} */
194
198
  const context = [ops.context];
195
199
  if (depth > 0) {
196
200
  context.push(depth);
@@ -249,24 +253,29 @@ function reference(code, globals, locals) {
249
253
 
250
254
  // See if the whole key is a global or local variable
251
255
  let type = referenceType(key, globals, locals);
252
- let result;
253
256
  if (type === REFERENCE_GLOBAL) {
254
- result = globalReference(key, globals, location);
257
+ return {
258
+ type,
259
+ result: globalReference(key, globals),
260
+ };
255
261
  } else if (type === REFERENCE_LOCAL) {
256
- result = localReference(key, locals, location);
257
- } else {
258
- // Try key as a compound reference x.y.z
259
- const compound = compoundReference(key, globals, locals, location);
260
- result = compound?.result;
261
- type = compound?.type;
262
+ return {
263
+ type,
264
+ result: localReference(key, locals, location),
265
+ };
262
266
  }
263
267
 
264
- if (!result) {
265
- // If none of the above worked, it must be an external reference
266
- result = externalReference(key, locals, location);
268
+ // Try key as a compound reference x.y.z
269
+ const compound = compoundReference(key, globals, locals, location);
270
+ if (compound.type !== REFERENCE_EXTERNAL) {
271
+ return compound;
267
272
  }
268
273
 
269
- return { type, result };
274
+ // Not a compound reference, must be external
275
+ return {
276
+ type: REFERENCE_EXTERNAL,
277
+ result: externalReference(key, locals, location),
278
+ };
270
279
  }
271
280
 
272
281
  function referenceType(key, globals, locals) {
@@ -287,7 +296,13 @@ function resolvePath(code, globals, locals, cache) {
287
296
 
288
297
  let { type, result } = reference(head, globals, locals);
289
298
 
290
- result.push(...tail);
299
+ if (tail.length > 0) {
300
+ if (result instanceof Array) {
301
+ result.push(...tail);
302
+ } else {
303
+ result = annotate([result, ...tail], code.location);
304
+ }
305
+ }
291
306
 
292
307
  if (type === REFERENCE_EXTERNAL && cache !== null) {
293
308
  // Cache external path
@@ -299,6 +314,7 @@ function resolvePath(code, globals, locals, cache) {
299
314
 
300
315
  function scopeCall(locals, location) {
301
316
  const depth = locals.length;
317
+ /** @type {any[]} */
302
318
  const code = [ops.scope];
303
319
  if (depth > 0) {
304
320
  // Add context for appropriate depth to scope call
@@ -164,10 +164,17 @@ comment "comment"
164
164
  / singleLineComment
165
165
 
166
166
  computedPropertyAccess
167
- = __ "[" expression:expectExpression expectClosingBracket {
167
+ = computedPropertySpace "[" expression:expectExpression expectClosingBracket {
168
168
  return annotate([markers.property, expression], location());
169
169
  }
170
170
 
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.
174
+ computedPropertySpace
175
+ = shellMode
176
+ / !shellMode __
177
+
171
178
  conditionalExpression
172
179
  = condition:logicalOrExpression tail:(__
173
180
  "?" __ @shorthandFunction __
@@ -773,7 +780,12 @@ templateBodyText "template text"
773
780
 
774
781
  templateDocument "template document"
775
782
  = front:frontMatterExpression __ body:templateBody {
783
+ // TODO: Deprecate @template
776
784
  const macroName = text().includes("@template") ? "@template" : "_template";
785
+ if (macroName === "@template") {
786
+ // If the front matter is a macro, apply it to the body
787
+ console.warn("Warning: the @template() macro is deprecated. Use _template() instead.");
788
+ }
777
789
  return annotate(applyMacro(front, macroName, body), location());
778
790
  }
779
791
  / front:frontMatterYaml body:templateBody {
@@ -854,7 +866,8 @@ uriKey
854
866
  // A single character in a URI key
855
867
  uriKeyChar
856
868
  // Accept anything that doesn't end the URI key or path
857
- = [^/,\)\]\}\s]
869
+ // Reject whitespace; see notes for `whitespace` term
870
+ = char:[^/,\)\]\}] !&{ return /\s/.test(char); } { return char; }
858
871
  / escapedChar
859
872
 
860
873
  // A slash-separated path of keys: `a/b/c`
@@ -877,8 +890,9 @@ unaryOperator
877
890
  / minus
878
891
 
879
892
  whitespace
880
- = inlineSpace
881
- / newLine
893
+ // JavaScript considers a large number of characters whitespace so we use the
894
+ // `/s` definition to avoid missing any.
895
+ = char:. &{ return /\s/.test(char); } { return char; }
882
896
  / comment
883
897
 
884
898
  whitespaceWithNewLine
@@ -205,7 +205,7 @@ function peg$parse(input, options) {
205
205
  var peg$FAILED = {};
206
206
  var peg$source = options.grammarSource;
207
207
 
208
- var peg$startRuleFunctions = { __: peg$parse__, additiveExpression: peg$parseadditiveExpression, additiveOperator: peg$parseadditiveOperator, angleBracketLiteral: peg$parseangleBracketLiteral, angleBracketPath: peg$parseangleBracketPath, angleBracketKey: peg$parseangleBracketKey, angleBracketPathChar: peg$parseangleBracketPathChar, arguments: peg$parsearguments, arrayLiteral: peg$parsearrayLiteral, arrayEntries: peg$parsearrayEntries, arrayEntry: peg$parsearrayEntry, arrowFunction: peg$parsearrowFunction, bitwiseAndExpression: peg$parsebitwiseAndExpression, bitwiseAndOperator: peg$parsebitwiseAndOperator, bitwiseOrExpression: peg$parsebitwiseOrExpression, bitwiseOrOperator: peg$parsebitwiseOrOperator, bitwiseXorExpression: peg$parsebitwiseXorExpression, bitwiseXorOperator: peg$parsebitwiseXorOperator, callExpression: peg$parsecallExpression, commaExpression: peg$parsecommaExpression, comment: peg$parsecomment, computedPropertyAccess: peg$parsecomputedPropertyAccess, conditionalExpression: peg$parseconditionalExpression, digits: peg$parsedigits, doubleArrow: peg$parsedoubleArrow, doubleQuoteString: peg$parsedoubleQuoteString, doubleQuoteStringChar: peg$parsedoubleQuoteStringChar, ellipsis: peg$parseellipsis, equalityExpression: peg$parseequalityExpression, equalityOperator: peg$parseequalityOperator, escapedChar: peg$parseescapedChar, expectBacktick: peg$parseexpectBacktick, expectClosingBrace: peg$parseexpectClosingBrace, expectClosingBracket: peg$parseexpectClosingBracket, expectClosingParenthesis: peg$parseexpectClosingParenthesis, expectDoubleQuote: peg$parseexpectDoubleQuote, expectExpression: peg$parseexpectExpression, expectFrontDelimiter: peg$parseexpectFrontDelimiter, expectGuillemet: peg$parseexpectGuillemet, expectSingleQuote: peg$parseexpectSingleQuote, expectPipelineExpression: peg$parseexpectPipelineExpression, expectUnaryExpression: peg$parseexpectUnaryExpression, exponentiationExpression: peg$parseexponentiationExpression, expression: peg$parseexpression, floatLiteral: peg$parsefloatLiteral, frontDelimiter: peg$parsefrontDelimiter, frontMatterExpression: peg$parsefrontMatterExpression, frontMatterText: peg$parsefrontMatterText, frontMatterYaml: peg$parsefrontMatterYaml, group: peg$parsegroup, guillemetString: peg$parseguillemetString, guillemetStringChar: peg$parseguillemetStringChar, host: peg$parsehost, hostname: peg$parsehostname, identifier: peg$parseidentifier, identifierLiteral: peg$parseidentifierLiteral, identifierPart: peg$parseidentifierPart, identifierStart: peg$parseidentifierStart, implicitParenthesesCallExpression: peg$parseimplicitParenthesesCallExpression, implicitParensthesesArguments: peg$parseimplicitParensthesesArguments, inlineSpace: peg$parseinlineSpace, integerLiteral: peg$parseintegerLiteral, key: peg$parsekey, keyChar: peg$parsekeyChar, keyCharStart: peg$parsekeyCharStart, list: peg$parselist, logicalAndExpression: peg$parselogicalAndExpression, logicalOrExpression: peg$parselogicalOrExpression, minus: peg$parseminus, multiLineComment: peg$parsemultiLineComment, multiplicativeExpression: peg$parsemultiplicativeExpression, multiplicativeOperator: peg$parsemultiplicativeOperator, newExpression: peg$parsenewExpression, newLine: peg$parsenewLine, numericLiteral: peg$parsenumericLiteral, nullishCoalescingExpression: peg$parsenullishCoalescingExpression, objectLiteral: peg$parseobjectLiteral, objectEntries: peg$parseobjectEntries, objectEntry: peg$parseobjectEntry, objectGetter: peg$parseobjectGetter, objectHiddenKey: peg$parseobjectHiddenKey, objectKey: peg$parseobjectKey, objectProperty: peg$parseobjectProperty, objectShorthandProperty: peg$parseobjectShorthandProperty, objectPublicKey: peg$parseobjectPublicKey, optionalChaining: peg$parseoptionalChaining, parameter: peg$parseparameter, parameterList: peg$parseparameterList, parameterSingleton: peg$parseparameterSingleton, parenthesesArguments: peg$parseparenthesesArguments, pathArguments: peg$parsepathArguments, pathKeys: peg$parsepathKeys, pathLiteral: peg$parsepathLiteral, pathSegment: peg$parsepathSegment, pipelineExpression: peg$parsepipelineExpression, primary: peg$parseprimary, program: peg$parseprogram, propertyAccess: peg$parsepropertyAccess, regexFlags: peg$parseregexFlags, regexLiteral: peg$parseregexLiteral, regexLiteralChar: peg$parseregexLiteralChar, relationalExpression: peg$parserelationalExpression, relationalOperator: peg$parserelationalOperator, separator: peg$parseseparator, shebang: peg$parseshebang, shellMode: peg$parseshellMode, shiftExpression: peg$parseshiftExpression, shiftOperator: peg$parseshiftOperator, shorthandFunction: peg$parseshorthandFunction, singleArrow: peg$parsesingleArrow, singleLineComment: peg$parsesingleLineComment, singleQuoteString: peg$parsesingleQuoteString, singleQuoteStringChar: peg$parsesingleQuoteStringChar, slash: peg$parseslash, slashes: peg$parseslashes, slashFollows: peg$parseslashFollows, spreadElement: peg$parsespreadElement, stringLiteral: peg$parsestringLiteral, templateBody: peg$parsetemplateBody, templateBodyChar: peg$parsetemplateBodyChar, templateBodyText: peg$parsetemplateBodyText, templateDocument: peg$parsetemplateDocument, templateLiteral: peg$parsetemplateLiteral, templateLiteralChar: peg$parsetemplateLiteralChar, templateLiteralText: peg$parsetemplateLiteralText, templateSubstitution: peg$parsetemplateSubstitution, textChar: peg$parsetextChar, unaryExpression: peg$parseunaryExpression, uri: peg$parseuri, uriExpression: peg$parseuriExpression, uriKey: peg$parseuriKey, uriKeyChar: peg$parseuriKeyChar, uriPath: peg$parseuriPath, uriScheme: peg$parseuriScheme, unaryOperator: peg$parseunaryOperator, whitespace: peg$parsewhitespace, whitespaceWithNewLine: peg$parsewhitespaceWithNewLine };
208
+ var peg$startRuleFunctions = { __: peg$parse__, additiveExpression: peg$parseadditiveExpression, additiveOperator: peg$parseadditiveOperator, angleBracketLiteral: peg$parseangleBracketLiteral, angleBracketPath: peg$parseangleBracketPath, angleBracketKey: peg$parseangleBracketKey, angleBracketPathChar: peg$parseangleBracketPathChar, arguments: peg$parsearguments, arrayLiteral: peg$parsearrayLiteral, arrayEntries: peg$parsearrayEntries, arrayEntry: peg$parsearrayEntry, arrowFunction: peg$parsearrowFunction, bitwiseAndExpression: peg$parsebitwiseAndExpression, bitwiseAndOperator: peg$parsebitwiseAndOperator, bitwiseOrExpression: peg$parsebitwiseOrExpression, bitwiseOrOperator: peg$parsebitwiseOrOperator, bitwiseXorExpression: peg$parsebitwiseXorExpression, bitwiseXorOperator: peg$parsebitwiseXorOperator, callExpression: peg$parsecallExpression, commaExpression: peg$parsecommaExpression, comment: peg$parsecomment, computedPropertyAccess: peg$parsecomputedPropertyAccess, computedPropertySpace: peg$parsecomputedPropertySpace, conditionalExpression: peg$parseconditionalExpression, digits: peg$parsedigits, doubleArrow: peg$parsedoubleArrow, doubleQuoteString: peg$parsedoubleQuoteString, doubleQuoteStringChar: peg$parsedoubleQuoteStringChar, ellipsis: peg$parseellipsis, equalityExpression: peg$parseequalityExpression, equalityOperator: peg$parseequalityOperator, escapedChar: peg$parseescapedChar, expectBacktick: peg$parseexpectBacktick, expectClosingBrace: peg$parseexpectClosingBrace, expectClosingBracket: peg$parseexpectClosingBracket, expectClosingParenthesis: peg$parseexpectClosingParenthesis, expectDoubleQuote: peg$parseexpectDoubleQuote, expectExpression: peg$parseexpectExpression, expectFrontDelimiter: peg$parseexpectFrontDelimiter, expectGuillemet: peg$parseexpectGuillemet, expectSingleQuote: peg$parseexpectSingleQuote, expectPipelineExpression: peg$parseexpectPipelineExpression, expectUnaryExpression: peg$parseexpectUnaryExpression, exponentiationExpression: peg$parseexponentiationExpression, expression: peg$parseexpression, floatLiteral: peg$parsefloatLiteral, frontDelimiter: peg$parsefrontDelimiter, frontMatterExpression: peg$parsefrontMatterExpression, frontMatterText: peg$parsefrontMatterText, frontMatterYaml: peg$parsefrontMatterYaml, group: peg$parsegroup, guillemetString: peg$parseguillemetString, guillemetStringChar: peg$parseguillemetStringChar, host: peg$parsehost, hostname: peg$parsehostname, identifier: peg$parseidentifier, identifierLiteral: peg$parseidentifierLiteral, identifierPart: peg$parseidentifierPart, identifierStart: peg$parseidentifierStart, implicitParenthesesCallExpression: peg$parseimplicitParenthesesCallExpression, implicitParensthesesArguments: peg$parseimplicitParensthesesArguments, inlineSpace: peg$parseinlineSpace, integerLiteral: peg$parseintegerLiteral, key: peg$parsekey, keyChar: peg$parsekeyChar, keyCharStart: peg$parsekeyCharStart, list: peg$parselist, logicalAndExpression: peg$parselogicalAndExpression, logicalOrExpression: peg$parselogicalOrExpression, minus: peg$parseminus, multiLineComment: peg$parsemultiLineComment, multiplicativeExpression: peg$parsemultiplicativeExpression, multiplicativeOperator: peg$parsemultiplicativeOperator, newExpression: peg$parsenewExpression, newLine: peg$parsenewLine, numericLiteral: peg$parsenumericLiteral, nullishCoalescingExpression: peg$parsenullishCoalescingExpression, objectLiteral: peg$parseobjectLiteral, objectEntries: peg$parseobjectEntries, objectEntry: peg$parseobjectEntry, objectGetter: peg$parseobjectGetter, objectHiddenKey: peg$parseobjectHiddenKey, objectKey: peg$parseobjectKey, objectProperty: peg$parseobjectProperty, objectShorthandProperty: peg$parseobjectShorthandProperty, objectPublicKey: peg$parseobjectPublicKey, optionalChaining: peg$parseoptionalChaining, parameter: peg$parseparameter, parameterList: peg$parseparameterList, parameterSingleton: peg$parseparameterSingleton, parenthesesArguments: peg$parseparenthesesArguments, pathArguments: peg$parsepathArguments, pathKeys: peg$parsepathKeys, pathLiteral: peg$parsepathLiteral, pathSegment: peg$parsepathSegment, pipelineExpression: peg$parsepipelineExpression, primary: peg$parseprimary, program: peg$parseprogram, propertyAccess: peg$parsepropertyAccess, regexFlags: peg$parseregexFlags, regexLiteral: peg$parseregexLiteral, regexLiteralChar: peg$parseregexLiteralChar, relationalExpression: peg$parserelationalExpression, relationalOperator: peg$parserelationalOperator, separator: peg$parseseparator, shebang: peg$parseshebang, shellMode: peg$parseshellMode, shiftExpression: peg$parseshiftExpression, shiftOperator: peg$parseshiftOperator, shorthandFunction: peg$parseshorthandFunction, singleArrow: peg$parsesingleArrow, singleLineComment: peg$parsesingleLineComment, singleQuoteString: peg$parsesingleQuoteString, singleQuoteStringChar: peg$parsesingleQuoteStringChar, slash: peg$parseslash, slashes: peg$parseslashes, slashFollows: peg$parseslashFollows, spreadElement: peg$parsespreadElement, stringLiteral: peg$parsestringLiteral, templateBody: peg$parsetemplateBody, templateBodyChar: peg$parsetemplateBodyChar, templateBodyText: peg$parsetemplateBodyText, templateDocument: peg$parsetemplateDocument, templateLiteral: peg$parsetemplateLiteral, templateLiteralChar: peg$parsetemplateLiteralChar, templateLiteralText: peg$parsetemplateLiteralText, templateSubstitution: peg$parsetemplateSubstitution, textChar: peg$parsetextChar, unaryExpression: peg$parseunaryExpression, uri: peg$parseuri, uriExpression: peg$parseuriExpression, uriKey: peg$parseuriKey, uriKeyChar: peg$parseuriKeyChar, uriPath: peg$parseuriPath, uriScheme: peg$parseuriScheme, unaryOperator: peg$parseunaryOperator, whitespace: peg$parsewhitespace, whitespaceWithNewLine: peg$parsewhitespaceWithNewLine };
209
209
  var peg$startRuleFunction = peg$parse__;
210
210
 
211
211
  var peg$c0 = "+";
@@ -285,7 +285,7 @@ function peg$parse(input, options) {
285
285
  var peg$r7 = /^[gimuy]/;
286
286
  var peg$r8 = /^[^\/\n\r]/;
287
287
  var peg$r9 = /^[^\n\r]/;
288
- var peg$r10 = /^[^\/,)\]}s]/;
288
+ var peg$r10 = /^[^\/,)\]}]/;
289
289
  var peg$r11 = /^[a-z]/;
290
290
  var peg$r12 = /^[a-z0-9+-.]/;
291
291
  var peg$r13 = /^[:]/;
@@ -399,7 +399,7 @@ function peg$parse(input, options) {
399
399
  var peg$e105 = peg$otherExpectation("template document");
400
400
  var peg$e106 = peg$otherExpectation("template literal");
401
401
  var peg$e107 = peg$otherExpectation("template substitution");
402
- var peg$e108 = peg$classExpectation(["/", ",", ")", "]", "}", "s"], true, false);
402
+ var peg$e108 = peg$classExpectation(["/", ",", ")", "]", "}"], true, false);
403
403
  var peg$e109 = peg$otherExpectation("slash-separated path");
404
404
  var peg$e110 = peg$classExpectation([["a", "z"]], false, false);
405
405
  var peg$e111 = peg$classExpectation([["a", "z"], ["0", "9"], ["+", "."]], false, false);
@@ -732,7 +732,12 @@ function peg$parse(input, options) {
732
732
  return annotate([ops.literal, chars.join("")], location());
733
733
  };
734
734
  var peg$f99 = function(front, body) {
735
+ // TODO: Deprecate @template
735
736
  const macroName = text().includes("@template") ? "@template" : "_template";
737
+ if (macroName === "@template") {
738
+ // If the front matter is a macro, apply it to the body
739
+ console.warn("Warning: the @template() macro is deprecated. Use _template() instead.");
740
+ }
736
741
  return annotate(applyMacro(front, macroName, body), location());
737
742
  };
738
743
  var peg$f100 = function(front, body) {
@@ -775,12 +780,16 @@ function peg$parse(input, options) {
775
780
  // A single slash is a path key
776
781
  return annotate([ops.literal, ""], location());
777
782
  };
778
- var peg$f110 = function(keys) {
783
+ var peg$f110 = function(char) { return /\s/.test(char); };
784
+ var peg$f111 = function(char) { return char; };
785
+ var peg$f112 = function(keys) {
779
786
  return annotate(keys, location());
780
787
  };
781
- var peg$f111 = function() {
788
+ var peg$f113 = function() {
782
789
  return annotate([markers.global, text()], location());
783
790
  };
791
+ var peg$f114 = function(char) { return /\s/.test(char); };
792
+ var peg$f115 = function(char) { return char; };
784
793
  var peg$currPos = options.peg$currPos | 0;
785
794
  var peg$savedPos = peg$currPos;
786
795
  var peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -1896,21 +1905,26 @@ function peg$parse(input, options) {
1896
1905
  var s0, s1, s2, s3, s4;
1897
1906
 
1898
1907
  s0 = peg$currPos;
1899
- s1 = peg$parse__();
1900
- if (input.charCodeAt(peg$currPos) === 91) {
1901
- s2 = peg$c6;
1902
- peg$currPos++;
1903
- } else {
1904
- s2 = peg$FAILED;
1905
- if (peg$silentFails === 0) { peg$fail(peg$e9); }
1906
- }
1907
- if (s2 !== peg$FAILED) {
1908
- s3 = peg$parseexpectExpression();
1909
- if (s3 !== peg$FAILED) {
1910
- s4 = peg$parseexpectClosingBracket();
1911
- if (s4 !== peg$FAILED) {
1912
- peg$savedPos = s0;
1913
- s0 = peg$f16(s3);
1908
+ s1 = peg$parsecomputedPropertySpace();
1909
+ if (s1 !== peg$FAILED) {
1910
+ if (input.charCodeAt(peg$currPos) === 91) {
1911
+ s2 = peg$c6;
1912
+ peg$currPos++;
1913
+ } else {
1914
+ s2 = peg$FAILED;
1915
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
1916
+ }
1917
+ if (s2 !== peg$FAILED) {
1918
+ s3 = peg$parseexpectExpression();
1919
+ if (s3 !== peg$FAILED) {
1920
+ s4 = peg$parseexpectClosingBracket();
1921
+ if (s4 !== peg$FAILED) {
1922
+ peg$savedPos = s0;
1923
+ s0 = peg$f16(s3);
1924
+ } else {
1925
+ peg$currPos = s0;
1926
+ s0 = peg$FAILED;
1927
+ }
1914
1928
  } else {
1915
1929
  peg$currPos = s0;
1916
1930
  s0 = peg$FAILED;
@@ -1927,6 +1941,35 @@ function peg$parse(input, options) {
1927
1941
  return s0;
1928
1942
  }
1929
1943
 
1944
+ function peg$parsecomputedPropertySpace() {
1945
+ var s0, s1, s2;
1946
+
1947
+ s0 = peg$parseshellMode();
1948
+ if (s0 === peg$FAILED) {
1949
+ s0 = peg$currPos;
1950
+ s1 = peg$currPos;
1951
+ peg$silentFails++;
1952
+ s2 = peg$parseshellMode();
1953
+ peg$silentFails--;
1954
+ if (s2 === peg$FAILED) {
1955
+ s1 = undefined;
1956
+ } else {
1957
+ peg$currPos = s1;
1958
+ s1 = peg$FAILED;
1959
+ }
1960
+ if (s1 !== peg$FAILED) {
1961
+ s2 = peg$parse__();
1962
+ s1 = [s1, s2];
1963
+ s0 = s1;
1964
+ } else {
1965
+ peg$currPos = s0;
1966
+ s0 = peg$FAILED;
1967
+ }
1968
+ }
1969
+
1970
+ return s0;
1971
+ }
1972
+
1930
1973
  function peg$parseconditionalExpression() {
1931
1974
  var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
1932
1975
 
@@ -6009,15 +6052,44 @@ function peg$parse(input, options) {
6009
6052
  }
6010
6053
 
6011
6054
  function peg$parseuriKeyChar() {
6012
- var s0;
6055
+ var s0, s1, s2, s3;
6013
6056
 
6014
- s0 = input.charAt(peg$currPos);
6015
- if (peg$r10.test(s0)) {
6057
+ s0 = peg$currPos;
6058
+ s1 = input.charAt(peg$currPos);
6059
+ if (peg$r10.test(s1)) {
6016
6060
  peg$currPos++;
6017
6061
  } else {
6018
- s0 = peg$FAILED;
6062
+ s1 = peg$FAILED;
6019
6063
  if (peg$silentFails === 0) { peg$fail(peg$e108); }
6020
6064
  }
6065
+ if (s1 !== peg$FAILED) {
6066
+ s2 = peg$currPos;
6067
+ peg$silentFails++;
6068
+ peg$savedPos = peg$currPos;
6069
+ s3 = peg$f110(s1);
6070
+ if (s3) {
6071
+ s3 = undefined;
6072
+ } else {
6073
+ s3 = peg$FAILED;
6074
+ }
6075
+ peg$silentFails--;
6076
+ if (s3 === peg$FAILED) {
6077
+ s2 = undefined;
6078
+ } else {
6079
+ peg$currPos = s2;
6080
+ s2 = peg$FAILED;
6081
+ }
6082
+ if (s2 !== peg$FAILED) {
6083
+ peg$savedPos = s0;
6084
+ s0 = peg$f111(s1);
6085
+ } else {
6086
+ peg$currPos = s0;
6087
+ s0 = peg$FAILED;
6088
+ }
6089
+ } else {
6090
+ peg$currPos = s0;
6091
+ s0 = peg$FAILED;
6092
+ }
6021
6093
  if (s0 === peg$FAILED) {
6022
6094
  s0 = peg$parseescapedChar();
6023
6095
  }
@@ -6045,7 +6117,7 @@ function peg$parse(input, options) {
6045
6117
  }
6046
6118
  if (s1 !== peg$FAILED) {
6047
6119
  peg$savedPos = s0;
6048
- s1 = peg$f110(s1);
6120
+ s1 = peg$f112(s1);
6049
6121
  }
6050
6122
  s0 = s1;
6051
6123
  peg$silentFails--;
@@ -6096,7 +6168,7 @@ function peg$parse(input, options) {
6096
6168
  }
6097
6169
  if (s3 !== peg$FAILED) {
6098
6170
  peg$savedPos = s0;
6099
- s0 = peg$f111();
6171
+ s0 = peg$f113();
6100
6172
  } else {
6101
6173
  peg$currPos = s0;
6102
6174
  s0 = peg$FAILED;
@@ -6164,14 +6236,37 @@ function peg$parse(input, options) {
6164
6236
  }
6165
6237
 
6166
6238
  function peg$parsewhitespace() {
6167
- var s0;
6239
+ var s0, s1, s2;
6168
6240
 
6169
- s0 = peg$parseinlineSpace();
6170
- if (s0 === peg$FAILED) {
6171
- s0 = peg$parsenewLine();
6172
- if (s0 === peg$FAILED) {
6173
- s0 = peg$parsecomment();
6241
+ s0 = peg$currPos;
6242
+ if (input.length > peg$currPos) {
6243
+ s1 = input.charAt(peg$currPos);
6244
+ peg$currPos++;
6245
+ } else {
6246
+ s1 = peg$FAILED;
6247
+ if (peg$silentFails === 0) { peg$fail(peg$e41); }
6248
+ }
6249
+ if (s1 !== peg$FAILED) {
6250
+ peg$savedPos = peg$currPos;
6251
+ s2 = peg$f114(s1);
6252
+ if (s2) {
6253
+ s2 = undefined;
6254
+ } else {
6255
+ s2 = peg$FAILED;
6174
6256
  }
6257
+ if (s2 !== peg$FAILED) {
6258
+ peg$savedPos = s0;
6259
+ s0 = peg$f115(s1);
6260
+ } else {
6261
+ peg$currPos = s0;
6262
+ s0 = peg$FAILED;
6263
+ }
6264
+ } else {
6265
+ peg$currPos = s0;
6266
+ s0 = peg$FAILED;
6267
+ }
6268
+ if (s0 === peg$FAILED) {
6269
+ s0 = peg$parsecomment();
6175
6270
  }
6176
6271
 
6177
6272
  return s0;
@@ -6255,6 +6350,7 @@ const peg$allowedStartRules = [
6255
6350
  "commaExpression",
6256
6351
  "comment",
6257
6352
  "computedPropertyAccess",
6353
+ "computedPropertySpace",
6258
6354
  "conditionalExpression",
6259
6355
  "digits",
6260
6356
  "doubleArrow",
@@ -28,7 +28,7 @@ export const markers = {
28
28
  * If a parse result is an object that will be evaluated at runtime, attach the
29
29
  * location of the source code that produced it for debugging and error messages.
30
30
  *
31
- * @param {Code[]} code
31
+ * @param {any[]} code
32
32
  * @param {CodeLocation} location
33
33
  */
34
34
  export function annotate(code, location) {
@@ -375,11 +375,12 @@ export function makePath(keys) {
375
375
  const reference = annotate([markers.reference, headKey], head.location);
376
376
 
377
377
  let code = [markers.traverse, reference, ...tail];
378
- code.location = spanLocations(code);
378
+ const location = spanLocations(code);
379
+ code = annotate(code, location);
379
380
 
380
381
  // Last key has trailing slash implies unpack operation
381
382
  if (trailingSlash.has(args.at(-1)[1])) {
382
- code = annotate([ops.unpack, code], code.location);
383
+ code = annotate([ops.unpack, code], location);
383
384
  }
384
385
 
385
386
  return code;
@@ -1,6 +1,7 @@
1
1
  // Text we look for in an error stack to guess whether a given line represents a
2
2
 
3
3
  import {
4
+ box,
4
5
  scope as scopeFn,
5
6
  trailingSlash,
6
7
  TraverseError,
@@ -8,6 +9,7 @@ import {
8
9
  import path from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
11
  import codeFragment from "./codeFragment.js";
12
+ import * as symbols from "./symbols.js";
11
13
  import { typos } from "./typos.js";
12
14
 
13
15
  // function in the Origami source code.
@@ -18,6 +20,19 @@ const origamiSourceSignals = [
18
20
  "at Scope.evaluate",
19
21
  ];
20
22
 
23
+ const displayedWarnings = new Set();
24
+
25
+ export function attachWarning(value, message) {
26
+ if (typeof value === "object" && value?.[symbols.warningSymbol]) {
27
+ // Already has a warning, don't overwrite it
28
+ return value;
29
+ }
30
+
31
+ const boxed = box(value);
32
+ boxed[symbols.warningSymbol] = message;
33
+ return boxed;
34
+ }
35
+
21
36
  export async function builtinReferenceError(tree, builtins, key) {
22
37
  // See if the key is in scope (but not as a builtin)
23
38
  const scope = scopeFn(tree);
@@ -40,6 +55,16 @@ export async function builtinReferenceError(tree, builtins, key) {
40
55
  return new ReferenceError(message);
41
56
  }
42
57
 
58
+ // Display a warning message in the console, but only once for each unique
59
+ // message and location.
60
+ export function displayWarning(message, location) {
61
+ const warning = "Warning: " + message + lineInfo(location);
62
+ if (!displayedWarnings.has(warning)) {
63
+ displayedWarnings.add(warning);
64
+ console.warn(warning);
65
+ }
66
+ }
67
+
43
68
  /**
44
69
  * Format an error for display in the console.
45
70
  *
@@ -80,33 +105,10 @@ export function formatError(error) {
80
105
 
81
106
  // Add location
82
107
  if (location) {
83
- let { source, start } = location;
84
- // Adjust line number with offset if present (for example, if the code is in
85
- // an Origami template document with front matter that was stripped)
86
- let line = start.line + (source.offset ?? 0);
87
108
  if (!fragmentInMessage) {
88
109
  message += `\nevaluating: ${fragment}`;
89
110
  }
90
-
91
- if (typeof source === "object" && source.url) {
92
- const { url } = source;
93
- let fileRef;
94
- // If URL is a file: URL, change to a relative path
95
- if (url.protocol === "file:") {
96
- fileRef = fileURLToPath(url);
97
- const relativePath = path.relative(process.cwd(), fileRef);
98
- if (!relativePath.startsWith("..")) {
99
- fileRef = relativePath;
100
- }
101
- } else {
102
- // Not a file: URL, use as is
103
- fileRef = url.href;
104
- }
105
- message += `\n at ${fileRef}:${line}:${start.column}`;
106
- } else if (source.text.includes("\n")) {
107
- // Don't know the URL, but has multiple lines so add line number
108
- message += `\n at line ${line}, column ${start.column}`;
109
- }
111
+ message += lineInfo(location);
110
112
  }
111
113
 
112
114
  return message;
@@ -128,6 +130,36 @@ export function maybeOrigamiSourceCode(text) {
128
130
  return origamiSourceSignals.some((signal) => text.includes(signal));
129
131
  }
130
132
 
133
+ // Return user-friendly line information for the error location
134
+ function lineInfo(location) {
135
+ let { source, start } = location;
136
+ // Adjust line number with offset if present (for example, if the code is in
137
+ // an Origami template document with front matter that was stripped)
138
+ let line = start.line + (source.offset ?? 0);
139
+
140
+ if (typeof source === "object" && source.url) {
141
+ const { url } = source;
142
+ let fileRef;
143
+ // If URL is a file: URL, change to a relative path
144
+ if (url.protocol === "file:") {
145
+ fileRef = fileURLToPath(url);
146
+ const relativePath = path.relative(process.cwd(), fileRef);
147
+ if (!relativePath.startsWith("..")) {
148
+ fileRef = relativePath;
149
+ }
150
+ } else {
151
+ // Not a file: URL, use as is
152
+ fileRef = url.href;
153
+ }
154
+ return `\n at ${fileRef}:${line}:${start.column}`;
155
+ } else if (source.text.includes("\n")) {
156
+ // Don't know the URL, but has multiple lines so add line number
157
+ return `\n at line ${line}, column ${start.column}`;
158
+ } else {
159
+ return "";
160
+ }
161
+ }
162
+
131
163
  export async function scopeReferenceError(scope, key) {
132
164
  const messages = [
133
165
  `"${key}" is not in scope or is undefined.`,
@@ -1,4 +1,5 @@
1
1
  import { Tree, isUnpackable } from "@weborigami/async-tree";
2
+ import { displayWarning, symbols } from "@weborigami/language";
2
3
  import codeFragment from "./codeFragment.js";
3
4
 
4
5
  /**
@@ -66,33 +67,10 @@ export default async function evaluate(code) {
66
67
  throw error;
67
68
  }
68
69
 
69
- // To aid debugging, add the code to the result.
70
- // if (Object.isExtensible(result)) {
71
- // try {
72
- // if (code.location && !result[sourceSymbol]) {
73
- // Object.defineProperty(result, sourceSymbol, {
74
- // value: codeFragment(code.location),
75
- // enumerable: false,
76
- // });
77
- // }
78
- // if (!result[codeSymbol]) {
79
- // Object.defineProperty(result, codeSymbol, {
80
- // value: code,
81
- // enumerable: false,
82
- // });
83
- // }
84
- // if (!result[scopeSymbol]) {
85
- // Object.defineProperty(result, scopeSymbol, {
86
- // get() {
87
- // return scope(this).trees;
88
- // },
89
- // enumerable: false,
90
- // });
91
- // }
92
- // } catch (/** @type {any} */ error) {
93
- // // Ignore errors.
94
- // }
95
- // }
70
+ if (result?.[symbols.warningSymbol]) {
71
+ displayWarning(result[symbols.warningSymbol], code.location);
72
+ delete result[symbols.warningSymbol];
73
+ }
96
74
 
97
75
  return result;
98
76
  }
@@ -6,5 +6,5 @@ export default function getHandlers(tree) {
6
6
  return null;
7
7
  }
8
8
  const root = Tree.root(tree);
9
- return root.handlers;
9
+ return /** @type {any} */ (root).handlers;
10
10
  }
@@ -204,7 +204,7 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
204
204
  export async function homeDirectory(...keys) {
205
205
  const tree = new OrigamiFiles(os.homedir());
206
206
  // Use the same handlers as the current tree
207
- tree.handlers = getHandlers(this);
207
+ /** @type {any} */ (tree).handlers = getHandlers(this);
208
208
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
209
209
  }
210
210
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
@@ -424,7 +424,7 @@ addOpLabel(remainder, "«ops.remainder»");
424
424
  export async function rootDirectory(...keys) {
425
425
  const tree = new OrigamiFiles("/");
426
426
  // Use the same handlers as the current tree
427
- tree.handlers = getHandlers(this);
427
+ /** @type {any} */ (tree).handlers = getHandlers(this);
428
428
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
429
429
  }
430
430
  addOpLabel(rootDirectory, "«ops.rootDirectory»");
@@ -2,3 +2,4 @@ export const codeSymbol = Symbol("code");
2
2
  export const configSymbol = Symbol("config");
3
3
  export const scopeSymbol = Symbol("scope");
4
4
  export const sourceSymbol = Symbol("source");
5
+ export const warningSymbol = Symbol("warning");
@@ -7,6 +7,15 @@ export function assertCodeEqual(actual, expected) {
7
7
  assert.deepStrictEqual(actualStripped, expectedStripped);
8
8
  }
9
9
 
10
+ export function assertCodeLocations(code) {
11
+ assert(code.location, "no location");
12
+ for (const item of code) {
13
+ if (Array.isArray(item)) {
14
+ assertCodeLocations(item);
15
+ }
16
+ }
17
+ }
18
+
10
19
  /**
11
20
  * Adds a fake source to code.
12
21
  *
@@ -4,7 +4,11 @@ import * as compile from "../../src/compiler/compile.js";
4
4
  import optimize from "../../src/compiler/optimize.js";
5
5
  import { markers } from "../../src/compiler/parserHelpers.js";
6
6
  import { ops } from "../../src/runtime/internal.js";
7
- import { assertCodeEqual, createCode } from "./codeHelpers.js";
7
+ import {
8
+ assertCodeEqual,
9
+ assertCodeLocations,
10
+ createCode,
11
+ } from "./codeHelpers.js";
8
12
 
9
13
  describe("optimize", () => {
10
14
  test("change local references to context references", () => {
@@ -155,8 +159,8 @@ describe("optimize", () => {
155
159
  test("global reference", () => {
156
160
  // Compilation of `Math` where Math is a global variable
157
161
  const code = createCode([markers.traverse, [markers.reference, "Math"]]);
158
- const globals = { Math: null }; // value doesn't matter
159
- const expected = [globals, "Math"];
162
+ const globals = { Math: {} }; // value doesn't matter
163
+ const expected = globals.Math;
160
164
  assertCodeEqual(optimize(code, { globals }), expected);
161
165
  });
162
166
 
@@ -167,7 +171,7 @@ describe("optimize", () => {
167
171
  [markers.reference, "Math.PI"],
168
172
  ]);
169
173
  const globals = { Math: { PI: null } }; // value doesn't matter
170
- const expected = [[globals, "Math"], "PI"];
174
+ const expected = [globals.Math, "PI"];
171
175
  assertCodeEqual(optimize(code, { globals }), expected);
172
176
  });
173
177
 
@@ -277,5 +281,6 @@ function assertCompile(expression, expected, mode = "shell") {
277
281
  const globals = new ObjectTree({});
278
282
  const fn = compile.expression(expression, { globals, mode, parent });
279
283
  const actual = fn.code;
284
+ assertCodeLocations(actual);
280
285
  assertCodeEqual(actual, expected);
281
286
  }
@@ -3,7 +3,7 @@ import { describe, test } from "node:test";
3
3
  import { parse } from "../../src/compiler/parse.js";
4
4
  import { markers } from "../../src/compiler/parserHelpers.js";
5
5
  import * as ops from "../../src/runtime/ops.js";
6
- import { assertCodeEqual } from "./codeHelpers.js";
6
+ import { assertCodeEqual, assertCodeLocations } from "./codeHelpers.js";
7
7
 
8
8
  describe("Origami parser", () => {
9
9
  test("additiveExpression", () => {
@@ -1596,16 +1596,8 @@ function assertParse(
1596
1596
  assertCodeEqual(code, expected);
1597
1597
  }
1598
1598
 
1599
- function assertCodeLocations(code) {
1600
- assert(code.location, "no location");
1601
- for (const item of code) {
1602
- if (Array.isArray(item)) {
1603
- assertCodeLocations(item);
1604
- }
1605
- }
1606
- }
1607
-
1608
1599
  function assertThrows(startRule, source, message, position, mode = "shell") {
1600
+ // @ts-ignore We declare this so we can inspect it in debugger
1609
1601
  let code;
1610
1602
  try {
1611
1603
  code = parse(source, {
@@ -54,7 +54,7 @@ describe("expressionObject", () => {
54
54
  test("returned object values can be unpacked", async () => {
55
55
  const entries = [["data.json", `{ "a": 1 }`]];
56
56
  const parent = new ObjectTree({});
57
- parent.handlers = {
57
+ /** @type {any} */ (parent).handlers = {
58
58
  "json.handler": {
59
59
  unpack: JSON.parse,
60
60
  },
@@ -358,7 +358,7 @@ describe("ops", () => {
358
358
  const a = await tree.get("a");
359
359
  const b = await a.get("b");
360
360
  const scope = await ops.scope.call(b);
361
- assert.equal(await scope.get("c"), 1);
361
+ assert.equal(await scope?.get("c"), 1);
362
362
  });
363
363
 
364
364
  test("accepts an optional context", async () => {
@@ -372,7 +372,7 @@ describe("ops", () => {
372
372
  const a = await tree.get("a");
373
373
  const b = await a.get("b");
374
374
  const scope = await ops.scope.call(b, tree);
375
- assert.equal(await scope.get("c"), 1);
375
+ assert.equal(await scope?.get("c"), 1);
376
376
  });
377
377
  });
378
378