@weborigami/language 0.0.46 → 0.0.47

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/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Packed } from "@weborigami/async-tree";
1
2
 
2
3
  export * from "./main.js";
3
4
 
@@ -17,13 +18,13 @@ export type Code = ArrayWithSource;
17
18
  export type Constructor<T> = new (...args: any[]) => T;
18
19
 
19
20
  /**
20
- * A function that can convert a value from some persistent form into some kind
21
- * of live value.
21
+ * A structure associating a media type and an unpack function with a given file
22
+ * extension.
22
23
  */
23
- export type FileUnpackFunction = (
24
- input: any,
25
- options?: any
26
- ) => any;
24
+ export type ExtensionHandler = {
25
+ mediaType?: string;
26
+ unpack?: UnpackFunction;
27
+ }
27
28
 
28
29
  /**
29
30
  * A mixin is a function that takes an existing class and returns a new class.
@@ -39,9 +40,14 @@ export type Mixin<MixinMembers> = <T>(
39
40
 
40
41
  /**
41
42
  * Source code representation used by the parser.
42
- */
43
+ */
43
44
  export type Source = {
44
45
  name: string;
45
46
  text: string;
46
- url: URL;
47
- }
47
+ url: URL;
48
+ }
49
+
50
+ /**
51
+ * A function that converts a value from a persistent form into a live value.
52
+ */
53
+ export type UnpackFunction = (input: Packed, options?: any) => any;
package/main.js CHANGED
@@ -3,7 +3,7 @@ export * from "./src/runtime/internal.js";
3
3
  export * as compile from "./src/compiler/compile.js";
4
4
  export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
5
5
  export { default as ExpressionTree } from "./src/runtime/ExpressionTree.js";
6
- export { default as FileLoadersTransform } from "./src/runtime/FileLoadersTransform.js";
6
+ export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
7
7
  export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
8
8
  export { default as InheritScopeMixin } from "./src/runtime/InheritScopeMixin.js";
9
9
  export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
@@ -19,4 +19,3 @@ export * as expressionFunction from "./src/runtime/expressionFunction.js";
19
19
  export { default as extname } from "./src/runtime/extname.js";
20
20
  export { default as formatError } from "./src/runtime/formatError.js";
21
21
  export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
22
- export * as symbols from "./src/runtime/symbols.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -10,9 +10,9 @@
10
10
  "typescript": "5.3.3"
11
11
  },
12
12
  "dependencies": {
13
- "@weborigami/async-tree": "0.0.46",
14
- "@weborigami/types": "0.0.46",
15
- "peggy": "3.0.2",
13
+ "@weborigami/async-tree": "0.0.47",
14
+ "@weborigami/types": "0.0.47",
15
+ "peggy": "4.0.2",
16
16
  "watcher": "2.3.0"
17
17
  },
