@weborigami/language 0.6.17 → 0.7.0-beta.1

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 (83) hide show
  1. package/index.ts +1 -0
  2. package/main.js +7 -1
  3. package/package.json +6 -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/getSource.js +11 -0
  9. package/src/handlers/htm_handler.js +1 -1
  10. package/src/handlers/js_handler.js +13 -4
  11. package/src/handlers/mediaTypeExtensions.json +15 -0
  12. package/src/handlers/ori_handler.js +8 -7
  13. package/src/handlers/oridocument_handler.js +17 -10
  14. package/src/handlers/processOriExport.js +17 -0
  15. package/src/handlers/tsv_handler.js +1 -1
  16. package/src/handlers/txt_handler.js +4 -2
  17. package/src/handlers/xhtml_handler.js +1 -1
  18. package/src/handlers/yaml_handler.js +6 -3
  19. package/src/project/activeProjectRoot.js +9 -0
  20. package/src/project/getGlobalsForTree.js +5 -0
  21. package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
  22. package/src/project/jsGlobals.js +1 -0
  23. package/src/project/projectConfig.js +2 -2
  24. package/src/project/projectRootFromPath.js +2 -0
  25. package/src/protocols/constructHref.js +3 -3
  26. package/src/protocols/constructSiteTree.js +11 -2
  27. package/src/protocols/explore.js +1 -1
  28. package/src/protocols/explorehttp.js +1 -1
  29. package/src/protocols/fetchAndHandleExtension.js +23 -11
  30. package/src/protocols/files.js +1 -0
  31. package/src/protocols/http.js +4 -1
  32. package/src/protocols/https.js +4 -1
  33. package/src/protocols/httpstree.js +1 -1
  34. package/src/protocols/httptree.js +1 -1
  35. package/src/protocols/package.js +15 -3
  36. package/src/runtime/AsyncCacheTransform.d.ts +5 -0
  37. package/src/runtime/AsyncCacheTransform.js +134 -0
  38. package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
  39. package/src/runtime/HandleExtensionsTransform.js +18 -2
  40. package/src/runtime/OrigamiFileMap.d.ts +5 -2
  41. package/src/runtime/OrigamiFileMap.js +27 -4
  42. package/src/runtime/ScopeMap.js +72 -0
  43. package/src/runtime/SyncCacheTransform.d.ts +8 -0
  44. package/src/runtime/SyncCacheTransform.js +133 -0
  45. package/src/runtime/SystemCacheMap.js +259 -0
  46. package/src/runtime/WatchFilesMixin.js +52 -19
  47. package/src/runtime/enableValueCaching.js +192 -0
  48. package/src/runtime/execute.js +2 -2
  49. package/src/runtime/executionContext.js +7 -0
  50. package/src/runtime/explainReferenceError.js +7 -2
  51. package/src/runtime/expressionObject.js +54 -46
  52. package/src/runtime/handleExtension.js +65 -34
  53. package/src/runtime/interop.js +2 -2
  54. package/src/runtime/mergeTrees.js +1 -1
  55. package/src/runtime/ops.js +28 -33
  56. package/src/runtime/symbols.js +3 -0
  57. package/src/runtime/systemCache.js +3 -0
  58. package/src/runtime/volatile.js +14 -0
  59. package/test/compiler/codeHelpers.js +2 -1
  60. package/test/compiler/optimize.test.js +62 -54
  61. package/test/handlers/ori_handler.test.js +22 -3
  62. package/test/handlers/oridocument_handler.test.js +1 -1
  63. package/test/protocols/https.test.js +19 -0
  64. package/test/protocols/package.test.js +7 -2
  65. package/test/runtime/AsyncCacheTransform.test.js +91 -0
  66. package/test/runtime/OrigamiFileMap.test.js +26 -23
  67. package/test/runtime/ScopeMap.test.js +49 -0
  68. package/test/runtime/SyncCacheTransform.test.js +93 -0
  69. package/test/runtime/SystemCacheMap.test.js +239 -0
  70. package/test/runtime/asyncCalcs.js +28 -0
  71. package/test/runtime/enableValueCaching.test.js +55 -0
  72. package/test/runtime/errors.test.js +53 -30
  73. package/test/runtime/evaluate.test.js +9 -4
  74. package/test/runtime/execute.test.js +6 -1
  75. package/test/runtime/expressionObject.test.js +55 -15
  76. package/test/runtime/fetchAndHandleExtension.test.js +24 -0
  77. package/test/runtime/fixtures/unpack/hello.json +1 -0
  78. package/test/runtime/handleExtension.test.js +12 -1
  79. package/test/runtime/ops.test.js +70 -65
  80. package/test/runtime/syncCalcs.js +27 -0
  81. package/test/runtime/systemCache.test.js +66 -0
  82. package/src/runtime/assignPropertyDescriptors.js +0 -23
  83. package/src/runtime/asyncStorage.js +0 -7
