@weborigami/language 0.5.5 → 0.5.7

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.
Files changed (95) hide show
  1. package/index.ts +16 -6
  2. package/main.js +9 -4
  3. package/package.json +4 -3
  4. package/src/compiler/compile.js +10 -4
  5. package/src/compiler/optimize.js +115 -97
  6. package/src/compiler/origami.pegjs +1 -4
  7. package/src/compiler/parse.js +568 -588
  8. package/src/compiler/parserHelpers.js +2 -2
  9. package/src/handlers/css_handler.js +7 -0
  10. package/src/handlers/csv_handler.js +129 -0
  11. package/src/handlers/handlers.js +33 -0
  12. package/src/handlers/htm_handler.js +2 -0
  13. package/src/handlers/html_handler.js +7 -0
  14. package/src/handlers/jpeg_handler.js +62 -0
  15. package/src/handlers/jpg_handler.js +2 -0
  16. package/src/handlers/js_handler.js +51 -0
  17. package/src/handlers/json_handler.js +26 -0
  18. package/src/handlers/md_handler.js +7 -0
  19. package/src/handlers/mjs_handler.js +2 -0
  20. package/src/handlers/ori_handler.js +52 -0
  21. package/src/handlers/oridocument_handler.js +77 -0
  22. package/src/handlers/parseFrontMatter.js +16 -0
  23. package/src/handlers/ts_handler.js +1 -0
  24. package/src/handlers/txt_handler.js +108 -0
  25. package/src/handlers/wasm_handler.js +15 -0
  26. package/src/handlers/xhtml_handler.js +2 -0
  27. package/src/handlers/yaml_handler.js +33 -0
  28. package/src/handlers/yml_handler.js +2 -0
  29. package/src/project/builtins.js +5 -0
  30. package/src/project/coreGlobals.js +17 -0
  31. package/src/{runtime → project}/jsGlobals.js +3 -1
  32. package/src/project/projectConfig.js +36 -0
  33. package/src/project/projectGlobals.js +19 -0
  34. package/src/project/projectRoot.js +59 -0
  35. package/src/protocols/constructHref.js +20 -0
  36. package/src/protocols/constructSiteTree.js +26 -0
  37. package/src/protocols/explore.js +14 -0
  38. package/src/protocols/fetchAndHandleExtension.js +25 -0
  39. package/src/protocols/files.js +26 -0
  40. package/src/protocols/http.js +15 -0
  41. package/src/protocols/https.js +15 -0
  42. package/src/protocols/httpstree.js +14 -0
  43. package/src/protocols/httptree.js +14 -0
  44. package/src/protocols/node.js +13 -0
  45. package/src/protocols/package.js +67 -0
  46. package/src/protocols/protocolGlobals.js +12 -0
  47. package/src/protocols/protocols.js +8 -0
  48. package/src/runtime/EventTargetMixin.js +1 -1
  49. package/src/runtime/HandleExtensionsTransform.js +3 -12
  50. package/src/runtime/ImportModulesMixin.js +4 -10
  51. package/src/runtime/InvokeFunctionsTransform.js +1 -1
  52. package/src/runtime/evaluate.js +15 -8
  53. package/src/runtime/expressionFunction.js +5 -7
  54. package/src/runtime/expressionObject.js +10 -20
  55. package/src/runtime/functionResultsMap.js +1 -3
  56. package/src/runtime/{handlers.js → handleExtension.js} +13 -11
  57. package/src/runtime/mergeTrees.js +1 -8
  58. package/src/runtime/ops.js +83 -90
  59. package/test/compiler/compile.test.js +20 -19
  60. package/test/compiler/optimize.test.js +60 -25
  61. package/test/compiler/parse.test.js +4 -4
  62. package/test/generator/oriEval.js +4 -5
  63. package/test/handlers/csv.handler.test.js +36 -0
  64. package/test/handlers/fixtures/add.wasm +0 -0
  65. package/test/handlers/fixtures/exif.jpeg +0 -0
  66. package/test/handlers/fixtures/frontMatter.md +5 -0
  67. package/test/handlers/fixtures/list.js +4 -0
  68. package/test/handlers/fixtures/multiple.js +4 -0
  69. package/test/handlers/fixtures/obj.js +3 -0
  70. package/test/handlers/fixtures/site.ori +5 -0
  71. package/test/handlers/fixtures/string.js +1 -0
  72. package/test/handlers/fixtures/tag.yaml +5 -0
  73. package/test/handlers/fixtures/test.ori +9 -0
  74. package/test/handlers/jpeg.handler.test.js +18 -0
  75. package/test/handlers/js.handler.test.js +46 -0
  76. package/test/handlers/json.handler.test.js +14 -0
  77. package/test/handlers/ori.handler.test.js +87 -0
  78. package/test/handlers/oridocument.handler.test.js +68 -0
  79. package/test/handlers/txt.handler.test.js +41 -0
  80. package/test/handlers/wasm.handler.test.js +20 -0
  81. package/test/handlers/yaml.handler.test.js +17 -0
  82. package/test/project/fixtures/withConfig/config.ori +4 -0
  83. package/test/project/fixtures/withConfig/subfolder/greet.js +1 -0
  84. package/test/project/fixtures/withPackageJson/package.json +0 -0
  85. package/test/project/jsGlobals.test.js +21 -0
  86. package/test/project/projectConfig.test.js +28 -0
  87. package/test/project/projectRoot.test.js +40 -0
  88. package/test/protocols/package.test.js +11 -0
  89. package/test/runtime/evaluate.test.js +26 -42
  90. package/test/runtime/expressionObject.test.js +16 -20
  91. package/test/runtime/{handlers.test.js → handleExtension.test.js} +4 -20
  92. package/test/runtime/jsGlobals.test.js +4 -6
  93. package/test/runtime/mergeTrees.test.js +2 -4
  94. package/test/runtime/ops.test.js +66 -68
  95. package/src/runtime/getHandlers.js +0 -10