18
18
  "scripts": {
@@ -1,8 +1,9 @@
1
- // Generated by Peggy 3.0.2.
1
+ // @generated by Peggy 4.0.2.
2
2
  //
3
3
  // https://peggyjs.org/
4
4
 
5
5
 
6
+
6
7
  //
7
8
  // Origami language parser
8
9
  //
@@ -406,16 +407,16 @@ function peg$parse(input, options) {
406
407
  var peg$f39 = function(assignments) {
407
408
  return annotate([ops.tree, ...(assignments ?? [])], location());
408
409
  };
409
- var peg$currPos = 0;
410
- var peg$savedPos = 0;
410
+ var peg$currPos = options.peg$currPos | 0;
411
+ var peg$savedPos = peg$currPos;
411
412
  var peg$posDetailsCache = [{ line: 1, column: 1 }];
412
- var peg$maxFailPos = 0;
413
- var peg$maxFailExpected = [];
414
- var peg$silentFails = 0;
413
+ var peg$maxFailPos = peg$currPos;
414
+ var peg$maxFailExpected = options.peg$maxFailExpected || [];
415
+ var peg$silentFails = options.peg$silentFails | 0;
415
416
 
416
417
  var peg$result;
417
418
 
418
- if ("startRule" in options) {
419
+ if (options.startRule) {
419
420
  if (!(options.startRule in peg$startRuleFunctions)) {
420
421
  throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
421
422
  }
@@ -490,9 +491,11 @@ function peg$parse(input, options) {
490
491
  if (details) {
491
492
  return details;
492
493
  } else {
493
- p = pos - 1;
494
- while (!peg$posDetailsCache[p]) {
495
- p--;
494
+ if (pos >= peg$posDetailsCache.length) {
495
+ p = peg$posDetailsCache.length - 1;
496
+ } else {
497
+ p = pos;
498
+ while (!peg$posDetailsCache[--p]) {}
496
499
  }
497
500
 
498
501
  details = peg$posDetailsCache[p];
@@ -909,8 +912,8 @@ function peg$parse(input, options) {
909
912
 
910
913
  s0 = peg$currPos;
911
914
  s1 = [];
912
- if (peg$r0.test(input.charAt(peg$currPos))) {
913
- s2 = input.charAt(peg$currPos);
915
+ s2 = input.charAt(peg$currPos);
916
+ if (peg$r0.test(s2)) {
914
917
  peg$currPos++;
915
918
  } else {
916
919
  s2 = peg$FAILED;
@@ -919,8 +922,8 @@ function peg$parse(input, options) {
919
922
  if (s2 !== peg$FAILED) {
920
923
  while (s2 !== peg$FAILED) {
921
924
  s1.push(s2);
922
- if (peg$r0.test(input.charAt(peg$currPos))) {
923
- s2 = input.charAt(peg$currPos);
925
+ s2 = input.charAt(peg$currPos);
926
+ if (peg$r0.test(s2)) {
924
927
  peg$currPos++;
925
928
  } else {
926
929
  s2 = peg$FAILED;
@@ -1311,8 +1314,8 @@ function peg$parse(input, options) {
1311
1314
  function peg$parseidentifierChar() {
1312
1315
  var s0, s1, s2, s3;
1313
1316
 
1314
- if (peg$r1.test(input.charAt(peg$currPos))) {
1315
- s0 = input.charAt(peg$currPos);
1317
+ s0 = input.charAt(peg$currPos);
1318
+ if (peg$r1.test(s0)) {
1316
1319
  peg$currPos++;
1317
1320
  } else {
1318
1321
  s0 = peg$FAILED;
@@ -1444,8 +1447,8 @@ function peg$parse(input, options) {
1444
1447
  function peg$parseinlineSpace() {
1445
1448
  var s0;
1446
1449
 
1447
- if (peg$r2.test(input.charAt(peg$currPos))) {
1448
- s0 = input.charAt(peg$currPos);
1450
+ s0 = input.charAt(peg$currPos);
1451
+ if (peg$r2.test(s0)) {
1449
1452
  peg$currPos++;
1450
1453
  } else {
1451
1454
  s0 = peg$FAILED;
@@ -2361,8 +2364,8 @@ function peg$parse(input, options) {
2361
2364
  function peg$parsesign() {
2362
2365
  var s0;
2363
2366
 
2364
- if (peg$r3.test(input.charAt(peg$currPos))) {
2365
- s0 = input.charAt(peg$currPos);
2367
+ s0 = input.charAt(peg$currPos);
2368
+ if (peg$r3.test(s0)) {
2366
2369
  peg$currPos++;
2367
2370
  } else {
2368
2371
  s0 = peg$FAILED;
@@ -2408,8 +2411,8 @@ function peg$parse(input, options) {
2408
2411
  }
2409
2412
  if (s1 !== peg$FAILED) {
2410
2413
  s2 = [];
2411
- if (peg$r4.test(input.charAt(peg$currPos))) {
2412
- s3 = input.charAt(peg$currPos);
2414
+ s3 = input.charAt(peg$currPos);
2415
+ if (peg$r4.test(s3)) {
2413
2416
  peg$currPos++;
2414
2417
  } else {
2415
2418
  s3 = peg$FAILED;
@@ -2417,8 +2420,8 @@ function peg$parse(input, options) {
2417
2420
  }
2418
2421
  while (s3 !== peg$FAILED) {
2419
2422
  s2.push(s3);
2420
- if (peg$r4.test(input.charAt(peg$currPos))) {
2421
- s3 = input.charAt(peg$currPos);
2423
+ s3 = input.charAt(peg$currPos);
2424
+ if (peg$r4.test(s3)) {
2422
2425
  peg$currPos++;
2423
2426
  } else {
2424
2427
  s3 = peg$FAILED;
@@ -2442,8 +2445,8 @@ function peg$parse(input, options) {
2442
2445
  }
2443
2446
  if (s1 !== peg$FAILED) {
2444
2447
  s2 = [];
2445
- if (peg$r4.test(input.charAt(peg$currPos))) {
2446
- s3 = input.charAt(peg$currPos);
2448
+ s3 = input.charAt(peg$currPos);
2449
+ if (peg$r4.test(s3)) {
2447
2450
  peg$currPos++;
2448
2451
  } else {
2449
2452
  s3 = peg$FAILED;
@@ -2451,8 +2454,8 @@ function peg$parse(input, options) {
2451
2454
  }
2452
2455
  while (s3 !== peg$FAILED) {
2453
2456
  s2.push(s3);
2454
- if (peg$r4.test(input.charAt(peg$currPos))) {
2455
- s3 = input.charAt(peg$currPos);
2457
+ s3 = input.charAt(peg$currPos);
2458
+ if (peg$r4.test(s3)) {
2456
2459
  peg$currPos++;
2457
2460
  } else {
2458
2461
  s3 = peg$FAILED;
@@ -3085,6 +3088,15 @@ function peg$parse(input, options) {
3085
3088
 
3086
3089
  peg$result = peg$startRuleFunction();
3087
3090
 
3091
+ if (options.peg$library) {
3092
+ return /** @type {any} */ ({
3093
+ peg$result,
3094
+ peg$currPos,
3095
+ peg$FAILED,
3096
+ peg$maxFailExpected,
3097
+ peg$maxFailPos
3098
+ });
3099
+ }
3088
3100
  if (peg$result !== peg$FAILED && peg$currPos === input.length) {
3089
3101
  return peg$result;
3090
3102
  } else {
@@ -3102,8 +3114,80 @@ function peg$parse(input, options) {
3102
3114
  }
3103
3115
  }
3104
3116
 
3117
+ const peg$allowedStartRules = [
3118
+ "__",
3119
+ "absoluteFilePath",
3120
+ "args",
3121
+ "array",
3122
+ "assignment",
3123
+ "assignmentOrShorthand",
3124
+ "callTarget",
3125
+ "closingBrace",
3126
+ "closingBracket",
3127
+ "closingParen",
3128
+ "comment",
3129
+ "digits",
3130
+ "doubleArrow",
3131
+ "doubleQuoteString",
3132
+ "doubleQuoteStringChar",
3133
+ "escapedChar",
3134
+ "expr",
3135
+ "expression",
3136
+ "float",
3137
+ "functionComposition",
3138
+ "group",
3139
+ "host",
3140
+ "identifier",
3141
+ "identifierChar",
3142
+ "identifierList",
3143
+ "implicitParensArgs",
3144
+ "inlineSpace",
3145
+ "integer",
3146
+ "lambda",
3147
+ "leadingSlashPath",
3148
+ "list",
3149
+ "multiLineComment",
3150
+ "newLine",
3151
+ "number",
3152
+ "object",
3153
+ "objectProperties",
3154
+ "objectProperty",
3155
+ "objectPropertyOrShorthand",
3156
+ "parameterizedLambda",
3157
+ "parensArgs",
3158
+ "pipeline",
3159
+ "path",
3160
+ "pathKey",
3161
+ "protocolCall",
3162
+ "protocol",
3163
+ "reservedProtocol",
3164
+ "scopeReference",
3165
+ "separator",
3166
+ "sign",
3167
+ "singleArrow",
3168
+ "singleLineComment",
3169
+ "singleQuoteString",
3170
+ "singleQuoteStringChar",
3171
+ "step",
3172
+ "start",
3173
+ "string",
3174
+ "templateDocument",
3175
+ "templateDocumentChar",
3176
+ "templateDocumentContents",
3177
+ "templateDocumentText",
3178
+ "templateLiteral",
3179
+ "templateLiteralChar",
3180
+ "templateLiteralContents",
3181
+ "templateLiteralText",
3182
+ "templateSubstitution",
3183
+ "textChar",
3184
+ "tree",
3185
+ "treeAssignments",
3186
+ "whitespaceWithNewLine"
3187
+ ];
3188
+
3105
3189
  export {
3190
+ peg$allowedStartRules as StartRules,
3106
3191
  peg$SyntaxError as SyntaxError,
3107
-
3108
3192
  peg$parse as parse
3109
3193
  };
@@ -0,0 +1,5 @@
1
+ import { Mixin } from "../../index.ts";
2
+
3
+ declare const HandleExtensionsTransform: Mixin<{}>;
4
+
5
+ export default HandleExtensionsTransform;
@@ -1,15 +1,15 @@
1
1
  import { isStringLike } from "@weborigami/async-tree";
2
2
  import Scope from "./Scope.js";
3
- import attachFileLoader from "./attachFileLoader.js";
3
+ import handleExtension from "./handleExtension.js";
4
4
 
5
5
  /**
6
6
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("../../index.js").Constructor<AsyncTree>} AsyncTreeConstructor
8
- * @typedef {import("../../index.js").FileUnpackFunction} FileUnpackFunction
7
+ * @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
8
+ * @typedef {import("../../index.ts").UnpackFunction} FileUnpackFunction
9
9
  *
10
10
  * @param {AsyncTreeConstructor} Base
11
11
  */
12
- export default function FileLoadersTransform(Base) {
12
+ export default function HandleExtensionsTransform(Base) {
13
13
  return class FileLoaders extends Base {
14
14
  async get(key) {
15
15
  let value = await super.get(key);
@@ -18,7 +18,7 @@ export default function FileLoadersTransform(Base) {
18
18
  // exists) that handles that extension.
19
19
  if (value && isStringLike(key)) {
20
20
  const scope = Scope.getScope(this);
21
- value = await attachFileLoader(scope, String(key), value, this);
21
+ value = await handleExtension(scope, String(key), value, this);
22
22
  }
23
23
 
24
24
  return value;
@@ -32,17 +32,24 @@ export default function ImportModulesMixin(Base) {
32
32
  // Ignore errors.
33
33
  }
34
34
  if (stats) {
35
- // Module exists, but we can't load it, probably due to a syntax error.
36
- throw new SyntaxError(`Error loading ${filePath}`);
35
+ // Module exists, but we can't load it. This is often due to a syntax
36
+ // error in the target module, so we offer that as a hint.
37
+ const message = `Error loading ${filePath}, possibly due to a syntax error.\n${error.message}`;
38
+ throw new SyntaxError(message);
37
39
  }
38
40
 
39
41
  // Module doesn't exist.
40
42
  return undefined;
41
43
  }
42
44
 
43
- // If the module loaded and defines a default export, return that, otherwise
44
- // return the overall module.
45
- return typeof obj === "object" && "default" in obj ? obj.default : obj;
45
+ if ("default" in obj) {
46
+ // Module with a default export; return that.
47
+ return obj.default;
48
+ } else {
49
+ // Module with multiple named exports. Cast from a module namespace
50
+ // object to a plain object.
51
+ return { ...obj };
52
+ }
46
53
  }
47
54
  };
48
55
  }
@@ -23,5 +23,11 @@ export default function InvokeFunctionsTransform(Base) {
23
23
  }
24
24
  return value;
25
25
  }
26
+
27
+ // Need to evaluate the value before checking if it is a tree.
28
+ async isKeyForSubtree(key) {
29
+ const value = await this.get(key);
30
+ return Tree.isAsyncTree(value);
31
+ }
26
32
  };
27
33
  }
@@ -3,7 +3,7 @@ import { Mixin } from "../../index.ts";
3
3
  import type { AsyncTree } from "@weborigami/types";
4
4
 
5
5
  // TODO: Figure out how to import declarations from InheritScopeMixin and
6
- // FileLoadersTransform and apply them here.
6
+ // HandleExtensionsTransform and apply them here.
7
7
  declare const OrigamiTransform: Mixin<{
8
8
  scope: AsyncTree|null;
9
9
  }>;
@@ -1,4 +1,4 @@
1
- import FileLoadersTransform from "./FileLoadersTransform.js";
1
+ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
2
2
  import InheritScopeMixin from "./InheritScopeMixin.js";
3
3
 
4
4
  /**
@@ -7,5 +7,7 @@ import InheritScopeMixin from "./InheritScopeMixin.js";
7
7
  * @param {AsyncTreeConstructor} Base
8
8
  */
9
9
  export default function OrigamiTransform(Base) {
10
- return class Origami extends InheritScopeMixin(FileLoadersTransform(Base)) {};
10
+ return class Origami extends InheritScopeMixin(
11
+ HandleExtensionsTransform(Base)
12
+ ) {};
11
13
  }
@@ -38,13 +38,20 @@ export default class Scope {
38
38
  static getScope(tree) {
39
39
  if (!tree) {
40
40
  return null;
41
- } else if ("scope" in tree) {
42
- return /** @type {any} */ (tree).scope;
43
- } else if (Tree.isAsyncTree(tree)) {
44
- return new Scope(tree, this.getScope(tree.parent));
45
- } else {
46
- return tree;
41
+ } else if (!Tree.isAsyncTree(tree)) {
42
+ throw new Error("Tried to get the scope of something that's not a tree.");
47
43
  }
44
+
45
+ if ("scope" in tree) {
46
+ // Ask tree for its scope, use that if defined.
47
+ const scope = /** @type {any} */ (tree).scope;
48
+ if (scope) {
49
+ return scope;
50
+ }
51
+ }
52
+
53
+ // Default scope is tree plus its parent scope.
54
+ return new Scope(tree, this.getScope(tree.parent));
48
55
  }
49
56
 
50
57
  async keys() {
@@ -1,8 +1,4 @@
1
- import {
2
- Tree,
3
- getRealmObjectPrototype,
4
- isPlainObject,
5
- } from "@weborigami/async-tree";
1
+ import { Tree, getRealmObjectPrototype } from "@weborigami/async-tree";
6
2
 
7
3
  const textDecoder = new TextDecoder();
8
4
  const TypedArray = Object.getPrototypeOf(Uint8Array);
@@ -31,16 +27,7 @@ async function getText(value, scope) {
31
27
  value = await value.call(scope);
32
28
  }
33
29
 
34
- // We'd prefer to use Tree.isTreelike() here, but that counts an object with
35
- // an unpack() function as a treelike object. In this case, we don't want to
36
- // unpack anything that's not already treelike.
37
- const isTreelike =
38
- Tree.isAsyncTree(value) ||
39
- value instanceof Function ||
40
- value instanceof Array ||
41
- value instanceof Set ||
42
- isPlainObject(value);
43
- if (isTreelike) {
30
+ if (Tree.isTreelike(value)) {
44
31
  // The mapReduce operation above only implicit casts its top-level input to
45
32
  // a tree. If we're asked for the text of a treelike value, we need to
46
33
  // explicitly recurse.
@@ -1,4 +1,4 @@
1
- import { Tree, isPlainObject } from "@weborigami/async-tree";
1
+ import { Tree, isPlainObject, isUnpackable } from "@weborigami/async-tree";
2
2
  import { ops } from "./internal.js";
3
3
 
4
4
  const codeSymbol = Symbol("code");
@@ -45,17 +45,14 @@ export default async function evaluate(code) {
45
45
  throw ReferenceError(`${codeFragment(code[0])} is not defined`);
46
46
  }
47
47
 
48
- if (
49
- !(fn instanceof Function || Tree.isAsyncTree(fn)) &&
50
- typeof fn.unpack === "function"
51
- ) {
48
+ if (isUnpackable(fn)) {
52
49
  // Unpack the object and use the result as the function or tree.
53
50
  fn = await fn.unpack();
54
51
  }
55
52
 
56
53
  if (!Tree.isTreelike(fn)) {
57
54
  throw TypeError(
58
- `${codeFragment(code[0])} didn't return a function that can be called`
55
+ `${codeFragment(code[0])} didn't return a function or a treelike object`
59
56
  );
60
57
  }
61
58
 
@@ -0,0 +1,56 @@
1
+ import { isUnpackable, symbols } from "@weborigami/async-tree";
2
+ import extname from "./extname.js";
3
+
4
+ /**
5
+ * Given a value that was retrieved using the given key, search in scope for a
6
+ * handler for the file extension on the key (if present). If a handler is
7
+ * found, attach information from it to the value and return the modified value.
8
+ *
9
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
+ *
11
+ * @param {AsyncTree|null} scope
12
+ * @param {any} key
13
+ * @param {any} value
14
+ * @param {AsyncTree|null} parent
15
+ */
16
+ export default async function handleExtension(scope, key, value, parent) {
17
+ const extension = extname(key);
18
+ let result = value;
19
+ if (extension) {
20
+ const handlerName = `${extension.slice(1)}_handler`;
21
+ /** @type {import("../../index.ts").ExtensionHandler} */
22
+ let extensionHandler = await scope?.get(handlerName);
23
+ if (isUnpackable(extensionHandler)) {
24
+ // The extension handler itself needs to be unpacked. E.g., if it's a
25
+ // buffer containing JavaScript file, we need to unpack it to get its
26
+ // default export.
27
+ // @ts-ignore
28
+ extensionHandler = await extensionHandler.unpack();
29
+ }
30
+ if (extensionHandler) {
31
+ const input = value;
32
+
33
+ // If the result is a plain string, box it as a String so we can attach
34
+ // data to it.
35
+ if (typeof result === "string") {
36
+ result = new String(result);
37
+ }
38
+
39
+ if (extensionHandler.mediaType) {
40
+ result.mediaType = extensionHandler.mediaType;
41
+ }
42
+ result[symbols.parent] = parent;
43
+
44
+ const unpack = extensionHandler.unpack;
45
+ if (unpack) {
46
+ // Wrap the unpack function so its only called once per value.
47
+ let loaded;
48
+ result.unpack = async () => {
49
+ loaded ??= await unpack(input, { key, parent });
50
+ return loaded;
51
+ };
52
+ }
53
+ }
54
+ }
55
+ return result;
56
+ }
@@ -3,12 +3,12 @@
3
3
  * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
4
4
  */
5
5
 
6
- import { SiteTree, Tree } from "@weborigami/async-tree";
7
- import FileLoadersTransform from "./FileLoadersTransform.js";
6
+ import { ObjectTree, SiteTree, Tree } from "@weborigami/async-tree";
7
+ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
8
8
  import OrigamiFiles from "./OrigamiFiles.js";
9
9
  import Scope from "./Scope.js";
10
- import attachFileLoader from "./attachFileLoader.js";
11
10
  import concatTreeValues from "./concatTreeValues.js";
11
+ import handleExtension from "./handleExtension.js";
12
12
  import { OrigamiTree, evaluate, expressionFunction } from "./internal.js";
13
13
 
14
14
  // For memoizing lambda functions
@@ -75,7 +75,7 @@ async function fetchResponse(href) {
75
75
  const url = new URL(href);
76
76
  const filename = url.pathname.split("/").pop();
77
77
  if (filename) {
78
- buffer = await attachFileLoader(this, filename, buffer, null);
78
+ buffer = await handleExtension(this, filename, buffer, null);
79
79
  }
80
80
 
81
81
  return buffer;
@@ -163,7 +163,7 @@ export function lambda(parameters, code) {
163
163
  ambients[parameter] = args.shift();
164
164
  }
165
165
  ambients["@recurse"] = invoke;
166
- const scope = new Scope(ambients, this);
166
+ const scope = new Scope(new ObjectTree(ambients), this);
167
167
 
168
168
  let result = await evaluate.call(scope, code);
169
169
 
@@ -252,7 +252,7 @@ tree.toString = () => "«ops.tree»";
252
252
  export function treeHttp(host, ...keys) {
253
253
  const href = constructHref("http:", host, ...keys);
254
254
  /** @type {AsyncTree} */
255
- let result = new (FileLoadersTransform(SiteTree))(href);
255
+ let result = new (HandleExtensionsTransform(SiteTree))(href);
256
256
  result = Scope.treeWithScope(result, this);
257
257
  return result;
258
258
  }
@@ -268,7 +268,7 @@ treeHttp.toString = () => "«ops.treeHttp»";
268
268
  export function treeHttps(host, ...keys) {
269
269
  const href = constructHref("https:", host, ...keys);
270
270
  /** @type {AsyncTree} */
271
- let result = new (FileLoadersTransform(SiteTree))(href);
271
+ let result = new (HandleExtensionsTransform(SiteTree))(href);
272
272
  result = Scope.treeWithScope(result, this);
273
273
  return result;
274
274
  }
@@ -1,10 +1,10 @@
1
1
  import { ObjectTree, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
- import FileLoadersTransform from "../../src/runtime/FileLoadersTransform.js";
4
+ import HandleExtensionsTransform from "../../src/runtime/HandleExtensionsTransform.js";
5
5
  import Scope from "../../src/runtime/Scope.js";
6
6
 
7
- describe("FileLoadersTransform", () => {
7
+ describe("HandleExtensionsTransform", () => {
8
8
  test("invokes an appropriate loader for a .json file extension", async () => {
9
9
  const fixture = createFixture();
10
10
  const numberValue = await fixture.get("foo");
@@ -26,16 +26,16 @@ describe("FileLoadersTransform", () => {
26
26
 
27
27
  function createFixture() {
28
28
  /** @type {import("@weborigami/types").AsyncTree} */
29
- let tree = new (FileLoadersTransform(ObjectTree))({
29
+ let tree = new (HandleExtensionsTransform(ObjectTree))({
30
30
  foo: 1, // No extension, should be left alone
31
31
  "bar.json": `{ "bar": 2 }`,
32
32
  });
33
33
  /** @type {any} */
34
- const scope = {
35
- "@loaders": {
36
- json: (buffer) => JSON.parse(String(buffer)),
34
+ const scope = new ObjectTree({
35
+ json_handler: {
36
+ unpack: (buffer) => JSON.parse(String(buffer)),
37
37
  },
38
- };
38
+ });
39
39
  tree = Scope.treeWithScope(tree, scope);
40
40
  return tree;
41
41
  }
@@ -53,9 +53,8 @@ describe("evaluate", () => {
53
53
 
54
54
  test("if object in function position isn't a function, can unpack it", async () => {
55
55
  const fn = (...args) => args.join(",");
56
- const packed = {
57
- unpack: async () => fn,
58
- };
56
+ const packed = new String();
57
+ /** @type {any} */ (packed).unpack = async () => fn;
59
58
  const code = [packed, "a", "b", "c"];
60
59
  const result = await evaluate.call(null, code);
61
60
  assert.equal(result, "a,b,c");
@@ -1,5 +0,0 @@
1
- import { Mixin } from "../../index.ts";
2
-
3
- declare const FileLoadersTransform: Mixin<{}>;
4
-
5
- export default FileLoadersTransform;
@@ -1,31 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import extname from "./extname.js";
3
- import * as symbols from "./symbols.js";
4
-
5
- export default async function attachFileLoader(scope, key, value, parent) {
6
- const extension = extname(key);
7
- let result = value;
8
- if (extension) {
9
- const loaderName = extension.slice(1);
10
- const loader = await Tree.traverse(scope, "@loaders", loaderName);
11
- if (loader) {
12
- const input = value;
13
-
14
- // If the result is a plain string, box it as a String so we can attach
15
- // data to it.
16
- if (typeof result === "string") {
17
- result = new String(result);
18
- }
19
- result[symbols.parent] = parent;
20
-
21
- // Wrap the loader with a function that will only be called once per
22
- // value.
23
- let loaded;
24
- result.unpack = async () => {
25
- loaded ??= await loader(input, { key, parent });
26
- return loaded;
27
- };
28
- }
29
- }
30
- return result;
31
- }
@@ -1 +0,0 @@
1
- export const parent = Symbol("parent");