@weborigami/language 0.6.17 → 0.7.0-beta.2

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 (90) hide show
  1. package/index.ts +1 -0
  2. package/main.js +7 -1
  3. package/package.json +7 -6
  4. package/src/compiler/compile.js +10 -3
  5. package/src/compiler/optimize.js +71 -40
  6. package/src/compiler/parse.js +1 -1
  7. package/src/compiler/parserHelpers.js +5 -3
  8. package/src/handlers/addExtensionKeyFn.js +18 -0
  9. package/src/handlers/epub_handler.js +54 -0
  10. package/src/handlers/getSource.js +11 -0
  11. package/src/handlers/handlers.js +2 -0
  12. package/src/handlers/htm_handler.js +1 -1
  13. package/src/handlers/js_handler.js +13 -4
  14. package/src/handlers/mediaTypeExtensions.json +15 -0
  15. package/src/handlers/ori_handler.js +8 -7
  16. package/src/handlers/oridocument_handler.js +19 -28
  17. package/src/handlers/processOriExport.js +17 -0
  18. package/src/handlers/tsv_handler.js +1 -1
  19. package/src/handlers/txt_handler.js +4 -2
  20. package/src/handlers/xhtml_handler.js +1 -1
  21. package/src/handlers/yaml_handler.js +6 -3
  22. package/src/handlers/zip_handler.js +112 -0
  23. package/src/project/activeProjectRoot.js +9 -0
  24. package/src/project/getGlobalsForTree.js +5 -0
  25. package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
  26. package/src/project/jsGlobals.js +1 -0
  27. package/src/project/projectConfig.js +2 -2
  28. package/src/project/projectRootFromPath.js +2 -0
  29. package/src/protocols/constructHref.js +3 -3
  30. package/src/protocols/constructSiteTree.js +11 -2
  31. package/src/protocols/explore.js +1 -1
  32. package/src/protocols/explorehttp.js +1 -1
  33. package/src/protocols/fetchAndHandleExtension.js +23 -11
  34. package/src/protocols/files.js +1 -0
  35. package/src/protocols/http.js +4 -1
  36. package/src/protocols/https.js +4 -1
  37. package/src/protocols/httpstree.js +1 -1
  38. package/src/protocols/httptree.js +1 -1
  39. package/src/protocols/package.js +15 -3
  40. package/src/runtime/AsyncCacheTransform.d.ts +5 -0
  41. package/src/runtime/AsyncCacheTransform.js +134 -0
  42. package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
  43. package/src/runtime/HandleExtensionsTransform.js +18 -2
  44. package/src/runtime/OrigamiFileMap.d.ts +5 -2
  45. package/src/runtime/OrigamiFileMap.js +27 -4
  46. package/src/runtime/ScopeMap.js +72 -0
  47. package/src/runtime/SyncCacheTransform.d.ts +8 -0
  48. package/src/runtime/SyncCacheTransform.js +133 -0
  49. package/src/runtime/SystemCacheMap.js +259 -0
  50. package/src/runtime/WatchFilesMixin.js +52 -19
  51. package/src/runtime/enableValueCaching.js +192 -0
  52. package/src/runtime/execute.js +2 -2
  53. package/src/runtime/executionContext.js +7 -0
  54. package/src/runtime/explainReferenceError.js +7 -2
  55. package/src/runtime/expressionObject.js +54 -46
  56. package/src/runtime/handleExtension.js +65 -34
  57. package/src/runtime/interop.js +2 -2
  58. package/src/runtime/mergeTrees.js +1 -1
  59. package/src/runtime/ops.js +28 -33
  60. package/src/runtime/symbols.js +3 -0
  61. package/src/runtime/systemCache.js +3 -0
  62. package/src/runtime/volatile.js +14 -0
  63. package/test/compiler/codeHelpers.js +2 -1
  64. package/test/compiler/optimize.test.js +62 -54
  65. package/test/handlers/epub_handler.test.js +27 -0
  66. package/test/handlers/fixtures/test.zip +0 -0
  67. package/test/handlers/ori_handler.test.js +22 -3
  68. package/test/handlers/oridocument_handler.test.js +1 -1
  69. package/test/handlers/zip_handler.test.js +45 -0
  70. package/test/protocols/https.test.js +19 -0
  71. package/test/protocols/package.test.js +7 -2
  72. package/test/runtime/AsyncCacheTransform.test.js +91 -0
  73. package/test/runtime/OrigamiFileMap.test.js +26 -23
  74. package/test/runtime/ScopeMap.test.js +49 -0
  75. package/test/runtime/SyncCacheTransform.test.js +93 -0
  76. package/test/runtime/SystemCacheMap.test.js +239 -0
  77. package/test/runtime/asyncCalcs.js +28 -0
  78. package/test/runtime/enableValueCaching.test.js +55 -0
  79. package/test/runtime/errors.test.js +53 -30
  80. package/test/runtime/evaluate.test.js +9 -4
  81. package/test/runtime/execute.test.js +6 -1
  82. package/test/runtime/expressionObject.test.js +55 -15
  83. package/test/runtime/fetchAndHandleExtension.test.js +24 -0
  84. package/test/runtime/fixtures/unpack/hello.json +1 -0
  85. package/test/runtime/handleExtension.test.js +12 -1
  86. package/test/runtime/ops.test.js +70 -65
  87. package/test/runtime/syncCalcs.js +27 -0
  88. package/test/runtime/systemCache.test.js +66 -0
  89. package/src/runtime/assignPropertyDescriptors.js +0 -23
  90. package/src/runtime/asyncStorage.js +0 -7
