@weborigami/language 0.3.4-jse.7 → 0.3.4-jse.8

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.8",
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.8",
15
+ "@weborigami/types": "0.3.4-jse.8",
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);
@@ -140,7 +140,7 @@ function compoundReference(key, globals, locals, location) {
140
140
  const type = referenceType(head, globals, locals);
141
141
  let result;
142
142
  if (type === REFERENCE_GLOBAL) {
143
- result = globalReference(head, globals, location);
143
+ result = globalReference(head, globals);
144
144
  } else if (type === REFERENCE_LOCAL) {
145
145
  result = localReference(head, locals, location);
146
146
  } else {
@@ -177,9 +177,9 @@ function getLocalReferenceDepth(locals, key) {
177
177
  return depth;
178
178
  }
179
179
 
180
- function globalReference(key, globals, location) {
180
+ function globalReference(key, globals) {
181
181
  const normalized = trailingSlash.remove(key);
182
- return annotate([globals, normalized], location);
182
+ return globals[normalized];
183
183
  }
184
184
 
185
185
  function inlineLiteral(code) {
@@ -249,24 +249,29 @@ function reference(code, globals, locals) {
249
249
 
250
250
  // See if the whole key is a global or local variable
251
251
  let type = referenceType(key, globals, locals);
252
- let result;
253
252
  if (type === REFERENCE_GLOBAL) {
254
- result = globalReference(key, globals, location);
253
+ return {
254
+ type,
255
+ result: globalReference(key, globals),
256
+ };
255
257
  } 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;
258
+ return {
259
+ type,
260
+ result: localReference(key, locals, location),
261
+ };
262
262
  }
263
263
 
264
- if (!result) {
265
- // If none of the above worked, it must be an external reference
266
- result = externalReference(key, locals, location);
264
+ // Try key as a compound reference x.y.z
265
+ const compound = compoundReference(key, globals, locals, location);
266
+ if (compound.type !== REFERENCE_EXTERNAL) {
267
+ return compound;
267
268
  }
268
269
 
269
- return { type, result };
270
+ // Not a compound reference, must be external
271
+ return {
272
+ type: REFERENCE_EXTERNAL,
273
+ result: externalReference(key, locals, location),
274
+ };
270
275
  }
271
276
 
272
277
  function referenceType(key, globals, locals) {
@@ -287,7 +292,13 @@ function resolvePath(code, globals, locals, cache) {
287
292
 
288
293
  let { type, result } = reference(head, globals, locals);
289
294
 
290
- result.push(...tail);
295
+ if (tail.length > 0) {
296
+ if (result instanceof Array) {
297
+ result.push(...tail);
298
+ } else {
299
+ result = annotate([result, ...tail], code.location);
300
+ }
301
+ }
291
302
 
292
303
  if (type === REFERENCE_EXTERNAL && cache !== null) {
293
304
  // Cache external path
@@ -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
+ // Note: Peggy doesn't support `/s` so we list space and tab explicitly.
870
+ = [^/,\)\]\} \t]
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 = /^[^\/,)\]} \t]/;
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(["/", ",", ")", "]", "}", " ", "\t"], 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) {
@@ -781,6 +786,8 @@ function peg$parse(input, options) {
781
786
  var peg$f111 = function() {
782
787
  return annotate([markers.global, text()], location());
783
788
  };
789
+ var peg$f112 = function(char) { return /\s/.test(char); };
790
+ var peg$f113 = function(char) { return char; };
784
791
  var peg$currPos = options.peg$currPos | 0;
785
792
  var peg$savedPos = peg$currPos;
786
793
  var peg$posDetailsCache = [{ line: 1, column: 1 }];
@@ -1896,21 +1903,26 @@ function peg$parse(input, options) {
1896
1903
  var s0, s1, s2, s3, s4;
1897
1904
 
1898
1905
  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);
1906
+ s1 = peg$parsecomputedPropertySpace();
1907
+ if (s1 !== peg$FAILED) {
1908
+ if (input.charCodeAt(peg$currPos) === 91) {
1909
+ s2 = peg$c6;
1910
+ peg$currPos++;
1911
+ } else {
1912
+ s2 = peg$FAILED;
1913
+ if (peg$silentFails === 0) { peg$fail(peg$e9); }
1914
+ }
1915
+ if (s2 !== peg$FAILED) {
1916
+ s3 = peg$parseexpectExpression();
1917
+ if (s3 !== peg$FAILED) {
1918
+ s4 = peg$parseexpectClosingBracket();
1919
+ if (s4 !== peg$FAILED) {
1920
+ peg$savedPos = s0;
1921
+ s0 = peg$f16(s3);
1922
+ } else {
1923
+ peg$currPos = s0;
1924
+ s0 = peg$FAILED;
1925
+ }
1914
1926
  } else {
1915
1927
  peg$currPos = s0;
1916
1928
  s0 = peg$FAILED;
@@ -1927,6 +1939,35 @@ function peg$parse(input, options) {
1927
1939
  return s0;
1928
1940
  }
1929
1941
 
1942
+ function peg$parsecomputedPropertySpace() {
1943
+ var s0, s1, s2;
1944
+
1945
+ s0 = peg$parseshellMode();
1946
+ if (s0 === peg$FAILED) {
1947
+ s0 = peg$currPos;
1948
+ s1 = peg$currPos;
1949
+ peg$silentFails++;
1950
+ s2 = peg$parseshellMode();
1951
+ peg$silentFails--;
1952
+ if (s2 === peg$FAILED) {
1953
+ s1 = undefined;
1954
+ } else {
1955
+ peg$currPos = s1;
1956
+ s1 = peg$FAILED;
1957
+ }
1958
+ if (s1 !== peg$FAILED) {
1959
+ s2 = peg$parse__();
1960
+ s1 = [s1, s2];
1961
+ s0 = s1;
1962
+ } else {
1963
+ peg$currPos = s0;
1964
+ s0 = peg$FAILED;
1965
+ }
1966
+ }
1967
+
1968
+ return s0;
1969
+ }
1970
+
1930
1971
  function peg$parseconditionalExpression() {
1931
1972
  var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
1932
1973
 
@@ -6164,14 +6205,37 @@ function peg$parse(input, options) {
6164
6205
  }
6165
6206
 
6166
6207
  function peg$parsewhitespace() {
6167
- var s0;
6208
+ var s0, s1, s2;
6168
6209
 
6169
- s0 = peg$parseinlineSpace();
6170
- if (s0 === peg$FAILED) {
6171
- s0 = peg$parsenewLine();
6172
- if (s0 === peg$FAILED) {
6173
- s0 = peg$parsecomment();
6210
+ s0 = peg$currPos;
6211
+ if (input.length > peg$currPos) {
6212
+ s1 = input.charAt(peg$currPos);
6213
+ peg$currPos++;
6214
+ } else {
6215
+ s1 = peg$FAILED;
6216
+ if (peg$silentFails === 0) { peg$fail(peg$e41); }
6217
+ }
6218
+ if (s1 !== peg$FAILED) {
6219
+ peg$savedPos = peg$currPos;
6220
+ s2 = peg$f112(s1);
6221
+ if (s2) {
6222
+ s2 = undefined;
6223
+ } else {
6224
+ s2 = peg$FAILED;
6174
6225
  }
6226
+ if (s2 !== peg$FAILED) {
6227
+ peg$savedPos = s0;
6228
+ s0 = peg$f113(s1);
6229
+ } else {
6230
+ peg$currPos = s0;
6231
+ s0 = peg$FAILED;
6232
+ }
6233
+ } else {
6234
+ peg$currPos = s0;
6235
+ s0 = peg$FAILED;
6236
+ }
6237
+ if (s0 === peg$FAILED) {
6238
+ s0 = peg$parsecomment();
6175
6239
  }
6176
6240
 
6177
6241
  return s0;
@@ -6255,6 +6319,7 @@ const peg$allowedStartRules = [
6255
6319
  "commaExpression",
6256
6320
  "comment",
6257
6321
  "computedPropertyAccess",
6322
+ "computedPropertySpace",
6258
6323
  "conditionalExpression",
6259
6324
  "digits",
6260
6325
  "doubleArrow",
@@ -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
  }
@@ -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");
@@ -155,8 +155,8 @@ describe("optimize", () => {
155
155
  test("global reference", () => {
156
156
  // Compilation of `Math` where Math is a global variable
157
157
  const code = createCode([markers.traverse, [markers.reference, "Math"]]);
158
- const globals = { Math: null }; // value doesn't matter
159
- const expected = [globals, "Math"];
158
+ const globals = { Math: {} }; // value doesn't matter
159
+ const expected = globals.Math;
160
160
  assertCodeEqual(optimize(code, { globals }), expected);
161
161
  });
162
162
 
@@ -167,7 +167,7 @@ describe("optimize", () => {
167
167
  [markers.reference, "Math.PI"],
168
168
  ]);
169
169
  const globals = { Math: { PI: null } }; // value doesn't matter
170
- const expected = [[globals, "Math"], "PI"];
170
+ const expected = [globals.Math, "PI"];
171
171
  assertCodeEqual(optimize(code, { globals }), expected);
172
172
  });
173
173