@weborigami/language 0.3.3-jse.3 → 0.3.3

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,6 +46,14 @@ 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
+
49
57
  test("if object in function position isn't a function, can unpack it", async () => {
50
58
  const fn = (...args) => args.join(",");
51
59
  const packed = new String();
@@ -54,4 +62,12 @@ describe("evaluate", () => {
54
62
  const result = await evaluate.call(null, code);
55
63
  assert.equal(result, "a,b,c");
56
64
  });
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
+ });
57
73
  });
@@ -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,8 +53,7 @@ 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({});
57
- parent.handlers = new ObjectTree({
56
+ const parent = new ObjectTree({
58
57
  "json.handler": {
59
58
  unpack: JSON.parse,
60
59
  },
@@ -3,12 +3,6 @@ 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 = new ObjectTree({
7
- "json.handler": {
8
- unpack: (buffer) => JSON.parse(String(buffer)),
9
- },
10
- });
11
-
12
6
  describe("handlers", () => {
13
7
  test("attaches an unpack method to a value with an extension", async () => {
14
8
  const fixture = createFixture();
@@ -16,12 +10,7 @@ describe("handlers", () => {
16
10
  assert(typeof numberValue === "number");
17
11
  assert.equal(numberValue, 1);
18
12
  const jsonFile = await fixture.get("bar.json");
19
- const withHandler = await handleExtension(
20
- fixture,
21
- jsonFile,
22
- "bar.json",
23
- handlers
24
- );
13
+ const withHandler = await handleExtension(fixture, jsonFile, "bar.json");
25
14
  assert.equal(String(withHandler), `{ "bar": 2 }`);
26
15
  const data = await withHandler.unpack();
27
16
  assert.deepEqual(data, { bar: 2 });
@@ -30,19 +19,21 @@ describe("handlers", () => {
30
19
  test("immediately unpacks if key ends in slash", async () => {
31
20
  const fixture = createFixture();
32
21
  const jsonFile = await fixture.get("bar.json");
33
- const data = await handleExtension(
34
- fixture,
35
- jsonFile,
36
- "bar.json/",
37
- handlers
38
- );
22
+ const data = await handleExtension(fixture, jsonFile, "bar.json/");
39
23
  assert.deepEqual(data, { bar: 2 });
40
24
  });
41
25
  });
42
26
 
43
27
  function createFixture() {
44
- return new ObjectTree({
28
+ const parent = new ObjectTree({
29
+ "json.handler": {
30
+ unpack: (buffer) => JSON.parse(String(buffer)),
31
+ },
32
+ });
33
+ let tree = new ObjectTree({
45
34
  foo: 1, // No extension, should be left alone
46
35
  "bar.json": `{ "bar": 2 }`,
47
36
  });
37
+ tree.parent = parent;
38
+ return tree;
48
39
  }
@@ -41,6 +41,11 @@ 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
+
44
49
  test("merges heterogenous arguments as trees", async () => {
45
50
  const tree = await mergeTrees.call(
46
51
  null,
@@ -1,4 +1,4 @@
1
- import { DeepObjectTree, ObjectTree, Tree } from "@weborigami/async-tree";
1
+ import { DeepObjectTree, ObjectTree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
 
@@ -42,6 +42,17 @@ 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
+
45
56
  test("ops.comma returns the last value", async () => {
46
57
  const code = createCode([ops.comma, 1, 2, 3]);
47
58
  const result = await evaluate.call(null, code);
@@ -53,12 +64,7 @@ describe("ops", () => {
53
64
  name: "world",
54
65
  });
55
66
 
56
- const code = createCode([
57
- ops.concat,
58
- "Hello, ",
59
- [[ops.scope], "name"],
60
- ".",
61
- ]);
67
+ const code = createCode([ops.concat, "Hello, ", [ops.scope, "name"], "."]);
62
68
 
63
69
  const result = await evaluate.call(scope, code);
64
70
  assert.strictEqual(result, "Hello, world.");
@@ -74,22 +80,27 @@ describe("ops", () => {
74
80
  assert.strictEqual(await ops.conditional(false, errorFn, trueFn), true);
75
81
  });
76
82
 
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
- },
83
+ test("ops.documentFunction", async () => {
84
+ const code = createCode([
85
+ ops.document,
86
+ {
87
+ a: 1,
87
88
  },
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",
88
103
  });
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);
93
104
  });
94
105
 
95
106
  test("ops.division divides two numbers", async () => {
@@ -112,21 +123,20 @@ describe("ops", () => {
112
123
  assert.strictEqual(ops.exponentiation(2, 0), 1);
113
124
  });
114
125
 
115
- test("ops.cache evaluates code and cache its result", async () => {
126
+ test("ops.external evaluates code and cache its result", async () => {
116
127
  let count = 0;
117
128
  const tree = new DeepObjectTree({
118
129
  group: {
119
130
  get count() {
120
- // Use promise to test async behavior
121
- return Promise.resolve(++count);
131
+ return ++count;
122
132
  },
123
133
  },
124
134
  });
125
135
  const code = createCode([
126
- ops.cache,
127
- {},
136
+ ops.external,
128
137
  "group/count",
129
- [[ops.scope], [ops.literal, "group"], [ops.literal, "count"]],
138
+ [ops.traverse, [ops.scope, "group"], [ops.literal, "count"]],
139
+ {},
130
140
  ]);
131
141
  const result = await evaluate.call(tree, code);
132
142
  assert.strictEqual(result, 1);
@@ -134,26 +144,6 @@ describe("ops", () => {
134
144
  assert.strictEqual(result2, 1);
135
145
  });
136
146
 
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
-
157
147
  test("ops.greaterThan", () => {
158
148
  assert(ops.greaterThan(5, 3));
159
149
  assert(!ops.greaterThan(3, 3));
@@ -166,6 +156,20 @@ describe("ops", () => {
166
156
  assert(ops.greaterThanOrEqual("ab", "aa"));
167
157
  });
168
158
 
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
+
169
173
  test("ops.lambda defines a function with no inputs", async () => {
170
174
  const code = createCode([ops.lambda, [], [ops.literal, "result"]]);
171
175
  const fn = await evaluate.call(null, code);
@@ -178,7 +182,7 @@ describe("ops", () => {
178
182
  message: "Hello",
179
183
  });
180
184
 
181
- const code = createCode([ops.lambda, ["_"], [[ops.scope], "message"]]);
185
+ const code = createCode([ops.lambda, ["_"], [ops.scope, "message"]]);
182
186
 
183
187
  const fn = await evaluate.call(scope, code);
184
188
  const result = await fn.call(scope);
@@ -192,7 +196,7 @@ describe("ops", () => {
192
196
  [ops.literal, "a"],
193
197
  [ops.literal, "b"],
194
198
  ],
195
- [ops.concat, [[ops.scope], "b"], [[ops.scope], "a"]],
199
+ [ops.concat, [ops.scope, "b"], [ops.scope, "a"]],
196
200
  ]);
197
201
  const fn = await evaluate.call(null, code);
198
202
  const result = await fn("x", "y");
@@ -246,31 +250,40 @@ describe("ops", () => {
246
250
  test("ops.merge", async () => {
247
251
  // {
248
252
  // a: 1
249
- // …more
250
- // c: a
253
+ // …fn(a)
251
254
  // }
252
255
  const scope = new ObjectTree({
253
- more: { b: 2 },
256
+ fn: (a) => ({ b: 2 * a }),
254
257
  });
255
258
  const code = createCode([
259
+ ops.merge,
260
+ [ops.object, ["a", [ops.literal, 1]]],
256
261
  [
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
- ],
262
+ [ops.builtin, "fn"],
263
+ [ops.scope, "a"],
269
264
  ],
270
- "_result",
271
265
  ]);
272
266
  const result = await evaluate.call(scope, code);
273
- assert.deepEqual(await Tree.plain(result), { a: 1, b: 2, c: 1 });
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
274
+ // c: a
275
+ // }
276
+ const scope = new ObjectTree({
277
+ more: { b: 2 },
278
+ });
279
+ const code = createCode([
280
+ ops.merge,
281
+ [ops.object, ["a", [ops.literal, 1]]],
282
+ [ops.scope, "more"],
283
+ [ops.object, ["c", [ops.scope, "a"]]],
284
+ ]);
285
+ const result = await evaluate.call(scope, code);
286
+ assert.deepEqual(result, { a: 1, b: 2, c: 1 });
274
287
  });
275
288
 
276
289
  test("ops.multiplication multiplies two numbers", async () => {
@@ -280,11 +293,6 @@ describe("ops", () => {
280
293
  assert.strictEqual(ops.multiplication("foo", 2), NaN);
281
294
  });
282
295
 
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
-
288
296
  test("ops.notEqual", () => {
289
297
  assert(!ops.notEqual(1, 1));
290
298
  assert(ops.notEqual(1, 2));
@@ -317,8 +325,8 @@ describe("ops", () => {
317
325
 
318
326
  const code = createCode([
319
327
  ops.object,
320
- ["hello", [[[ops.scope], "upper"], "hello"]],
321
- ["world", [[[ops.scope], "upper"], "world"]],
328
+ ["hello", [[ops.scope, "upper"], "hello"]],
329
+ ["world", [[ops.scope, "upper"], "world"]],
322
330
  ]);
323
331
 
324
332
  const result = await evaluate.call(scope, code);
@@ -334,7 +342,7 @@ describe("ops", () => {
334
342
  ops.array,
335
343
  "Hello",
336
344
  1,
337
- [[[ops.scope], "upper"], "world"],
345
+ [[ops.scope, "upper"], "world"],
338
346
  ]);
339
347
  const result = await evaluate.call(scope, code);
340
348
  assert.deepEqual(result, ["Hello", 1, "WORLD"]);
@@ -347,35 +355,6 @@ describe("ops", () => {
347
355
  assert.strictEqual(ops.remainder(-4, 2), -0);
348
356
  });
349
357
 
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
-
379
358
  test("ops.shiftLeft", () => {
380
359
  assert.strictEqual(ops.shiftLeft(5, 2), 20);
381
360
  });
@@ -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/templateIndent.js";
3
+ import indent from "../../src/runtime/taggedTemplateIndent.js";
4
4
 
5
5
  describe("taggedTemplateIndent", () => {
6
6
  test("joins strings and values together if template isn't a block template", async () => {
@@ -1,10 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
-
3
- // Return the extension handlers for the given tree
4
- export default function getHandlers(tree) {
5
- if (!tree) {
6
- return null;
7
- }
8
- const root = Tree.root(tree);
9
- return root.handlers;
10
- }
@@ -1,99 +0,0 @@
1
- import path from "node:path";
2
-
3
- /**
4
- * The complete set of support JavaScript globals and global-like values.
5
- *
6
- * See
7
- * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects.
8
- * That page lists some things like `TypedArrays` which are not globals so are
9
- * omitted here.
10
- */
11
- export default {
12
- AggregateError,
13
- Array,
14
- ArrayBuffer,
15
- Atomics,
16
- BigInt,
17
- BigInt64Array,
18
- BigUint64Array,
19
- Boolean,
20
- DataView,
21
- Date,
22
- Error,
23
- EvalError,
24
- FinalizationRegistry,
25
- Float32Array,
26
- Float64Array,
27
- Function,
28
- Infinity,
29
- Int16Array,
30
- Int32Array,
31
- Int8Array,
32
- Intl,
33
- // @ts-ignore Iterator does exist despite what TypeScript thinks
34
- Iterator,
35
- JSON,
36
- Map,
37
- Math,
38
- NaN,
39
- Number,
40
- Object,
41
- Promise,
42
- Proxy,
43
- RangeError,
44
- ReferenceError,
45
- Reflect,
46
- RegExp,
47
- Set,
48
- SharedArrayBuffer,
49
- String,
50
- Symbol,
51
- SyntaxError,
52
- TypeError,
53
- URIError,
54
- Uint16Array,
55
- Uint32Array,
56
- Uint8Array,
57
- Uint8ClampedArray,
58
- WeakMap,
59
- WeakRef,
60
- WeakSet,
61
- decodeURI,
62
- decodeURIComponent,
63
- encodeURI,
64
- encodeURIComponent,
65
- eval,
66
- false: false, // treat like a global
67
- fetch: fetchWrapper, // special case
68
- globalThis,
69
- import: importWrapper, // not a function in JS but acts like one
70
- isFinite,
71
- isNaN,
72
- null: null, // treat like a global
73
- parseFloat,
74
- parseInt,
75
- true: true, // treat like a global
76
- undefined,
77
- };
78
-
79
- async function fetchWrapper(resource, options) {
80
- const response = await fetch(resource, options);
81
- return response.ok ? await response.arrayBuffer() : undefined;
82
- }
83
-
84
- /** @this {import("@weborigami/types").AsyncTree|null|undefined} */
85
- async function importWrapper(modulePath) {
86
- // Walk up parent tree looking for a FileTree or other object with a `path`
87
- /** @type {any} */
88
- let current = this;
89
- while (current && !("path" in current)) {
90
- current = current.parent;
91
- }
92
- if (!current) {
93
- throw new TypeError(
94
- "Modules can only be imported from a folder or other object with a path property."
95
- );
96
- }
97
- const filePath = path.resolve(current.path, modulePath);
98
- return import(filePath);
99
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Concatenate the strings with a standard tagged template function that works
3
- * just like normal JavaScript templates. This is: a) synchronous, b) does not
4
- * convert treelike objects to strings.
5
- */
6
- export default function standardTemplate(strings, ...values) {
7
- let result = strings[0];
8
- for (let i = 0; i < values.length; i++) {
9
- result += values[i];
10
- result += strings[i + 1];
11
- }
12
- return result;
13
- }
@@ -1,18 +0,0 @@
1
- import assert from "node:assert";
2
- import { describe, test } from "node:test";
3
- import templateText from "../../src/runtime/templateStandard.js";
4
-
5
- describe("templateText", () => {
6
- test("joins strings and values together like JavaScript", async () => {
7
- const a = 1;
8
- const b = 2;
9
- const result = await templateText`-${a} ${b}-`;
10
- assert.equal(result, "-1 2-");
11
- });
12
-
13
- test("renders an object like JavaScript", async () => {
14
- const object = { a: 1 };
15
- const result = await templateText`-${object}-`;
16
- assert.equal(result, "-[object Object]-");
17
- });
18
- });