@@ -0,0 +1,17 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import protocolGlobals from "../protocols/protocolGlobals.js";
3
+ import * as protocols from "../protocols/protocols.js";
4
+ import builtins from "./builtins.js";
5
+ import jsGlobals from "./jsGlobals.js";
6
+
7
+ export default async function coreGlobals() {
8
+ const handlerGlobals = await import("../handlers/handlers.js");
9
+ return {
10
+ ...jsGlobals,
11
+ Tree,
12
+ Protocol: protocols,
13
+ ...protocolGlobals,
14
+ ...handlerGlobals,
15
+ ...builtins,
16
+ };
17
+ }
@@ -184,6 +184,7 @@ async function importWrapper(modulePath) {
184
184
  const filePath = path.resolve(current.path, modulePath);
185
185
  return import(filePath);
186
186
  }
187
+ importWrapper.containerAsTarget = true;
187
188
 
188
189
  /**
189
190
  * Some JavaScript globals like Promise have static methods like Promise.all
@@ -219,10 +220,11 @@ function bindStaticMethods(obj) {
219
220
  }
220
221
 
221
222
  let extended;
222
- // A regular object or an oddball like Proxy with no prototype
223
223
  if (typeof obj === "object" || !obj.prototype) {
224
+ // A regular object or an oddball like Proxy with no prototype
224
225
  extended = Object.create(obj);
225
226
  } else {
227
+ // A function, possibly a constructor called with or without `new`
226
228
  /** @this {any} */
