@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.
- package/index.ts +1 -0
- package/main.js +7 -1
- package/package.json +7 -6
- package/src/compiler/compile.js +10 -3
- package/src/compiler/optimize.js +71 -40
- package/src/compiler/parse.js +1 -1
- package/src/compiler/parserHelpers.js +5 -3
- package/src/handlers/addExtensionKeyFn.js +18 -0
- package/src/handlers/epub_handler.js +54 -0
- package/src/handlers/getSource.js +11 -0
- package/src/handlers/handlers.js +2 -0
- package/src/handlers/htm_handler.js +1 -1
- package/src/handlers/js_handler.js +13 -4
- package/src/handlers/mediaTypeExtensions.json +15 -0
- package/src/handlers/ori_handler.js +8 -7
- package/src/handlers/oridocument_handler.js +19 -28
- package/src/handlers/processOriExport.js +17 -0
- package/src/handlers/tsv_handler.js +1 -1
- package/src/handlers/txt_handler.js +4 -2
- package/src/handlers/xhtml_handler.js +1 -1
- package/src/handlers/yaml_handler.js +6 -3
- package/src/handlers/zip_handler.js +112 -0
- package/src/project/activeProjectRoot.js +9 -0
- package/src/project/getGlobalsForTree.js +5 -0
- package/src/project/{projectGlobals.js → initializeGlobalsForTree.js} +8 -13
- package/src/project/jsGlobals.js +1 -0
- package/src/project/projectConfig.js +2 -2
- package/src/project/projectRootFromPath.js +2 -0
- package/src/protocols/constructHref.js +3 -3
- package/src/protocols/constructSiteTree.js +11 -2
- package/src/protocols/explore.js +1 -1
- package/src/protocols/explorehttp.js +1 -1
- package/src/protocols/fetchAndHandleExtension.js +23 -11
- package/src/protocols/files.js +1 -0
- package/src/protocols/http.js +4 -1
- package/src/protocols/https.js +4 -1
- package/src/protocols/httpstree.js +1 -1
- package/src/protocols/httptree.js +1 -1
- package/src/protocols/package.js +15 -3
- package/src/runtime/AsyncCacheTransform.d.ts +5 -0
- package/src/runtime/AsyncCacheTransform.js +134 -0
- package/src/runtime/HandleExtensionsTransform.d.ts +3 -1
- package/src/runtime/HandleExtensionsTransform.js +18 -2
- package/src/runtime/OrigamiFileMap.d.ts +5 -2
- package/src/runtime/OrigamiFileMap.js +27 -4
- package/src/runtime/ScopeMap.js +72 -0
- package/src/runtime/SyncCacheTransform.d.ts +8 -0
- package/src/runtime/SyncCacheTransform.js +133 -0
- package/src/runtime/SystemCacheMap.js +259 -0
- package/src/runtime/WatchFilesMixin.js +52 -19
- package/src/runtime/enableValueCaching.js +192 -0
- package/src/runtime/execute.js +2 -2
- package/src/runtime/executionContext.js +7 -0
- package/src/runtime/explainReferenceError.js +7 -2
- package/src/runtime/expressionObject.js +54 -46
- package/src/runtime/handleExtension.js +65 -34
- package/src/runtime/interop.js +2 -2
- package/src/runtime/mergeTrees.js +1 -1
- package/src/runtime/ops.js +28 -33
- package/src/runtime/symbols.js +3 -0
- package/src/runtime/systemCache.js +3 -0
- package/src/runtime/volatile.js +14 -0
- package/test/compiler/codeHelpers.js +2 -1
- package/test/compiler/optimize.test.js +62 -54
- package/test/handlers/epub_handler.test.js +27 -0
- package/test/handlers/fixtures/test.zip +0 -0
- package/test/handlers/ori_handler.test.js +22 -3
- package/test/handlers/oridocument_handler.test.js +1 -1
- package/test/handlers/zip_handler.test.js +45 -0
- package/test/protocols/https.test.js +19 -0
- package/test/protocols/package.test.js +7 -2
- package/test/runtime/AsyncCacheTransform.test.js +91 -0
- package/test/runtime/OrigamiFileMap.test.js +26 -23
- package/test/runtime/ScopeMap.test.js +49 -0
- package/test/runtime/SyncCacheTransform.test.js +93 -0
- package/test/runtime/SystemCacheMap.test.js +239 -0
- package/test/runtime/asyncCalcs.js +28 -0
- package/test/runtime/enableValueCaching.test.js +55 -0
- package/test/runtime/errors.test.js +53 -30
- package/test/runtime/evaluate.test.js +9 -4
- package/test/runtime/execute.test.js +6 -1
- package/test/runtime/expressionObject.test.js +55 -15
- package/test/runtime/fetchAndHandleExtension.test.js +24 -0
- package/test/runtime/fixtures/unpack/hello.json +1 -0
- package/test/runtime/handleExtension.test.js +12 -1
- package/test/runtime/ops.test.js +70 -65
- package/test/runtime/syncCalcs.js +27 -0
- package/test/runtime/systemCache.test.js +66 -0
- package/src/runtime/assignPropertyDescriptors.js +0 -23
- 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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
16
|
+
const parent = new ObjectMap({
|
|
11
17
|
upper: (s) => s.toUpperCase(),
|
|
12
18
|
});
|
|
13
19
|
|
|
14
20
|
const entries = [
|
|
15
|
-
["hello", [[[ops.scope
|
|
16
|
-
["world", [[[ops.scope
|
|
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, {
|
|
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("
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
// assert.equal(noGetter[symbols.async], undefined);
|
|
148
|
+
test("tracks dependencies on upstream values", async () => {
|
|
149
|
+
systemCache.clear();
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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 });
|
package/test/runtime/ops.test.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
400
|
-
["world", [[[ops.scope
|
|
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.
|
|
409
|
-
const
|
|
410
|
-
|
|
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
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
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
|
+
}
|