@weborigami/language 0.3.3 → 0.3.4-jse.5

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.
@@ -8,7 +8,7 @@ import { createCode } from "../compiler/codeHelpers.js";
8
8
 
9
9
  describe("evaluate", () => {
10
10
  test("can retrieve values from scope", async () => {
11
- const code = createCode([ops.scope, "message"]);
11
+ const code = createCode([[ops.scope], "message"]);
12
12
  const parent = new ObjectTree({
13
13
  message: "Hello",
14
14
  });
@@ -21,8 +21,8 @@ describe("evaluate", () => {
21
21
  test("can invoke functions in scope", async () => {
22
22
  // Match the array representation of code generated by the parser.
23
23
  const code = createCode([
24
- [ops.scope, "greet"],
25
- [ops.scope, "name"],
24
+ [[ops.scope], "greet"],
25
+ [[ops.scope], "name"],
26
26
  ]);
27
27
 
28
28
  const tree = new ObjectTree({
@@ -37,7 +37,7 @@ describe("evaluate", () => {
37
37
  });
38
38
 
39
39
  test("passes context to invoked functions", async () => {
40
- const code = createCode([ops.scope, "fn"]);
40
+ const code = createCode([[ops.scope], "fn"]);
41
41
  const tree = new ObjectTree({
42
42
  async fn() {
43
43
  assert.equal(this, tree);
@@ -46,14 +46,6 @@ describe("evaluate", () => {
46
46
  await evaluate.call(tree, code);
47
47
  });
48
48
 
49
- test("evaluates a function with fixed number of arguments", async () => {
50
- const fn = (x, y) => ({
51
- c: `${x}${y}c`,
52
- });
53
- const code = createCode([ops.traverse, fn, "a", "b", "c"]);
54
- assert.equal(await evaluate.call(null, code), "abc");
55
- });
56
-
57
49
  test("if object in function position isn't a function, can unpack it", async () => {
58
50
  const fn = (...args) => args.join(",");
59
51
  const packed = new String();
@@ -62,12 +54,4 @@ describe("evaluate", () => {
62
54
  const result = await evaluate.call(null, code);
63
55
  assert.equal(result, "a,b,c");
64
56
  });
65
-
66
- test("by defalut sets the parent of a returned tree to the current tree", async () => {
67
- const fn = () => new ObjectTree({});
68
- const code = createCode([fn]);
69
- const tree = new ObjectTree({});
70
- const result = await evaluate.call(tree, code);
71
- assert.equal(result.parent, tree);
72
- });
73
57
  });
@@ -12,8 +12,8 @@ describe("expressionObject", () => {
12
12
  });
13
13
 
14
14
  const entries = [
15
- ["hello", [[ops.scope, "upper"], "hello"]],
16
- ["world", [[ops.scope, "upper"], "world"]],
15
+ ["hello", [[[ops.scope], "upper"], "hello"]],
16
+ ["world", [[[ops.scope], "upper"], "world"]],
17
17
  ];
18
18
 
19
19
  const object = await expressionObject(entries, scope);
@@ -40,7 +40,7 @@ describe("expressionObject", () => {
40
40
  test("can instantiate an Origami tree", async () => {
41
41
  const entries = [
42
42
  ["name", "world"],
43
- ["message", [ops.concat, "Hello, ", [ops.scope, "name"], "!"]],
43
+ ["message", [ops.concat, "Hello, ", [[ops.scope], "name"], "!"]],
44
44
  ];
45
45
  const parent = new ObjectTree({});
46
46
  const object = await expressionObject(entries, parent);
@@ -53,11 +53,12 @@ describe("expressionObject", () => {
53
53
 
54
54
  test("returned object values can be unpacked", async () => {
55
55
  const entries = [["data.json", `{ "a": 1 }`]];
56
- const parent = new ObjectTree({
56
+ const parent = new ObjectTree({});
57
+ parent.handlers = {
57
58
  "json.handler": {
58
59
  unpack: JSON.parse,
59
60
  },
60
- });
61
+ };
61
62
  const result = await expressionObject(entries, parent);
62
63
  const dataJson = await result["data.json"];
63
64
  const json = await dataJson.unpack();
@@ -3,6 +3,12 @@ import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import { handleExtension } from "../../src/runtime/handlers.js";
5
5
 
6
+ const handlers = {
7
+ "json.handler": {
8
+ unpack: (buffer) => JSON.parse(String(buffer)),
9
+ },
10
+ };
11
+
6
12
  describe("handlers", () => {
7
13
  test("attaches an unpack method to a value with an extension", async () => {
8
14
  const fixture = createFixture();
@@ -10,7 +16,12 @@ describe("handlers", () => {
10
16
  assert(typeof numberValue === "number");
11
17
  assert.equal(numberValue, 1);
12
18
  const jsonFile = await fixture.get("bar.json");
13
- const withHandler = await handleExtension(fixture, jsonFile, "bar.json");
19
+ const withHandler = await handleExtension(
20
+ fixture,
21
+ jsonFile,
22
+ "bar.json",
23
+ handlers
24
+ );
14
25
  assert.equal(String(withHandler), `{ "bar": 2 }`);
15
26
  const data = await withHandler.unpack();
16
27
  assert.deepEqual(data, { bar: 2 });
@@ -19,21 +30,19 @@ describe("handlers", () => {
19
30
  test("immediately unpacks if key ends in slash", async () => {
20
31
  const fixture = createFixture();
21
32
  const jsonFile = await fixture.get("bar.json");
22
- const data = await handleExtension(fixture, jsonFile, "bar.json/");
33
+ const data = await handleExtension(
34
+ fixture,
35
+ jsonFile,
36
+ "bar.json/",
37
+ handlers
38
+ );
23
39
  assert.deepEqual(data, { bar: 2 });
24
40
  });
25
41
  });
26
42
 
27
43
  function createFixture() {
28
- const parent = new ObjectTree({
29
- "json.handler": {
30
- unpack: (buffer) => JSON.parse(String(buffer)),
31
- },
32
- });
33
- let tree = new ObjectTree({
44
+ return new ObjectTree({
34
45
  foo: 1, // No extension, should be left alone
35
46
  "bar.json": `{ "bar": 2 }`,
36
47
  });
37
- tree.parent = parent;
38
- return tree;
39
48
  }
@@ -41,11 +41,6 @@ describe("mergeTrees", () => {
41
41
  });
42
42
  });
43
43
 
44
- test("if all arguments are arrays, result is an array", async () => {
45
- const result = await mergeTrees.call(null, [1, 2], [3, 4]);
46
- assert.deepEqual(result, [1, 2, 3, 4]);
47
- });
48
-
49
44
  test("merges heterogenous arguments as trees", async () => {
50
45
  const tree = await mergeTrees.call(
51
46
  null,
@@ -1,4 +1,4 @@
1
- import { DeepObjectTree, ObjectTree } from "@weborigami/async-tree";
1
+ import { DeepObjectTree, ObjectTree, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
 
@@ -42,17 +42,6 @@ describe("ops", () => {
42
42
  assert.strictEqual(ops.bitwiseXor(5, 3), 6);
43
43
  });
44
44
 
45
- test("ops.builtin gets a value from the top of the scope chain", async () => {
46
- const root = new ObjectTree({
47
- a: 1,
48
- });
49
- const tree = new ObjectTree({});
50
- tree.parent = root;
51
- const code = createCode([ops.builtin, "a"]);
52
- const result = await evaluate.call(tree, code);
53
- assert.strictEqual(result, 1);
54
- });
55
-
56
45
  test("ops.comma returns the last value", async () => {
57
46
  const code = createCode([ops.comma, 1, 2, 3]);
58
47
  const result = await evaluate.call(null, code);
@@ -64,7 +53,12 @@ describe("ops", () => {
64
53
  name: "world",
65
54
  });
66
55
 
67
- const code = createCode([ops.concat, "Hello, ", [ops.scope, "name"], "."]);
56
+ const code = createCode([
57
+ ops.concat,
58
+ "Hello, ",
59
+ [[ops.scope], "name"],
60
+ ".",
61
+ ]);
68
62
 
69
63
  const result = await evaluate.call(scope, code);
70
64
  assert.strictEqual(result, "Hello, world.");
@@ -80,27 +74,22 @@ describe("ops", () => {
80
74
  assert.strictEqual(await ops.conditional(false, errorFn, trueFn), true);
81
75
  });
82
76
 
83
- test("ops.documentFunction", async () => {
84
- const code = createCode([
85
- ops.document,
86
- {
87
- a: 1,
77
+ test("ops.construct", async () => {
78
+ assert.equal(await ops.construct(String, "hello"), "hello");
79
+ });
80
+
81
+ test("ops.context", async () => {
82
+ const tree = new DeepObjectTree({
83
+ a: {
84
+ b: {
85
+ c: {},
86
+ },
88
87
  },
89
- [
90
- ops.lambda,
91
- [["_"]],
92
- [
93
- ops.template,
94
- [ops.literal, ["a = ", ""]],
95
- [ops.concat, [ops.scope, "a"]],
96
- ],
97
- ],
98
- ]);
99
- const result = await evaluate.call(null, code);
100
- assert.deepEqual(result, {
101
- a: 1,
102
- "@text": "a = 1",
103
88
  });
89
+ const b = await Tree.traverse(tree, "a", "b");
90
+ const c = await b.get("c");
91
+ assert.equal(ops.context.call(c), c);
92
+ assert.equal(ops.context.call(c, 1), b);
104
93
  });
105
94
 
106
95
  test("ops.division divides two numbers", async () => {
@@ -123,20 +112,21 @@ describe("ops", () => {
123
112
  assert.strictEqual(ops.exponentiation(2, 0), 1);
124
113
  });
125
114
 
126
- test("ops.external evaluates code and cache its result", async () => {
115
+ test("ops.cache evaluates code and cache its result", async () => {
127
116
  let count = 0;
128
117
  const tree = new DeepObjectTree({
129
118
  group: {
130
119
  get count() {
131
- return ++count;
120
+ // Use promise to test async behavior
121
+ return Promise.resolve(++count);
132
122
  },
133
123
  },
134
124
  });
135
125
  const code = createCode([
136
- ops.external,
137
- "group/count",
138
- [ops.traverse, [ops.scope, "group"], [ops.literal, "count"]],
126
+ ops.cache,
139
127
  {},
128
+ "group/count",
129
+ [[ops.scope], [ops.literal, "group"], [ops.literal, "count"]],
140
130
  ]);
141
131
  const result = await evaluate.call(tree, code);
142
132
  assert.strictEqual(result, 1);
@@ -144,6 +134,26 @@ describe("ops", () => {
144
134
  assert.strictEqual(result2, 1);
145
135
  });
146
136
 
137
+ describe("ops.flat", () => {
138
+ test("flattens arrays", async () => {
139
+ assert.deepEqual(await ops.flat(1, 2, [3]), [1, 2, 3]);
140
+ });
141
+
142
+ test("flattens treelike objects", async () => {
143
+ const object = {
144
+ a: 1,
145
+ b: 2,
146
+ };
147
+ const tree = new ObjectTree({
148
+ c: 3,
149
+ d: 4,
150
+ });
151
+ const array = [5, 6];
152
+ const result = await ops.flat(object, tree, array);
153
+ assert.deepEqual(result, [1, 2, 3, 4, 5, 6]);
154
+ });
155
+ });
156
+
147
157
  test("ops.greaterThan", () => {
148
158
  assert(ops.greaterThan(5, 3));
149
159
  assert(!ops.greaterThan(3, 3));
@@ -156,20 +166,6 @@ describe("ops", () => {
156
166
  assert(ops.greaterThanOrEqual("ab", "aa"));
157
167
  });
158
168
 
159
- test("ops.inherited searches inherited scope", async () => {
160
- const parent = new ObjectTree({
161
- a: 1, // This is the inherited value we want
162
- });
163
- /** @type {any} */
164
- const child = new ObjectTree({
165
- a: 2, // Should be ignored
166
- });
167
- child.parent = parent;
168
- const code = createCode([ops.inherited, "a"]);
169
- const result = await evaluate.call(child, code);
170
- assert.strictEqual(result, 1);
171
- });
172
-
173
169
  test("ops.lambda defines a function with no inputs", async () => {
174
170
  const code = createCode([ops.lambda, [], [ops.literal, "result"]]);
175
171
  const fn = await evaluate.call(null, code);
@@ -182,7 +178,7 @@ describe("ops", () => {
182
178
  message: "Hello",
183
179
  });
184
180
 
185
- const code = createCode([ops.lambda, ["_"], [ops.scope, "message"]]);
181
+ const code = createCode([ops.lambda, ["_"], [[ops.scope], "message"]]);
186
182
 
187
183
  const fn = await evaluate.call(scope, code);
188
184
  const result = await fn.call(scope);
@@ -196,7 +192,7 @@ describe("ops", () => {
196
192
  [ops.literal, "a"],
197
193
  [ops.literal, "b"],
198
194
  ],
199
- [ops.concat, [ops.scope, "b"], [ops.scope, "a"]],
195
+ [ops.concat, [[ops.scope], "b"], [[ops.scope], "a"]],
200
196
  ]);
201
197
  const fn = await evaluate.call(null, code);
202
198
  const result = await fn("x", "y");
@@ -250,40 +246,31 @@ describe("ops", () => {
250
246
  test("ops.merge", async () => {
251
247
  // {
252
248
  // a: 1
253
- // …fn(a)
254
- // }
255
- const scope = new ObjectTree({
256
- fn: (a) => ({ b: 2 * a }),
257
- });
258
- const code = createCode([
259
- ops.merge,
260
- [ops.object, ["a", [ops.literal, 1]]],
261
- [
262
- [ops.builtin, "fn"],
263
- [ops.scope, "a"],
264
- ],
265
- ]);
266
- const result = await evaluate.call(scope, code);
267
- assert.deepEqual(result, { a: 1, b: 2 });
268
- });
269
-
270
- test("ops.merge lets all direct properties see each other", async () => {
271
- // {
272
- // a: 1
273
- // ...more
249
+ // …more
274
250
  // c: a
275
251
  // }
276
252
  const scope = new ObjectTree({
277
253
  more: { b: 2 },
278
254
  });
279
255
  const code = createCode([
280
- ops.merge,
281
- [ops.object, ["a", [ops.literal, 1]]],
282
- [ops.scope, "more"],
283
- [ops.object, ["c", [ops.scope, "a"]]],
256
+ [
257
+ ops.object,
258
+ ["a", [ops.literal, 1]],
259
+ ["c", [[ops.context], "a"]],
260
+ [
261
+ "_result",
262
+ [
263
+ ops.merge,
264
+ [ops.object, ["a", [ops.getter, [[ops.context, 1], "a"]]]],
265
+ [[ops.scope], "more"],
266
+ [ops.object, ["c", [ops.getter, [[ops.context, 1], "c"]]]],
267
+ ],
268
+ ],
269
+ ],
270
+ "_result",
284
271
  ]);
285
272
  const result = await evaluate.call(scope, code);
286
- assert.deepEqual(result, { a: 1, b: 2, c: 1 });
273
+ assert.deepEqual(await Tree.plain(result), { a: 1, b: 2, c: 1 });
287
274
  });
288
275
 
289
276
  test("ops.multiplication multiplies two numbers", async () => {
@@ -293,6 +280,11 @@ describe("ops", () => {
293
280
  assert.strictEqual(ops.multiplication("foo", 2), NaN);
294
281
  });
295
282
 
283
+ test("ops.optionalTraverse", async () => {
284
+ assert.equal(await ops.optionalTraverse(null, "a"), undefined);
285
+ assert.equal(await ops.optionalTraverse({ a: 1 }, "a"), 1);
286
+ });
287
+
296
288
  test("ops.notEqual", () => {
297
289
  assert(!ops.notEqual(1, 1));
298
290
  assert(ops.notEqual(1, 2));
@@ -325,8 +317,8 @@ describe("ops", () => {
325
317
 
326
318
  const code = createCode([
327
319
  ops.object,
328
- ["hello", [[ops.scope, "upper"], "hello"]],
329
- ["world", [[ops.scope, "upper"], "world"]],
320
+ ["hello", [[[ops.scope], "upper"], "hello"]],
321
+ ["world", [[[ops.scope], "upper"], "world"]],
330
322
  ]);
331
323
 
332
324
  const result = await evaluate.call(scope, code);
@@ -342,7 +334,7 @@ describe("ops", () => {
342
334
  ops.array,
343
335
  "Hello",
344
336
  1,
345
- [[ops.scope, "upper"], "world"],
337
+ [[[ops.scope], "upper"], "world"],
346
338
  ]);
347
339
  const result = await evaluate.call(scope, code);
348
340
  assert.deepEqual(result, ["Hello", 1, "WORLD"]);
@@ -355,6 +347,35 @@ describe("ops", () => {
355
347
  assert.strictEqual(ops.remainder(-4, 2), -0);
356
348
  });
357
349
 
350
+ describe("ops.scope", () => {
351
+ test("returns the scope of the current tree", async () => {
352
+ const tree = new DeepObjectTree({
353
+ a: {
354
+ b: {},
355
+ },
356
+ c: 1,
357
+ });
358
+ const a = await tree.get("a");
359
+ const b = await a.get("b");
360
+ const scope = await ops.scope.call(b);
361
+ assert.equal(await scope.get("c"), 1);
362
+ });
363
+
364
+ test("accepts an optional context", async () => {
365
+ const tree = new DeepObjectTree({
366
+ a: {
367
+ b: {},
368
+ c: 0, // shouldn't get this
369
+ },
370
+ c: 1,
371
+ });
372
+ const a = await tree.get("a");
373
+ const b = await a.get("b");
374
+ const scope = await ops.scope.call(b, tree);
375
+ assert.equal(await scope.get("c"), 1);
376
+ });
377
+ });
378
+
358
379
  test("ops.shiftLeft", () => {
359
380
  assert.strictEqual(ops.shiftLeft(5, 2), 20);
360
381
  });
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import indent from "../../src/runtime/taggedTemplateIndent.js";
3
+ import indent from "../../src/runtime/templateIndent.js";
4
4
 
5
5
  describe("taggedTemplateIndent", () => {
6
6
  test("joins strings and values together if template isn't a block template", async () => {