227
229
  extended = function (...args) {
228
230
  const calledWithNew = this instanceof extended;
@@ -0,0 +1,36 @@
1
+ import { FileTree, toString } from "@weborigami/async-tree";
2
+ import ori_handler from "../handlers/ori_handler.js";
3
+ import coreGlobals from "./coreGlobals.js";
4
+ import projectRoot from "./projectRoot.js";
5
+
6
+ const mapPathToConfig = new Map();
7
+
8
+ export default async function config(dir = process.cwd()) {
9
+ const root = await projectRoot(dir);
10
+
11
+ const rootPath = root.path;
12
+ const cached = mapPathToConfig.get(rootPath);
13
+ if (cached) {
14
+ return cached;
15
+ }
16
+
17
+ // Use a plain FileTree to avoid loading extension handlers
18
+ const rootFileTree = new FileTree(rootPath);
19
+ const configBuffer = await rootFileTree.get("config.ori");
20
+ let configObject = {};
21
+ if (configBuffer) {
22
+ const configText = toString(configBuffer);
23
+ if (configText) {
24
+ // Config uses only core globals (we're defining the config)
25
+ const globals = await coreGlobals();
26
+ // Evaluate the config file to obtain the configuration object
27
+ configObject = await ori_handler.unpack(configText, {
28
+ globals,
29
+ parent: root,
30
+ });
31
+ }
32
+ }
33
+
34
+ mapPathToConfig.set(rootPath, configObject);
35
+ return configObject;
36
+ }
@@ -0,0 +1,19 @@
1
+ import coreGlobals from "./coreGlobals.js";
2
+ import projectConfig from "./projectConfig.js";
3
+
4
+ let globals;
5
+
6
+ // Core globals plus project config
7
+ export default async function projectGlobals() {
8
+ if (!globals) {
9
+ // Start with core globals
10
+ globals = await coreGlobals();
11
+ // Now get config. The config.ori file may require access to globals,
12
+ // which will obtain the core globals set above. Once we've got the
13
+ // config, we add it to the globals.
14
+ const config = await projectConfig();
15
+ Object.assign(globals, config);
16
+ }
17
+
18
+ return globals;
19
+ }
@@ -0,0 +1,59 @@
1
+ import { FileTree } from "@weborigami/async-tree";
2
+ import path from "node:path";
3
+ import OrigamiFiles from "../runtime/OrigamiFiles.js";
4
+
5
+ const configFileName = "config.ori";
6
+ const packageFileName = "package.json";
7
+
8
+ const mapPathToRoot = new Map();
9
+
10
+ /**
11
+ * Return an OrigamiFiles object for the current project.
12
+ *
13
+ * This searches the current directory and its ancestors for an Origami file
14
+ * called `config.ori`. If an Origami configuration file is found, the
15
+ * containing folder is considered to be the project root.
16
+ *
17
+ * Otherwise, this looks for a package.json file to determine the project root.
18
+ * If no package.json is found, the current folder is used as the project root.
19
+ *
20
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
21
+ *
22
+ * @param {string} [dirname]
23
+ */
24
+ export default async function projectRoot(dirname = process.cwd()) {
25
+ const cached = mapPathToRoot.get(dirname);
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+
30
+ let root;
31
+ let value;
32
+ // Use a plain FileTree to avoid loading extension handlers
33
+ const currentTree = new FileTree(dirname);
34
+ // Try looking for config file
35
+ value = await currentTree.get(configFileName);
36
+ if (value) {
37
+ // Found config file
38
+ root = new OrigamiFiles(currentTree.path);
39
+ } else {
40
+ // Try looking for package.json
41
+ value = await currentTree.get(packageFileName);
42
+ if (value) {
43
+ // Found package.json
44
+ root = new OrigamiFiles(currentTree.path);
45
+ } else {
46
+ // Move up a folder and try again
47
+ const parentPath = path.dirname(dirname);
48
+ if (parentPath !== dirname) {
49
+ root = await projectRoot(parentPath);
50
+ } else {
51
+ // At filesystem root, use current working directory
52
+ root = new OrigamiFiles(process.cwd());
53
+ }
54
+ }
55
+ }
56
+
57
+ mapPathToRoot.set(dirname, root);
58
+ return root;
59
+ }
@@ -0,0 +1,20 @@
1
+ import { pathFromKeys } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * Given a protocol, a host, and a list of keys, construct an href.
5
+ *
6
+ * @param {string} protocol
7
+ * @param {string} host
8
+ * @param {string[]} keys
9
+ */
10
+ export default function constructHref(protocol, host, ...keys) {
11
+ const path = pathFromKeys(keys);
12
+ let href = [host, path].join("/");
13
+ if (!href.startsWith(protocol)) {
14
+ if (!href.startsWith("//")) {
15
+ href = `//${href}`;
16
+ }
17
+ href = `${protocol}${href}`;
18
+ }
19
+ return href;
20
+ }
@@ -0,0 +1,26 @@
1
+ import { trailingSlash } from "@weborigami/async-tree";
2
+ import HandleExtensionsTransform from "../runtime/HandleExtensionsTransform.js";
3
+ import constructHref from "./constructHref.js";
4
+
5
+ /**
6
+ * Given a protocol, a host, and a list of keys, construct an href.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ *
10
+ * @param {string} protocol
11
+ * @param {import("../../index.ts").Constructor<AsyncTree>} treeClass
12
+ * @param {string} host
13
+ * @param {string[]} keys
14
+ */
15
+ export default function constructSiteTree(protocol, treeClass, host, ...keys) {
16
+ // If the last key doesn't end in a slash, remove it for now.
17
+ let lastKey;
18
+ if (keys.length > 0 && keys.at(-1) && !trailingSlash.has(keys.at(-1))) {
19
+ lastKey = keys.pop();
20
+ }
21
+
22
+ const href = constructHref(protocol, host, ...keys);
23
+ let result = new (HandleExtensionsTransform(treeClass))(href);
24
+
25
+ return lastKey ? result.get(lastKey) : result;
26
+ }
@@ -0,0 +1,14 @@
1
+ import { ExplorableSiteTree } from "@weborigami/async-tree";
2
+ import constructSiteTree from "./constructSiteTree.js";
3
+
4
+ /**
5
+ * A site tree with JSON Keys via HTTPS.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string} host
10
+ * @param {...string} keys
11
+ */
12
+ export default function explore(host, ...keys) {
13
+ return constructSiteTree("https:", ExplorableSiteTree, host, ...keys);
14
+ }
@@ -0,0 +1,25 @@
1
+ import handleExtension from "../runtime/handleExtension.js";
2
+
3
+ /**
4
+ * Fetch the resource at the given href.
5
+ *
6
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
+ *
8
+ * @param {string} href
9
+ */
10
+ export default async function fetchAndHandleExtension(href) {
11
+ const response = await fetch(href);
12
+ if (!response.ok) {
13
+ return undefined;
14
+ }
15
+ let buffer = await response.arrayBuffer();
16
+
17
+ // Attach any loader defined for the file type.
18
+ const url = new URL(href);
19
+ const filename = url.pathname.split("/").pop();
20
+ if (filename) {
21
+ buffer = await handleExtension(buffer, filename);
22
+ }
23
+
24
+ return buffer;
25
+ }
@@ -0,0 +1,26 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import OrigamiFiles from "../runtime/OrigamiFiles.js";
5
+
6
+ /**
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string[]} keys
10
+ */
11
+ export default async function files(...keys) {
12
+ // If path begins with `~`, treat it relative to the home directory.
13
+ // Otherwise, treat it relative to the current working directory.
14
+ let relativePath = keys.join(path.sep);
15
+ let basePath;
16
+ if (relativePath.startsWith("~")) {
17
+ basePath = os.homedir();
18
+ relativePath = relativePath.slice(2);
19
+ } else {
20
+ basePath = process.cwd();
21
+ }
22
+ const resolved = path.resolve(basePath, relativePath);
23
+
24
+ const result = new OrigamiFiles(resolved);
25
+ return result;
26
+ }
@@ -0,0 +1,15 @@
1
+ import constructHref from "./constructHref.js";
2
+ import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
3
+
4
+ /**
5
+ * Retrieve the indicated web resource via HTTP.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string} host
10
+ * @param {...string} keys
11
+ */
12
+ export default async function http(host, ...keys) {
13
+ const href = constructHref("http:", host, ...keys);
14
+ return fetchAndHandleExtension(href);
15
+ }
@@ -0,0 +1,15 @@
1
+ import constructHref from "./constructHref.js";
2
+ import fetchAndHandleExtension from "./fetchAndHandleExtension.js";
3
+
4
+ /**
5
+ * Retrieve the indicated web resource via HTTPS.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string} host
10
+ * @param {...string} keys
11
+ */
12
+ export default async function https(host, ...keys) {
13
+ const href = constructHref("https:", host, ...keys);
14
+ return fetchAndHandleExtension(href);
15
+ }
@@ -0,0 +1,14 @@
1
+ import { SiteTree } from "@weborigami/async-tree";
2
+ import constructSiteTree from "./constructSiteTree.js";
3
+
4
+ /**
5
+ * Return a website tree via HTTPS.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string} host
10
+ * @param {...string} keys
11
+ */
12
+ export default function httpstree(host, ...keys) {
13
+ return constructSiteTree("https:", SiteTree, host, ...keys);
14
+ }
@@ -0,0 +1,14 @@
1
+ import { SiteTree } from "@weborigami/async-tree";
2
+ import constructSiteTree from "./constructSiteTree.js";
3
+
4
+ /**
5
+ * Return a website tree via HTTP.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @param {string} host
10
+ * @param {...string} keys
11
+ */
12
+ export default function httptree(host, ...keys) {
13
+ return constructSiteTree("http:", SiteTree, host, ...keys);
14
+ }
@@ -0,0 +1,13 @@
1
+ import { trailingSlash, Tree } from "@weborigami/async-tree";
2
+
3
+ /**
4
+ * The node: protocol does a dynamic import from the `node:` namespace.
5
+ *
6
+ * @param {string[]} keys
7
+ */
8
+ export default async function node(...keys) {
9
+ const key = keys.shift();
10
+ const normalized = trailingSlash.remove(key);
11
+ const module = await import(`node:${normalized}`);
12
+ return keys.length > 0 ? Tree.traverse(module, ...keys) : module;
13
+ }
@@ -0,0 +1,67 @@
1
+ import { Tree, keysFromPath } from "@weborigami/async-tree";
2
+ import projectRoot from "../project/projectRoot.js";
3
+
4
+ /**
5
+ * @param {string[]} keys
6
+ */
7
+ export default async function packageNamespace(...keys) {
8
+ const parent = await projectRoot();
9
+
10
+ let name = keys.shift();
11
+ let organization;
12
+ if (name?.startsWith("@")) {
13
+ // First key is an npm organization
14
+ organization = name;
15
+ if (keys.length === 0) {
16
+ // Return a function that will process the next key
17
+ return async (name, ...keys) =>
18
+ getPackage(parent, organization, name, keys);
19
+ }
20
+ name = keys.shift();
21
+ }
22
+
23
+ return getPackage(parent, organization, name, keys);
24
+ }
25
+
26
+ async function getPackage(parent, organization, name, keys) {
27
+ const packagePath = ["node_modules"];
28
+ if (organization) {
29
+ packagePath.push(organization);
30
+ }
31
+ packagePath.push(name);
32
+
33
+ const parentScope = await Tree.scope(parent);
34
+ const packageRoot = await Tree.traverse(
35
+ // @ts-ignore
36
+ parentScope,
37
+ ...packagePath
38
+ );
39
+
40
+ if (!packageRoot) {
41
+ throw new Error(`Can't find ${packagePath.join("/")}`);
42
+ }
43
+
44
+ const mainPath = await Tree.traverse(packageRoot, "package.json", "main");
45
+ if (!mainPath) {
46
+ throw new Error(
47
+ `node_modules/${keys.join(
48
+ "/"
49
+ )} doesn't contain a package.json with a "main" entry.`
50
+ );
51
+ }
52
+
53
+ const mainKeys = keysFromPath(mainPath);
54
+ const mainContainerKeys = mainKeys.slice(0, -1);
55
+ const mainFileName = mainKeys[mainKeys.length - 1];
56
+ const mainContainer = await Tree.traverse(packageRoot, ...mainContainerKeys);
57
+ const packageExports = await mainContainer.import(mainFileName);
58
+
59
+ let result =
60
+ "default" in packageExports ? packageExports.default : packageExports;
61
+
62
+ if (keys.length > 0) {
63
+ result = await Tree.traverse(result, ...keys);
64
+ }
65
+
66
+ return result;
67
+ }
@@ -0,0 +1,12 @@
1
+ import * as protocols from "./protocols.js";
2
+
3
+ export default {
4
+ "explore:": protocols.explore,
5
+ "files:": protocols.files,
6
+ "http:": protocols.http,
7
+ "https:": protocols.https,
8
+ "httpstree:": protocols.httpstree,
9
+ "httptree:": protocols.httptree,
10
+ "node:": protocols.node,
11
+ "package:": protocols.package,
12
+ };
@@ -0,0 +1,8 @@
1
+ export { default as explore } from "./explore.js";
2
+ export { default as files } from "./files.js";
3
+ export { default as http } from "./http.js";
4
+ export { default as https } from "./https.js";
5
+ export { default as httpstree } from "./httpstree.js";
6
+ export { default as httptree } from "./httptree.js";
7
+ export { default as node } from "./node.js";
8
+ export { default as package } from "./package.js";
@@ -85,7 +85,7 @@ export default function EventTargetMixin(Base) {
85
85
  if (stopImmediatePropagation) {
86
86
  break;
87
87
  }
88
- listener.call(this, event);
88
+ listener(event);
89
89
  }
90
90
 
91
91
  return !event.defaultPrevented;
@@ -1,5 +1,4 @@
1
- import getHandlers from "./getHandlers.js";
2
- import { handleExtension } from "./handlers.js";
1
+ import handleExtension from "./handleExtension.js";
3
2
 
4
3
  /**
5
4
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -9,18 +8,10 @@ import { handleExtension } from "./handlers.js";
9
8
  * @param {AsyncTreeConstructor} Base
10
9
  */
11
10
  export default function HandleExtensionsTransform(Base) {
12
- return class FileLoaders extends Base {
13
- constructor(...args) {
14
- super(...args);
15
-
16
- // Callers should set this to the set of supported extension handlers
17
- this.handlers = null;
18
- }
19
-
11
+ return class HandleExtensions extends Base {
20
12
  async get(key) {
21
13
  const value = await super.get(key);
22
- const handlers = getHandlers(this);
23
- return handleExtension(this, value, key, handlers);
14
+ return handleExtension(value, key, this);
24
15
  }
25
16
  };
26
17
  }
@@ -20,9 +20,9 @@ export default function ImportModulesMixin(Base) {
20
20
  fileUrl.href + `?cacheBust=${moduleCache.getTimestamp()}`;
21
21
 
22
22
  // Try to load the module.
23
- let obj;
23
+ let object;
24
24
  try {
25
- obj = await import(modulePath);
25
+ object = await import(modulePath);
26
26
  } catch (/** @type {any} */ error) {
27
27
  if (error.code !== "ERR_MODULE_NOT_FOUND") {
28
28
  throw error;
@@ -48,14 +48,8 @@ export default function ImportModulesMixin(Base) {
48
48
  throw new SyntaxError(message);
49
49
  }
50
50
 
51
- if ("default" in obj) {
52
- // Module with a default export; return that.
53
- return obj.default;
54
- } else {
55
- // Module with multiple named exports. Cast from a module namespace
56
- // object to a plain object.
57
- return { ...obj };
58
- }
51
+ // Cast from an exotic module namespace object to a plain object.
52
+ return { ...object };
59
53
  }
60
54
  };
61
55
  }
@@ -13,7 +13,7 @@ export default function InvokeFunctionsTransform(Base) {
13
13
  async get(key) {
14
14
  let value = await super.get(key);
15
15
  if (typeof value === "function") {
16
- value = await value.call(this);
16
+ value = await value();
17
17
 
18
18
  if (Tree.isAsyncTree(value) && !value.parent) {
19
19
  value.parent = this;
@@ -1,18 +1,17 @@
1
1
  import { Tree, isUnpackable } from "@weborigami/async-tree";
2
- import { displayWarning, symbols } from "@weborigami/language";
3
2
  import codeFragment from "./codeFragment.js";
3
+ import { displayWarning } from "./errors.js";
4
+ import * as symbols from "./symbols.js";
4
5
 
5
6
  /**
6
7
  * Evaluate the given code and return the result.
7
8
  *
8
9
  * `this` should be the tree used as the context for the evaluation.
9
10
  *
10
- * @this {import("@weborigami/types").AsyncTree|null}
11
11
  * @param {import("../../index.ts").AnnotatedCode} code
12
+ * @param {import("../../index.ts").RuntimeState} [state]
12
13
  */
13
- export default async function evaluate(code) {
14
- const tree = this;
15
-
14
+ export default async function evaluate(code, state = {}) {
16
15
  if (!(code instanceof Array)) {
17
16
  // Simple scalar; return as is.
18
17
  return code;
@@ -25,7 +24,7 @@ export default async function evaluate(code) {
25
24
  } else {
26
25
  // Evaluate each instruction in the code.
27
26
  evaluated = await Promise.all(
28
- code.map((instruction) => evaluate.call(tree, instruction))
27
+ code.map((instruction) => evaluate(instruction, state))
29
28
  );
30
29
  }
31
30
 
@@ -46,13 +45,21 @@ export default async function evaluate(code) {
46
45
  fn = await fn.unpack();
47
46
  }
48
47
 
48
+ if (fn.needsState) {
49
+ // The function is an op that wants the runtime state
50
+ args.push(state);
51
+ } else if (fn.containerAsTarget && state.container) {
52
+ // The function wants the code's container as the `this` target
53
+ fn = fn.bind(state.container);
54
+ }
55
+
49
56
  // Execute the function or traverse the tree.
50
57
  let result;
51
58
  try {
52
59
  result =
53
60
  fn instanceof Function
54
- ? await fn.call(tree, ...args) // Invoke the function
55
- : await Tree.traverseOrThrow.call(tree, fn, ...args); // Traverse the tree.
61
+ ? await fn(...args) // Invoke the function
62
+ : await Tree.traverseOrThrow(fn, ...args); // Traverse the tree.
56
63
  } catch (/** @type {any} */ error) {
57
64
  if (!error.location) {
58
65
  // Attach the location of the code we tried to evaluate.
@@ -6,15 +6,13 @@ import { evaluate } from "./internal.js";
6
6
  * Given parsed Origami code, return a function that executes that code.
7
7
  *
8
8
  * @param {import("../../index.js").AnnotatedCode} code - parsed Origami expression
9
- * @param {string} [name] - optional name of the function
9
+ * @param {AsyncTree} parent - the parent tree in which the code is running
10
10
  */
11
- export function createExpressionFunction(code, name) {
12
- /** @this {AsyncTree|null} */
11
+ export function createExpressionFunction(code, parent) {
13
12
  async function fn() {
14
- return evaluate.call(this, code);
15
- }
16
- if (name) {
17
- Object.defineProperty(fn, "name", { value: name });
13
+ return evaluate(code, {
14
+ container: parent,
15
+ });
18
16
  }
19
17
  fn.code = code;
20
18
  fn.toString = () => code.location.source.text;
@@ -6,8 +6,7 @@ import {
6
6
  trailingSlash,
7
7
  Tree,
8
8
  } from "@weborigami/async-tree";
9
- import getHandlers from "./getHandlers.js";
10
- import { handleExtension } from "./handlers.js";
9
+ import handleExtension from "./handleExtension.js";
11
10
  import { evaluate, ops } from "./internal.js";
12
11
 
13
12
  /**
@@ -25,11 +24,12 @@ import { evaluate, ops } from "./internal.js";
25
24
  * property getter on the object.
26
25
  *
27
26
  * @param {*} entries
28
- * @param {import("@weborigami/types").AsyncTree | null} parent
27
+ * @param {import("../../index.ts").RuntimeState} [state]
29
28
  */
30
- export default async function expressionObject(entries, parent) {
29
+ export default async function expressionObject(entries, state = {}) {
31
30
  // Create the object and set its parent
32
31
  const object = {};
32
+ const parent = state?.object ?? null;
33
33
  if (parent !== null && !Tree.isAsyncTree(parent)) {
34
34
  throw new TypeError(`Parent must be an AsyncTree or null`);
35
35
  }
@@ -86,22 +86,12 @@ export default async function expressionObject(entries, parent) {
86
86
  code = value;
87
87
  }
88
88
 
89
- let get;
90
- if (extname) {
91
- // Key has extension, getter will invoke code then attach unpack method
92
- get = async () => {
93
- tree ??= new ObjectTree(object);
94
- const result = await evaluate.call(tree, code);
95
- const handlers = getHandlers(tree);
96
- return handleExtension(tree, result, key, handlers);
97
- };
98
- } else {
99
- // No extension, so getter just invokes code.
100
- get = async () => {
101
- tree ??= new ObjectTree(object);
102
- return evaluate.call(tree, code);
103
- };
104
- }
89
+ const get = async () => {
90
+ tree ??= new ObjectTree(object);
91
+ const newState = Object.assign({}, state, { object: tree });
92
+ const result = await evaluate(code, newState);
93
+ return extname ? handleExtension(result, key, tree) : result;
94
+ };
105
95
 
106
96
  Object.defineProperty(object, key, {
107
97
  configurable: true,
@@ -10,9 +10,7 @@ export default async function functionResultsMap(treelike) {
10
10
 
11
11
  value: async (sourceValue, sourceKey, tree) => {
12
12
  const resultValue =
13
- typeof sourceValue === "function"
14
- ? await sourceValue.call(tree)
15
- : sourceValue;
13
+ typeof sourceValue === "function" ? await sourceValue() : sourceValue;
16
14
  return resultValue;
17
15
  },
18
16
  });