@weborigami/language 0.3.3 → 0.3.4-jse.4

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.
@@ -1,42 +1,281 @@
1
+ import { ObjectTree } from "@weborigami/async-tree";
1
2
  import { describe, test } from "node:test";
2
3
  import * as compile from "../../src/compiler/compile.js";
3
4
  import optimize from "../../src/compiler/optimize.js";
5
+ import { markers } from "../../src/compiler/parserHelpers.js";
4
6
  import { ops } from "../../src/runtime/internal.js";
5
7
  import { assertCodeEqual, createCode } from "./codeHelpers.js";
6
8
 
7
9
  describe("optimize", () => {
8
- test("optimize non-local ops.scope calls to ops.external", async () => {
9
- const expression = `
10
- (name) => {
11
- a: 1
12
- b: a // local, should be left as ops.scope
13
- c: elsewhere // external, should be converted to ops.external
14
- d: name // local, should be left as ops.scope
15
- }
16
- `;
17
- const fn = compile.expression(expression);
18
- const code = fn.code;
19
- assertCodeEqual(code, [
10
+ test("change local references to context references", () => {
11
+ const expression = `(name) => {
12
+ a: name,
13
+ b: a
14
+ }`;
15
+ const expected = [
20
16
  ops.lambda,
21
17
  [[ops.literal, "name"]],
22
18
  [
23
19
  ops.object,
24
- ["a", 1],
25
- ["b", [ops.scope, "a"]],
26
- ["c", [ops.external, "elsewhere", [ops.scope, "elsewhere"], {}]],
27
- ["d", [ops.scope, "name"]],
20
+ [
21
+ "a",
22
+ [
23
+ [ops.context, 1],
24
+ [ops.literal, "name"],
25
+ ],
26
+ ],
27
+ ["b", [[ops.context], [ops.literal, "a"]]],
28
28
  ],
29
- ]);
29
+ ];
30
+ assertCompile(expression, expected);
30
31
  });
31
32
 
32
- test("optimize scope traversals with all literal keys", async () => {
33
- // Compilation of `x/y.js`
33
+ test("resolve deeper context references", () => {
34
+ // Compilation of `{ a: 1, more: { a } }`
34
35
  const code = createCode([
35
- ops.traverse,
36
- [ops.scope, "x/"],
37
- [ops.literal, "y.js"],
36
+ ops.object,
37
+ ["a", [ops.literal, 1]],
38
+ [
39
+ "more",
40
+ [ops.object, ["a", [markers.traverse, [markers.reference, "a"]]]],
41
+ ],
38
42
  ]);
39
- const optimized = optimize(code);
40
- assertCodeEqual(optimized, [ops.external, "x/y.js", code, {}]);
43
+ const expected = [
44
+ ops.object,
45
+ ["a", 1],
46
+ [
47
+ "more",
48
+ [
49
+ ops.object,
50
+ [
51
+ "a",
52
+ [
53
+ [ops.context, 1],
54
+ [ops.literal, "a"],
55
+ ],
56
+ ],
57
+ ],
58
+ ],
59
+ ];
60
+ assertCodeEqual(optimize(code), expected);
61
+ });
62
+
63
+ test("when defining a property, avoid recursive references", () => {
64
+ const expression = `{
65
+ name: "Alice",
66
+ user: {
67
+ name: name
68
+ }
69
+ }`;
70
+ const expected = [
71
+ ops.object,
72
+ ["name", "Alice"],
73
+ [
74
+ "user",
75
+ [
76
+ ops.object,
77
+ [
78
+ "name",
79
+ [
80
+ [ops.context, 1],
81
+ [ops.literal, "name"],
82
+ ],
83
+ ],
84
+ ],
85
+ ],
86
+ ];
87
+ assertCompile(expression, expected);
88
+ assertCompile(expression, expected, "jse");
89
+ });
90
+
91
+ describe("resolve reference", () => {
92
+ test("external reference", () => {
93
+ // Compilation of `folder` where folder isn't a variable
94
+ const code = createCode([
95
+ markers.traverse,
96
+ [markers.reference, "folder"],
97
+ ]);
98
+ const expected = [
99
+ ops.cache,
100
+ {},
101
+ "folder",
102
+ [[ops.scope], [ops.literal, "folder"]],
103
+ ];
104
+ const globals = {};
105
+ assertCodeEqual(optimize(code, { globals }), expected);
106
+ });
107
+
108
+ test("external reference", () => {
109
+ // Compilation of `index.html` where `index` isn't a variable
110
+ const code = createCode([
111
+ markers.traverse,
112
+ [markers.reference, "index.html"],
113
+ ]);
114
+ const expected = [
115
+ ops.cache,
116
+ {},
117
+ "index.html",
118
+ [[ops.scope], [ops.literal, "index.html"]],
119
+ ];
120
+ const globals = {};
121
+ assertCodeEqual(optimize(code, { globals }), expected);
122
+ });
123
+
124
+ test("external reference inside object with matching key", () => {
125
+ // Compilation of `{ (posts) = posts.txt }`
126
+ const code = createCode([
127
+ ops.object,
128
+ [
129
+ "(posts)",
130
+ [ops.getter, [markers.traverse, [markers.reference, "posts.txt"]]],
131
+ ],
132
+ ]);
133
+ const expected = [
134
+ ops.object,
135
+ [
136
+ "(posts)",
137
+ [
138
+ ops.getter,
139
+ [
140
+ ops.cache,
141
+ {},
142
+ "posts.txt",
143
+ [
144
+ [ops.scope, [ops.context, 1]],
145
+ [ops.literal, "posts.txt"],
146
+ ],
147
+ ],
148
+ ],
149
+ ],
150
+ ];
151
+ const globals = {};
152
+ assertCodeEqual(optimize(code, { globals }), expected);
153
+ });
154
+
155
+ test("global reference", () => {
156
+ // Compilation of `Math` where Math is a global variable
157
+ const code = createCode([markers.traverse, [markers.reference, "Math"]]);
158
+ const globals = { Math: null }; // value doesn't matter
159
+ const expected = [globals, "Math"];
160
+ assertCodeEqual(optimize(code, { globals }), expected);
161
+ });
162
+
163
+ test("global reference", () => {
164
+ // Compilation of `Math.PI` where Math is a global variable
165
+ const code = createCode([
166
+ markers.traverse,
167
+ [markers.reference, "Math.PI"],
168
+ ]);
169
+ const globals = { Math: { PI: null } }; // value doesn't matter
170
+ const expected = [[globals, "Math"], "PI"];
171
+ assertCodeEqual(optimize(code, { globals }), expected);
172
+ });
173
+
174
+ test("local reference", () => {
175
+ // Compilation of `post` where post is a local variable
176
+ const code = createCode([markers.traverse, [markers.reference, "post"]]);
177
+ const globals = { post: {} }; // local should take precedence
178
+ const locals = [["post"]];
179
+ const actual = optimize(code, { globals, locals });
180
+ const expected = [[ops.context], [ops.literal, "post"]];
181
+ assertCodeEqual(actual, expected);
182
+ });
183
+
184
+ test("local reference and property", () => {
185
+ // Compilation of `post.author.name` where `post` is a local variable
186
+ const code = createCode([
187
+ markers.traverse,
188
+ [markers.reference, "post.author.name"],
189
+ ]);
190
+ const globals = { post: {} }; // local should take precedence
191
+ const locals = [["post"]];
192
+ const actual = optimize(code, { globals, locals });
193
+ const expected = [
194
+ [[[ops.context], [ops.literal, "post"]], "author"],
195
+ "name",
196
+ ];
197
+ assertCodeEqual(actual, expected);
198
+ });
199
+
200
+ test("root directory", () => {
201
+ // Compilation of `</>`
202
+ const code = createCode([markers.traverse, [markers.external, "/"]]);
203
+ const expected = [ops.cache, {}, "/", [ops.rootDirectory]];
204
+ assertCodeEqual(optimize(code), expected);
205
+ });
206
+
207
+ test("home directory", () => {
208
+ // Compilation of `<~>`
209
+ const code = createCode([markers.traverse, [ops.homeDirectory]]);
210
+ const expected = [ops.cache, {}, "~", [ops.homeDirectory]];
211
+ assertCodeEqual(optimize(code), expected);
212
+ });
213
+ });
214
+
215
+ describe("path traversal", () => {
216
+ test("explicit external path", () => {
217
+ // `<path/to/file>`
218
+ const code = createCode([
219
+ markers.traverse,
220
+ [markers.external, "path/"],
221
+ [ops.literal, "to/"],
222
+ [ops.literal, "file"],
223
+ ]);
224
+ const expected = [
225
+ ops.cache,
226
+ {},
227
+ "path/to/file",
228
+ [
229
+ [ops.scope],
230
+ [ops.literal, "path/"],
231
+ [ops.literal, "to/"],
232
+ [ops.literal, "file"],
233
+ ],
234
+ ];
235
+ assertCodeEqual(optimize(code), expected);
236
+ });
237
+
238
+ test("implicit external path", () => {
239
+ // Compilation of `package.json/name` where package is neither local nor global
240
+ const code = createCode([
241
+ markers.traverse,
242
+ [markers.reference, "package.json/"],
243
+ [ops.literal, "name"],
244
+ ]);
245
+ const globals = {};
246
+ const expected = [
247
+ ops.cache,
248
+ {},
249
+ "package.json/name",
250
+ [[ops.scope], [ops.literal, "package.json/"], [ops.literal, "name"]],
251
+ ];
252
+ assertCodeEqual(optimize(code, { globals }), expected);
253
+ });
254
+
255
+ test("local path", () => {
256
+ // Compilation of `page/title` where page is a local variable
257
+ const code = createCode([
258
+ markers.traverse,
259
+ [markers.reference, "page/"],
260
+ [ops.literal, "title"],
261
+ ]);
262
+ const globals = {};
263
+ const locals = [["page"]];
264
+ const actual = optimize(code, { globals, locals });
265
+ const expected = [
266
+ [ops.context],
267
+ [ops.literal, "page/"],
268
+ [ops.literal, "title"],
269
+ ];
270
+ assertCodeEqual(actual, expected);
271
+ });
41
272
  });
42
273
  });
274
+
275
+ function assertCompile(expression, expected, mode = "shell") {
276
+ const parent = new ObjectTree({});
277
+ const globals = new ObjectTree({});
278
+ const fn = compile.expression(expression, { globals, mode, parent });
279
+ const actual = fn.code;
280
+ assertCodeEqual(actual, expected);
281
+ }