@@ -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
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Integration tests of several forms of caching using the system cache
3
+ */
4
+
5
+ import { SyncMap, Tree } from "@weborigami/async-tree";
6
+ import assert from "node:assert";
7
+ import { describe, test } from "node:test";
8
+ import * as handlers from "../../src/handlers/handlers.js";
9
+ import HandleExtensionsTransform from "../../src/runtime/HandleExtensionsTransform.js";
10
+ import SyncCacheTransform from "../../src/runtime/SyncCacheTransform.js";
11
+ import { cachePathSymbol } from "../../src/runtime/symbols.js";
12
+
13
+ describe("systemCache", () => {
14
+ test("property based on external scope recalculates when scope changes", async () => {
15
+ // Virtual src folder
16
+ const src = new OrigamiSyncMap([
17
+ [
18
+ "site.ori",
19
+ `
20
+ {
21
+ value = data.json/
22
+ }
23
+ `,
24
+ ],
25
+ ]);
26
+
27
+ // Virtual project root folder
28
+ const project = new OrigamiSyncMap([
29
+ ["data.json", "1"],
30
+ ["src", src],
31
+ ]);
32
+
33
+ // Make src a child of the project root
34
+ src.parent = project;
35
+
36
+ // Paths are optional but make cache keys more meaningful
37
+ src[cachePathSymbol] = "project/src";
38
+ project[cachePathSymbol] = "project";
39
+
40
+ // Add handlers so we can unpack values
41
+ // @ts-ignore
42
+ project.globals = handlers;
43
+
44
+ const site = await Tree.traverseOrThrow(project, "src/", "site.ori/");
45
+
46
+ const value1 = await site.value;
47
+ assert.equal(value1, 1);
48
+
49
+ // Add new data.json to src folder, overriding the one in project root
50
+ src.set("data.json", "2");
51
+
52
+ const value2 = await site.value;
53
+ assert.equal(value2, 2);
54
+
55
+ // Delete data.json from src folder, reverting to the one in project root
56
+ src.delete("data.json");
57
+
58
+ const value3 = await site.value;
59
+ assert.equal(value3, 1);
60
+ });
61
+ });
62
+
63
+ // Like OrigamiFileMap, but in memory
64
+ class OrigamiSyncMap extends SyncCacheTransform(
65
+ HandleExtensionsTransform(SyncMap),
66
+ ) {}
@@ -1,23 +0,0 @@
1
- /**
2
- * This is an analogue of Object.assign that destructively copies properties to
3
- * a target object -- but avoids invoking property getters. Instead, it copies
4
- * property descriptors over to the target object.
5
- *
6
- * @param {any} target
7
- * @param {...any} sources
8
- */
9
- export default function assignPropertyDescriptors(target, ...sources) {
10
- for (const source of sources) {
11
- const descriptors = Object.getOwnPropertyDescriptors(source);
12
- for (const [key, descriptor] of Object.entries(descriptors)) {
13
- if (descriptor.value !== undefined) {
14
- // Simple value, copy it
15
- target[key] = descriptor.value;
16
- } else {
17
- // Getter and/or setter, copy the descriptor
18
- Object.defineProperty(target, key, descriptor);
19
- }
20
- }
21
- }
22
- return target;
23
- }
@@ -1,7 +0,0 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
-
3
- /**
4
- * Storage maintained by the execute() function and accessible to async
5
- * functions called during evaluation.
6
- */
7
- export default new AsyncLocalStorage();