@@ -1,12 +1,18 @@
1
+ import { ObjectMap } from "@weborigami/async-tree";
1
2
  import assert from "node:assert";
2
- import { describe, test } from "node:test";
3
+ import { beforeEach, describe, test } from "node:test";
3
4
  import coreGlobals from "../../src/project/coreGlobals.js";
4
5
  import { formatError } from "../../src/runtime/errors.js";
5
6
  import evaluate from "../../src/runtime/evaluate.js";
7
+ import systemCache from "../../src/runtime/systemCache.js";
6
8
 
7
9
  const globals = await coreGlobals();
8
10
 
9
11
  describe("formatError", () => {
12
+ beforeEach(() => {
13
+ systemCache.clear();
14
+ });
15
+
10
16
  describe("ReferenceError", () => {
11
17
  test("identifies an undefined function", async () => {
12
18
  await assertError(
@@ -17,7 +23,7 @@ evaluating: \x1b[31mdoesntExist\x1b[0m`,
17
23
  );
18
24
  });
19
25
 
20
- test("identifies the argument that produced an error", async () => {
26
+ test.skip("identifies the argument that produced an error", async () => {
21
27
  await assertError(
22
28
  `Tree.map(foo, (_) => _)`,
23
29
  `ReferenceError: Tree.map: The map argument wasn't defined.
@@ -73,9 +79,9 @@ evaluating: \x1B[31mdatu.toString\x1B[0m
73
79
  });
74
80
 
75
81
  test("proposes typos using scope keys", async () => {
76
- const parent = {
82
+ const parent = new ObjectMap({
77
83
  "index.ori": "Index page",
78
- };
84
+ });
79
85
  await assertError(
80
86
  `index.orj(1, 2, 3)`,
81
87
  `ReferenceError: Couldn't find the function or map to execute.
@@ -170,11 +176,14 @@ evaluating: \x1B[31mposts.md\x1B[0m
170
176
  });
171
177
 
172
178
  test("handles a traversal failure inside a reference error", async () => {
173
- const parent = {
174
- post1: {
175
- title: "First post",
179
+ const parent = new ObjectMap(
180
+ {
181
+ post1: {
182
+ title: "First post",
183
+ },
176
184
  },
177
- };
185
+ { deep: true },
186
+ );
178
187
  await assertError(
179
188
  `(post1/totle).toUpperCase()`,
180
189
  `ReferenceError: Tried to get a property of something that doesn't exist.
@@ -197,16 +206,19 @@ evaluating: \x1B[31mrepeat\x1B[0m`,
197
206
 
198
207
  describe("TraverseError", () => {
199
208
  test("suggests typos for failed path", async () => {
200
- const parent = {
201
- a: {
202
- b: {
203
- sub: {
204
- // need 2+ characters for typos
205
- c: 1,
209
+ const parent = new ObjectMap(
210
+ {
211
+ a: {
212
+ b: {
213
+ sub: {
214
+ // need 2+ characters for typos
215
+ c: 1,
216
+ },
206
217
  },
207
218
  },
208
219
  },
209
- };
220
+ { deep: true },
221
+ );
210
222
  await assertError(
211
223
  `a/b/sup/c`,
212
224
  `TraverseError: A path hit a null or undefined value.
@@ -219,9 +231,7 @@ evaluating: \x1B[31msup/\x1B[0m`,
219
231
  });
220
232
 
221
233
  test("identifies when a numeric key failed", async () => {
222
- const parent = {
223
- map: new Map([[1, new Map([["a", true]])]]),
224
- };
234
+ const parent = new Map([["map", new Map([[1, new Map([["a", true]])]])]]);
225
235
  await assertError(
226
236
  `map/1/a`,
227
237
  `TraverseError: A path hit a null or undefined value.
@@ -235,12 +245,12 @@ evaluating: \x1B[31m1/\x1B[0m`,
235
245
  });
236
246
 
237
247
  test("identifies when a value couldn't be unpacked due to a missing extension handler", async () => {
238
- const parent = {
248
+ const parent = new ObjectMap({
239
249
  "file.foo": Uint8Array.from("hello"),
240
- };
250
+ });
241
251
  await assertError(
242
252
  `file.foo/bar`,
243
- `TraverseError: A path hit binary file data that can't be unpacked.
253
+ `TraverseError: A path hit binary data that can't be unpacked.
244
254
  Tried to traverse path: file.foo/bar
245
255
  Stopped unexpectedly at: file.foo/
246
256
  The value couldn't be unpacked because no file extension handler is registered for ".foo".
@@ -250,11 +260,14 @@ evaluating: \x1B[31mfile.foo/\x1B[0m`,
250
260
  });
251
261
 
252
262
  test("identifies when data was already unpacked", async () => {
253
- const parent = {
254
- a: {
255
- "b.json": 1,
263
+ const parent = new ObjectMap(
264
+ {
265
+ a: {
266
+ "b.json": 1,
267
+ },
256
268
  },
257
- };
269
+ { deep: true },
270
+ );
258
271
  await assertError(
259
272
  `a/b.json/`,
260
273
  `TraverseError: A path tried to unpack data that's already unpacked.
@@ -267,9 +280,12 @@ evaluating: \x1B[31mb.json/\x1B[0m`,
267
280
  });
268
281
 
269
282
  test("identifies when a value didn't exist to be unpacked", async () => {
270
- const parent = {
271
- a: {},
272
- };
283
+ const parent = new ObjectMap(
284
+ {
285
+ a: {},
286
+ },
287
+ { deep: true },
288
+ );
273
289
  await assertError(
274
290
  `a/b/`,
275
291
  `TraverseError: A path tried to unpack a value that doesn't exist.
@@ -282,12 +298,19 @@ evaluating: \x1B[31mb/\x1B[0m`,
282
298
  });
283
299
  });
284
300
 
285
- async function assertError(source, expectedMessage, options) {
301
+ async function assertError(expression, expectedMessage, options) {
286
302
  try {
303
+ const source =
304
+ typeof expression !== "string"
305
+ ? expression
306
+ : {
307
+ text: expression,
308
+ relativePath: "test.ori",
309
+ };
287
310
  await evaluate(source, {
288
311
  globals,
289
312
  object: null,
290
- parent: {},
313
+ parent: null,
291
314
  stack: [],
292
315
  ...options,
293
316
  });
@@ -1,7 +1,8 @@
1
- import { Tree } from "@weborigami/async-tree";
1
+ import { ObjectMap, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
- import { describe, test } from "node:test";
3
+ import { beforeEach, describe, test } from "node:test";
4
4
  import evaluate from "../../src/runtime/evaluate.js";
5
+ import systemCache from "../../src/runtime/systemCache.js";
5
6
 
6
7
  const globals = {
7
8
  concat: (...args) => args.join(""),
@@ -10,6 +11,10 @@ const globals = {
10
11
  };
11
12
 
12
13
  describe("evaluate", () => {
14
+ beforeEach(() => {
15
+ systemCache.clear();
16
+ });
17
+
13
18
  test("array", async () => {
14
19
  await assertEvaluation("[]", []);
15
20
  await assertEvaluation("[ 1, 2, 3, ]", [1, 2, 3]);
@@ -31,9 +36,9 @@ describe("evaluate", () => {
31
36
 
32
37
  test("angle bracket path", async () => {
33
38
  await assertEvaluation("<data>", "Bob", {
34
- parent: {
39
+ parent: new ObjectMap({
35
40
  data: "Bob",
36
- },
41
+ }),
37
42
  });
38
43
  });
39
44
 
@@ -1,11 +1,16 @@
1
1
  import assert from "node:assert";
2
- import { describe, test } from "node:test";
2
+ import { beforeEach, describe, test } from "node:test";
3
3
 
4
4
  import { SyncMap } from "@weborigami/async-tree";
5
5
  import execute from "../../src/runtime/execute.js";
6
+ import systemCache from "../../src/runtime/systemCache.js";
6
7
  import { createCode } from "../compiler/codeHelpers.js";
7
8
 
8
9
  describe("execute", () => {
10
+ beforeEach(() => {
11
+ systemCache.clear();
12
+ });
13
+
9
14
  test("if object in function position isn't a function, can unpack it", async () => {
10
15
  const fn = (...args) => args.join(",");
11
16
  const packed = new String();
@@ -1,35 +1,59 @@
1
1
  import { ObjectMap, symbols, SyncMap, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
- import { describe, test } from "node:test";
3
+ import { beforeEach, describe, test } from "node:test";
4
4
 
5
5
  import expressionObject from "../../src/runtime/expressionObject.js";
6
6
  import { ops } from "../../src/runtime/internal.js";
7
+ import { cachePathSymbol } from "../../src/runtime/symbols.js";
8
+ import systemCache from "../../src/runtime/systemCache.js";
7
9
 
8
10
  describe("expressionObject", () => {
11
+ beforeEach(() => {
12
+ systemCache.clear();
13
+ });
14
+
9
15
  test("can instantiate an object", async () => {
10
- const container = new ObjectMap({
16
+ const parent = new ObjectMap({
11
17
  upper: (s) => s.toUpperCase(),
12
18
  });
13
19
 
14
20
  const entries = [
15
- ["hello", [[[ops.scope, container], "upper"], "hello"]],
16
- ["world", [[[ops.scope, container], "upper"], "world"]],
21
+ ["hello", [[[ops.scope], "upper"], "hello"]],
22
+ ["world", [[[ops.scope], "upper"], "world"]],
17
23
  ];
18
24
  const context = new SyncMap();
19
25
 
20
- const object = await expressionObject(entries, { object: context });
26
+ const object = await expressionObject(entries, {
27
+ object: context,
28
+ parent,
29
+ });
21
30
  assert.equal(await object.hello, "HELLO");
22
31
  assert.equal(await object.world, "WORLD");
23
32
  assert.equal(object[symbols.parent], context);
24
33
  });
25
34
 
26
- test("can define a property getter", async () => {
35
+ test("static property", async () => {
36
+ let count = 0;
37
+ const increment = () => count++;
38
+ const entries = [["count", [increment]]];
39
+ const object = await expressionObject(entries);
40
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(object, "count");
41
+ assert.equal(propertyDescriptor?.value, 0);
42
+ assert.equal(object.count, 0);
43
+ assert.equal(count, 1); // getter should have been called
44
+ });
45
+
46
+ test("with caching on, property getter uses system cache", async () => {
27
47
  let count = 0;
28
48
  const increment = () => count++;
29
49
  const entries = [["count", [ops.getter, [increment]]]];
30
50
  const object = await expressionObject(entries);
51
+ object[cachePathSymbol] = "foo.ori/"; // enable caching on this object tree
31
52
  assert.equal(await object.count, 0);
32
- assert.equal(await object.count, 1);
53
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(object, "count");
54
+ assert(propertyDescriptor?.get); // should still be a getter
55
+ assert.equal(count, 1); // getter should have been called
56
+ assert.equal(await object.count, 0); // getter result should be cached
33
57
  });
34
58
 
35
59
  test("treats a getter for a primitive value as a regular property", async () => {
@@ -75,10 +99,16 @@ describe("expressionObject", () => {
75
99
  });
76
100
  });
77
101
 
78
- test.only("returned object values can be unpacked", async () => {
102
+ test("returned object values can be unpacked", async () => {
79
103
  const entries = [["data.json", `{ "a": 1 }`]];
80
104
  const context = new SyncMap();
81
- const result = await expressionObject(entries, { object: context });
105
+ const globals = {
106
+ json_handler: { unpack: (data) => JSON.parse(data) },
107
+ };
108
+ const result = await expressionObject(entries, {
109
+ object: context,
110
+ globals,
111
+ });
82
112
  const dataJson = await result["data.json"];
83
113
  const json = await dataJson.unpack();
84
114
  assert.deepEqual(json, { a: 1 });
@@ -115,11 +145,21 @@ describe("expressionObject", () => {
115
145
  ]);
116
146
  });
117
147
 
118
- // test("sets symbols.async on objects with getters", async () => {
119
- // const noGetter = await expressionObject([["eager", 1]]);
120
- // assert.equal(noGetter[symbols.async], undefined);
148
+ test("tracks dependencies on upstream values", async () => {
149
+ systemCache.clear();
121
150
 
122
- // const hasGetter = await expressionObject([["lazy", [ops.getter, [2]]]]);
123
- // assert.equal(hasGetter[symbols.async], true);
124
- // });
151
+ // The `number` property gets a value from the cache
152
+ const getNumber = () =>
153
+ systemCache.getOrInsertComputed("dependency", () => 1);
154
+ const entries = [["number", [ops.getter, [getNumber]]]];
155
+ const object = await expressionObject(entries);
156
+ object[cachePathSymbol] = "src/test.ori/"; // enable caching on this object tree
157
+ const number = await object.number;
158
+ assert.equal(number, 1);
159
+
160
+ const dependencyEntry = systemCache.get("dependency");
161
+ const objectEntry = systemCache.get("src/test.ori/number");
162
+ assert(dependencyEntry.downstreams.has("src/test.ori/number"));
163
+ assert(objectEntry.upstreams.has("dependency"));
164
+ });
125
165
  });
@@ -0,0 +1,24 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import json_handler from "../../src/handlers/json_handler.js";
4
+ import fetchAndHandleExtension from "../../src/protocols/fetchAndHandleExtension.js";
5
+
6
+ describe("fetchAndHandleExtension", () => {
7
+ test("can unpack based on MIME content type", async () => {
8
+ /** @type {any} */
9
+ const parent = new Map();
10
+ parent.globals = {
11
+ json_handler,
12
+ };
13
+ const buffer = await fetchAndHandleExtension(
14
+ "https://weborigami.org/samples/help/pet.json",
15
+ null,
16
+ {
17
+ parent,
18
+ },
19
+ );
20
+ // @ts-ignore
21
+ const data = await buffer.unpack();
22
+ assert.strictEqual(data.name, "Fluffy");
23
+ });
24
+ });
@@ -0,0 +1 @@
1
+ "Hello"
@@ -1,7 +1,15 @@
1
1
  import { ObjectMap } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
+ import * as handlers from "../../src/handlers/handlers.js";
4
5
  import handleExtension from "../../src/runtime/handleExtension.js";
6
+ import OrigamiFileMap from "../../src/runtime/OrigamiFileMap.js";
7
+ import { cachePathSymbol } from "../../src/runtime/symbols.js";
8
+
9
+ const fixturesUrl = new URL("fixtures/unpack", import.meta.url);
10
+ const fixtureFiles = new OrigamiFileMap(fixturesUrl);
11
+ fixtureFiles[cachePathSymbol] = "fixtures";
12
+ fixtureFiles.globals = handlers;
5
13
 
6
14
  describe("handleExtension", () => {
7
15
  test("attaches an unpack method to a value with an extension", async () => {
@@ -10,7 +18,10 @@ describe("handleExtension", () => {
10
18
  assert(typeof numberValue === "number");
11
19
  assert.equal(numberValue, 1);
12
20
  const jsonFile = await fixture.get("bar.json");
13
- const withHandler = await handleExtension(jsonFile, "bar.json", fixture);
21
+ const globals = {
22
+ json_handler: { unpack: async (data) => JSON.parse(data) },
23
+ };
24
+ const withHandler = handleExtension(jsonFile, "bar.json", globals, fixture);
14
25
  assert.equal(String(withHandler), `{ "bar": 2 }`);
15
26
  const data = await withHandler.unpack();
16
27
  assert.deepEqual(data, { bar: 2 });
@@ -1,12 +1,17 @@
1
1
  import { ObjectMap, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
- import { describe, test } from "node:test";
3
+ import { beforeEach, describe, test } from "node:test";
4
4
 
5
5
  import execute from "../../src/runtime/execute.js";
6
6
  import { ops } from "../../src/runtime/internal.js";
7
+ import systemCache from "../../src/runtime/systemCache.js";
7
8
  import { createCode } from "../compiler/codeHelpers.js";
8
9
 
9
10
  describe("ops", () => {
11
+ beforeEach(() => {
12
+ systemCache.clear();
13
+ });
14
+
10
15
  test("ops.addition adds two numbers", async () => {
11
16
  assert.strictEqual(ops.addition(2, 2), 4);
12
17
  assert.strictEqual(ops.addition(2, true), 3);
@@ -49,6 +54,15 @@ describe("ops", () => {
49
54
  assert.strictEqual(ops.bitwiseXor(5, 3), 6);
50
55
  });
51
56
 
57
+ test("ops.cache", async () => {
58
+ const fn = () => 1;
59
+ const code = createCode([ops.object, ["a", [ops.getter, [fn]]]]);
60
+ const result = await ops.cache("a.ori/_refs/b.ori/", code, {});
61
+ assert.deepEqual(await Tree.plain(result), { a: 1 });
62
+ const cachedResult = await systemCache.get("a.ori/_refs/b.ori/");
63
+ assert.strictEqual(await cachedResult.value.a, 1);
64
+ });
65
+
52
66
  test("ops.comma returns the last value", async () => {
53
67
  const code = createCode([ops.comma, 1, 2, 3]);
54
68
  const result = await execute(code);
@@ -70,17 +84,17 @@ describe("ops", () => {
70
84
  });
71
85
 
72
86
  test("ops.deepText concatenates tree value text", async () => {
73
- const container = {
87
+ const parent = new ObjectMap({
74
88
  name: "world",
75
- };
89
+ });
76
90
  const code = createCode([
77
91
  ops.deepText,
78
92
  "Hello, ",
79
- [[ops.scope, container], "name"],
93
+ [[ops.scope], "name"],
80
94
  ".",
81
95
  ]);
82
96
 
83
- const result = await execute(code);
97
+ const result = await execute(code, { parent });
84
98
  assert.strictEqual(result, "Hello, world.");
85
99
  });
86
100
 
@@ -106,27 +120,22 @@ describe("ops", () => {
106
120
 
107
121
  test("ops.cache evaluates code and cache its result", async () => {
108
122
  let count = 0;
109
- const container = {
123
+ const parent = new ObjectMap({
110
124
  group: {
111
125
  get count() {
112
126
  // Use promise to test async behavior
113
127
  return Promise.resolve(++count);
114
128
  },
115
129
  },
116
- };
130
+ });
117
131
  const code = createCode([
118
132
  ops.cache,
119
- {},
120
- "group/count",
121
- [
122
- [ops.scope, container],
123
- [ops.literal, "group"],
124
- [ops.literal, "count"],
125
- ],
133
+ "_refs/test.ori/group/count",
134
+ [[ops.scope], [ops.literal, "group"], [ops.literal, "count"]],
126
135
  ]);
127
- const result = await execute(code);
136
+ const result = await execute(code, { parent });
128
137
  assert.strictEqual(result, 1);
129
- const result2 = await execute(code);
138
+ const result2 = await execute(code, { parent });
130
139
  assert.strictEqual(result2, 1);
131
140
  });
132
141
 
@@ -146,6 +155,7 @@ describe("ops", () => {
146
155
  describe("ops.flat", () => {
147
156
  test("flattens arrays", async () => {
148
157
  assert.deepEqual(await ops.flat(1, 2, [3]), [1, 2, 3]);
158
+ assert.deepEqual(await ops.flat([1], [2], [[3]]), [1, 2, [3]]);
149
159
  });
150
160
 
151
161
  test("flattens maplike objects", async () => {
@@ -171,6 +181,15 @@ describe("ops", () => {
171
181
  3: 8,
172
182
  });
173
183
  });
184
+
185
+ test("flattens arrays of objects", async () => {
186
+ const result = await ops.flat([{ a: 1 }], [{ b: 2 }, { c: 3 }]);
187
+ assert.deepEqual(await Tree.plain(result), [
188
+ { a: 1 },
189
+ { b: 2 },
190
+ { c: 3 },
191
+ ]);
192
+ });
174
193
  });
175
194
 
176
195
  test("ops.greaterThan", () => {
@@ -225,18 +244,18 @@ describe("ops", () => {
225
244
  });
226
245
 
227
246
  test("ops.lambda defines a function with underscore input", async () => {
228
- const container = {
247
+ const parent = new ObjectMap({
229
248
  message: "Hello",
230
- };
249
+ });
231
250
 
232
251
  const code = createCode([
233
252
  ops.lambda,
234
253
  1,
235
254
  [["_", [[ops.params, 0], 0]]],
236
- [[ops.scope, container], "message"],
255
+ [[ops.scope], "message"],
237
256
  ]);
238
257
 
239
- const fn = await execute(code);
258
+ const fn = await execute(code, { parent });
240
259
  assert.equal(fn.length, 1);
241
260
  const result = await fn();
242
261
  assert.strictEqual(result, "Hello");
@@ -308,9 +327,9 @@ describe("ops", () => {
308
327
  // ...more
309
328
  // c: a
310
329
  // }
311
- const container = {
330
+ const parent = new ObjectMap({
312
331
  more: { b: 2 },
313
- };
332
+ });
314
333
  const code = createCode([
315
334
  [
316
335
  ops.object,
@@ -321,14 +340,14 @@ describe("ops", () => {
321
340
  [
322
341
  ops.merge,
323
342
  [ops.object, ["a", [ops.getter, [[ops.inherited, 1], "a"]]]],
324
- [[ops.scope, container], "more"],
343
+ [[ops.scope], "more"],
325
344
  [ops.object, ["c", [ops.getter, [[ops.inherited, 1], "c"]]]],
326
345
  ],
327
346
  ],
328
347
  ],
329
348
  "_result",
330
349
  ]);
331
- const result = await execute(code);
350
+ const result = await execute(code, { parent });
332
351
  assert.deepEqual(await Tree.plain(result), { a: 1, b: 2, c: 1 });
333
352
  });
334
353
 
@@ -339,31 +358,6 @@ describe("ops", () => {
339
358
  assert.strictEqual(ops.multiplication("foo", 2), NaN);
340
359
  });
341
360
 
342
- test("ops.objectRest returns an object without specified keys", async () => {
343
- const obj = {
344
- a: 1,
345
- b: 2,
346
- c: 3,
347
- };
348
- const result = await ops.objectRest(obj, ["a", "b"]);
349
- assert.deepEqual(result, { c: 3 });
350
- });
351
-
352
- test("ops.optional", async () => {
353
- assert.equal(
354
- ops.optional(null, (x) => x.a),
355
- undefined,
356
- );
357
- assert.equal(
358
- ops.optional(undefined, (x) => x.a),
359
- undefined,
360
- );
361
- assert.equal(
362
- ops.optional({ a: 1 }, (x) => x.a),
363
- 1,
364
- );
365
- });
366
-
367
361
  test("ops.notEqual", () => {
368
362
  assert(!ops.notEqual(1, 1));
369
363
  assert(ops.notEqual(1, 2));
@@ -390,33 +384,44 @@ describe("ops", () => {
390
384
  });
391
385
 
392
386
  test("ops.object instantiates an object", async () => {
393
- const container = {
387
+ const parent = new ObjectMap({
394
388
  upper: (s) => s.toUpperCase(),
395
- };
389
+ });
396
390
 
397
391
  const code = createCode([
398
392
  ops.object,
399
- ["hello", [[[ops.scope, container], "upper"], "hello"]],
400
- ["world", [[[ops.scope, container], "upper"], "world"]],
393
+ ["hello", [[[ops.scope], "upper"], "hello"]],
394
+ ["world", [[[ops.scope], "upper"], "world"]],
401
395
  ]);
402
396
 
403
- const result = await execute(code);
397
+ const result = await execute(code, { parent });
404
398
  assert.strictEqual(result.hello, "HELLO");
405
399
  assert.strictEqual(result.world, "WORLD");
406
400
  });
407
401
 
408
- test("ops.object instantiates an array", async () => {
409
- const container = {
410
- upper: (s) => s.toUpperCase(),
402
+ test("ops.objectRest returns an object without specified keys", async () => {
403
+ const obj = {
404
+ a: 1,
405
+ b: 2,
406
+ c: 3,
411
407
  };
412
- const code = createCode([
413
- ops.array,
414
- "Hello",
408
+ const result = await ops.objectRest(obj, ["a", "b"]);
409
+ assert.deepEqual(result, { c: 3 });
410
+ });
411
+
412
+ test("ops.optional", async () => {
413
+ assert.equal(
414
+ ops.optional(null, (x) => x.a),
415
+ undefined,
416
+ );
417
+ assert.equal(
418
+ ops.optional(undefined, (x) => x.a),
419
+ undefined,
420
+ );
421
+ assert.equal(
422
+ ops.optional({ a: 1 }, (x) => x.a),
415
423
  1,
416
- [[[ops.scope, container], "upper"], "world"],
417
- ]);
418
- const result = await execute(code);
419
- assert.deepEqual(result, ["Hello", 1, "WORLD"]);
424
+ );
420
425
  });
421
426
 
422
427
  test("ops.params returns a stack frame", async () => {
@@ -455,7 +460,7 @@ describe("ops", () => {
455
460
  );
456
461
  const a = await tree.get("a");
457
462
  const b = await a.get("b");
458
- const scope = await ops.scope(b);
463
+ const scope = await ops.scope({ parent: b });
459
464
  assert.equal(await scope?.get("c"), 1);
460
465
  });
461
466
  });
@@ -0,0 +1,27 @@
1
+ import { SyncMap } from "@weborigami/async-tree";
2
+ import SyncCacheTransform from "../../src/runtime/SyncCacheTransform.js";
3
+
4
+ export default function syncCalcs(iterable) {
5
+ const data = new (SyncCacheTransform(SyncMap))(iterable);
6
+ const calcs = new (SyncCacheTransform(SyncResultsMap))(data);
7
+ return { calcs, data };
8
+ }
9
+
10
+ class SyncResultsMap extends SyncMap {
11
+ constructor(source) {
12
+ super();
13
+ this.source = source;
14
+ }
15
+
16
+ get(key) {
17
+ let value = this.source.get(key);
18
+ if (typeof value === "function") {
19
+ value = value();
20
+ }
21
+ return value;
22
+ }
23
+
24
+ keys() {
25
+ return this.source.keys();
26
+ }
27
+ }