@weborigami/language 0.2.6 → 0.2.8
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 +17 -9
- package/package.json +3 -3
- package/src/compiler/optimize.js +36 -20
- package/src/compiler/origami.pegjs +48 -37
- package/src/compiler/parse.d.ts +2 -2
- package/src/compiler/parse.js +299 -284
- package/src/compiler/parserHelpers.js +132 -89
- package/src/runtime/errors.js +5 -2
- package/src/runtime/evaluate.js +8 -11
- package/src/runtime/expressionFunction.js +1 -1
- package/src/runtime/expressionObject.js +12 -4
- package/src/runtime/mergeTrees.js +2 -0
- package/src/runtime/ops.js +63 -18
- package/src/runtime/typos.js +6 -0
- package/test/compiler/{stripCodeLocations.js → codeHelpers.js} +24 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/optimize.test.js +9 -11
- package/test/compiler/parse.test.js +27 -8
- package/test/runtime/OrigamiFiles.test.js +0 -2
- package/test/runtime/evaluate.test.js +1 -13
- package/test/runtime/mergeTrees.test.js +0 -1
- package/test/runtime/ops.test.js +40 -13
- package/test/runtime/typos.test.js +1 -0
|
@@ -4,6 +4,9 @@ import * as ops from "../runtime/ops.js";
|
|
|
4
4
|
|
|
5
5
|
// Parser helpers
|
|
6
6
|
|
|
7
|
+
/** @typedef {import("../../index.ts").AnnotatedCode} AnnotatedCode */
|
|
8
|
+
/** @typedef {import("../../index.ts").AnnotatedCodeItem} AnnotatedCodeItem */
|
|
9
|
+
/** @typedef {import("../../index.ts").CodeLocation} CodeLocation */
|
|
7
10
|
/** @typedef {import("../../index.ts").Code} Code */
|
|
8
11
|
|
|
9
12
|
// Marker for a reference that may be a builtin or a scope reference
|
|
@@ -15,15 +18,16 @@ const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
|
15
18
|
* If a parse result is an object that will be evaluated at runtime, attach the
|
|
16
19
|
* location of the source code that produced it for debugging and error messages.
|
|
17
20
|
*
|
|
18
|
-
* @param {Code} code
|
|
19
|
-
* @param {
|
|
21
|
+
* @param {Code[]} code
|
|
22
|
+
* @param {CodeLocation} location
|
|
20
23
|
*/
|
|
21
24
|
export function annotate(code, location) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
/** @type {AnnotatedCode} */
|
|
26
|
+
// @ts-ignore - Need to add annotation below before type is correct
|
|
27
|
+
const annotated = code.slice();
|
|
28
|
+
annotated.location = location;
|
|
29
|
+
annotated.source = codeFragment(location);
|
|
30
|
+
return annotated;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
/**
|
|
@@ -31,7 +35,7 @@ export function annotate(code, location) {
|
|
|
31
35
|
* Rewrite any [ops.scope, key] calls to be [ops.inherited, key] to avoid
|
|
32
36
|
* infinite recursion.
|
|
33
37
|
*
|
|
34
|
-
* @param {
|
|
38
|
+
* @param {AnnotatedCode} code
|
|
35
39
|
* @param {string} key
|
|
36
40
|
*/
|
|
37
41
|
function avoidRecursivePropertyCalls(code, key) {
|
|
@@ -45,45 +49,49 @@ function avoidRecursivePropertyCalls(code, key) {
|
|
|
45
49
|
trailingSlash.remove(code[1]) === trailingSlash.remove(key)
|
|
46
50
|
) {
|
|
47
51
|
// Rewrite to avoid recursion
|
|
48
|
-
// @ts-ignore
|
|
49
52
|
modified = [ops.inherited, code[1]];
|
|
50
53
|
} else if (code[0] === ops.lambda && code[1].includes(key)) {
|
|
51
54
|
// Lambda that defines the key; don't rewrite
|
|
52
55
|
return code;
|
|
53
56
|
} else {
|
|
54
57
|
// Process any nested code
|
|
55
|
-
// @ts-ignore
|
|
56
58
|
modified = code.map((value) => avoidRecursivePropertyCalls(value, key));
|
|
57
59
|
}
|
|
58
|
-
annotate(modified, code.location);
|
|
59
|
-
return modified;
|
|
60
|
+
return annotate(modified, code.location);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
64
|
* Downgrade a potential builtin reference to a scope reference.
|
|
64
65
|
*
|
|
65
|
-
* @param {
|
|
66
|
+
* @param {AnnotatedCode} code
|
|
66
67
|
*/
|
|
67
68
|
export function downgradeReference(code) {
|
|
68
69
|
if (code && code.length === 2 && code[0] === undetermined) {
|
|
69
|
-
|
|
70
|
-
// @ts-ignore
|
|
71
|
-
const result = [ops.scope, code[1]];
|
|
72
|
-
annotate(result, code.location);
|
|
73
|
-
return result;
|
|
70
|
+
return annotate([ops.scope, code[1]], code.location);
|
|
74
71
|
} else {
|
|
75
72
|
return code;
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Create an array
|
|
78
|
+
*
|
|
79
|
+
* @param {AnnotatedCode[]} entries
|
|
80
|
+
* @param {CodeLocation} location
|
|
81
|
+
*/
|
|
82
|
+
export function makeArray(entries, location) {
|
|
80
83
|
let currentEntries = [];
|
|
81
84
|
const spreads = [];
|
|
82
85
|
|
|
83
86
|
for (const value of entries) {
|
|
84
87
|
if (Array.isArray(value) && value[0] === ops.spread) {
|
|
85
88
|
if (currentEntries.length > 0) {
|
|
86
|
-
|
|
89
|
+
const location = { ...currentEntries[0].location };
|
|
90
|
+
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
91
|
+
/** @type {AnnotatedCodeItem} */
|
|
92
|
+
const fn = ops.array;
|
|
93
|
+
const spread = annotate([fn, ...currentEntries], location);
|
|
94
|
+
spreads.push(spread);
|
|
87
95
|
currentEntries = [];
|
|
88
96
|
}
|
|
89
97
|
spreads.push(...value.slice(1));
|
|
@@ -98,21 +106,24 @@ export function makeArray(entries) {
|
|
|
98
106
|
currentEntries = [];
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
let result;
|
|
101
110
|
if (spreads.length > 1) {
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return spreads[0];
|
|
111
|
+
result = [ops.merge, ...spreads];
|
|
112
|
+
} else if (spreads.length === 1) {
|
|
113
|
+
result = spreads[0];
|
|
106
114
|
} else {
|
|
107
|
-
|
|
115
|
+
result = [ops.array];
|
|
108
116
|
}
|
|
117
|
+
|
|
118
|
+
return annotate(result, location);
|
|
109
119
|
}
|
|
110
120
|
|
|
111
121
|
/**
|
|
112
122
|
* Create a chain of binary operators. The head is the first value, and the tail
|
|
113
|
-
* is
|
|
123
|
+
* is a [operator, value] pair as an array.
|
|
114
124
|
*
|
|
115
|
-
* @param {
|
|
125
|
+
* @param {AnnotatedCode} left
|
|
126
|
+
* @param {[token: any, right: AnnotatedCode]} tail
|
|
116
127
|
*/
|
|
117
128
|
export function makeBinaryOperation(left, [operatorToken, right]) {
|
|
118
129
|
const operators = {
|
|
@@ -139,20 +150,19 @@ export function makeBinaryOperation(left, [operatorToken, right]) {
|
|
|
139
150
|
};
|
|
140
151
|
const op = operators[operatorToken];
|
|
141
152
|
|
|
142
|
-
|
|
143
|
-
// @ts-ignore
|
|
144
|
-
const value = [op, left, right];
|
|
145
|
-
value.location = {
|
|
153
|
+
const location = {
|
|
146
154
|
source: left.location.source,
|
|
147
155
|
start: left.location.start,
|
|
148
156
|
end: right.location.end,
|
|
149
157
|
};
|
|
150
158
|
|
|
151
|
-
return
|
|
159
|
+
return annotate([op, left, right], location);
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
/**
|
|
155
|
-
*
|
|
163
|
+
* Create a function call.
|
|
164
|
+
*
|
|
165
|
+
* @param {AnnotatedCode} target
|
|
156
166
|
* @param {any[]} args
|
|
157
167
|
*/
|
|
158
168
|
export function makeCall(target, args) {
|
|
@@ -162,10 +172,6 @@ export function makeCall(target, args) {
|
|
|
162
172
|
throw error;
|
|
163
173
|
}
|
|
164
174
|
|
|
165
|
-
const source = target.location.source;
|
|
166
|
-
let start = target.location.start;
|
|
167
|
-
let end = target.location.end;
|
|
168
|
-
|
|
169
175
|
let fnCall;
|
|
170
176
|
if (args[0] === ops.traverse) {
|
|
171
177
|
let tree = target;
|
|
@@ -196,18 +202,21 @@ export function makeCall(target, args) {
|
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
// Create a location spanning the newly-constructed function call.
|
|
205
|
+
const location = { ...target.location };
|
|
199
206
|
if (args instanceof Array) {
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
let end;
|
|
208
|
+
if ("location" in args) {
|
|
209
|
+
end = /** @type {any} */ (args).location.end;
|
|
210
|
+
} else if ("location" in args.at(-1)) {
|
|
211
|
+
end = args.at(-1).location.end;
|
|
212
|
+
}
|
|
202
213
|
if (end === undefined) {
|
|
203
214
|
throw "Internal parser error: no location for function call argument";
|
|
204
215
|
}
|
|
216
|
+
location.end = end;
|
|
205
217
|
}
|
|
206
218
|
|
|
207
|
-
|
|
208
|
-
annotate(fnCall, { start, source, end });
|
|
209
|
-
|
|
210
|
-
return fnCall;
|
|
219
|
+
return annotate(fnCall, location);
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
/**
|
|
@@ -215,32 +224,47 @@ export function makeCall(target, args) {
|
|
|
215
224
|
* the arguments until the function is called. Exception: if the argument is a
|
|
216
225
|
* literal, we leave it alone.
|
|
217
226
|
*
|
|
218
|
-
* @param {
|
|
227
|
+
* @param {AnnotatedCode[]} args
|
|
219
228
|
*/
|
|
220
229
|
export function makeDeferredArguments(args) {
|
|
221
230
|
return args.map((arg) => {
|
|
222
231
|
if (arg instanceof Array && arg[0] === ops.literal) {
|
|
223
232
|
return arg;
|
|
224
233
|
}
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return fn;
|
|
234
|
+
const lambdaParameters = annotate([], arg.location);
|
|
235
|
+
/** @type {AnnotatedCodeItem} */
|
|
236
|
+
const fn = [ops.lambda, lambdaParameters, arg];
|
|
237
|
+
return annotate(fn, arg.location);
|
|
229
238
|
});
|
|
230
239
|
}
|
|
231
240
|
|
|
232
|
-
|
|
241
|
+
/**
|
|
242
|
+
* Make an object.
|
|
243
|
+
*
|
|
244
|
+
* @param {AnnotatedCode[]} entries
|
|
245
|
+
* @param {CodeLocation} location
|
|
246
|
+
*/
|
|
247
|
+
export function makeObject(entries, location) {
|
|
233
248
|
let currentEntries = [];
|
|
234
249
|
const spreads = [];
|
|
235
250
|
|
|
236
|
-
for (let
|
|
251
|
+
for (let entry of entries) {
|
|
252
|
+
const [key, value] = entry;
|
|
237
253
|
if (key === ops.spread) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
if (value[0] === ops.object) {
|
|
255
|
+
// Spread of an object; fold into current object
|
|
256
|
+
currentEntries.push(...value.slice(1));
|
|
257
|
+
} else {
|
|
258
|
+
// Spread of a tree; accumulate
|
|
259
|
+
if (currentEntries.length > 0) {
|
|
260
|
+
const location = { ...currentEntries[0].location };
|
|
261
|
+
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
262
|
+
const spread = annotate([ops.object, ...currentEntries], location);
|
|
263
|
+
spreads.push(spread);
|
|
264
|
+
currentEntries = [];
|
|
265
|
+
}
|
|
266
|
+
spreads.push(value);
|
|
242
267
|
}
|
|
243
|
-
spreads.push(value);
|
|
244
268
|
continue;
|
|
245
269
|
}
|
|
246
270
|
|
|
@@ -251,48 +275,50 @@ export function makeObject(entries, op) {
|
|
|
251
275
|
value[1][0] === ops.literal
|
|
252
276
|
) {
|
|
253
277
|
// Optimize a getter for a primitive value to a regular property
|
|
254
|
-
|
|
278
|
+
entry = annotate([key, value[1]], entry.location);
|
|
255
279
|
}
|
|
256
|
-
// else if (
|
|
257
|
-
// value[0] === ops.object ||
|
|
258
|
-
// (value[0] === ops.getter &&
|
|
259
|
-
// value[1] instanceof Array &&
|
|
260
|
-
// (value[1][0] === ops.object || value[1][0] === ops.merge))
|
|
261
|
-
// ) {
|
|
262
|
-
// // Add a trailing slash to key to indicate value is a subtree
|
|
263
|
-
// key = trailingSlash.add(key);
|
|
264
|
-
// }
|
|
265
280
|
}
|
|
266
281
|
|
|
267
|
-
currentEntries.push(
|
|
282
|
+
currentEntries.push(entry);
|
|
268
283
|
}
|
|
269
284
|
|
|
270
285
|
// Finish any current entries.
|
|
271
286
|
if (currentEntries.length > 0) {
|
|
272
|
-
|
|
287
|
+
const location = { ...currentEntries[0].location };
|
|
288
|
+
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
289
|
+
const spread = annotate([ops.object, ...currentEntries], location);
|
|
290
|
+
spreads.push(spread);
|
|
273
291
|
currentEntries = [];
|
|
274
292
|
}
|
|
275
293
|
|
|
294
|
+
let code;
|
|
276
295
|
if (spreads.length > 1) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (spreads.length === 1) {
|
|
280
|
-
|
|
296
|
+
// Merge multiple spreads
|
|
297
|
+
code = [ops.merge, ...spreads];
|
|
298
|
+
} else if (spreads.length === 1) {
|
|
299
|
+
// A single spread can just be the object
|
|
300
|
+
code = spreads[0];
|
|
281
301
|
} else {
|
|
282
|
-
|
|
302
|
+
// Empty object
|
|
303
|
+
code = [ops.object];
|
|
283
304
|
}
|
|
305
|
+
|
|
306
|
+
return annotate(code, location);
|
|
284
307
|
}
|
|
285
308
|
|
|
286
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Make a pipline: similar to a function call, but the order is reversed.
|
|
311
|
+
*
|
|
312
|
+
* @param {AnnotatedCode} arg
|
|
313
|
+
* @param {AnnotatedCode} fn
|
|
314
|
+
*/
|
|
287
315
|
export function makePipeline(arg, fn) {
|
|
288
316
|
const upgraded = upgradeReference(fn);
|
|
289
317
|
const result = makeCall(upgraded, [arg]);
|
|
290
318
|
const source = fn.location.source;
|
|
291
319
|
let start = arg.location.start;
|
|
292
320
|
let end = fn.location.end;
|
|
293
|
-
|
|
294
|
-
annotate(result, { start, source, end });
|
|
295
|
-
return result;
|
|
321
|
+
return annotate(result, { start, source, end });
|
|
296
322
|
}
|
|
297
323
|
|
|
298
324
|
// Define a property on an object.
|
|
@@ -316,38 +342,55 @@ export function makeReference(identifier) {
|
|
|
316
342
|
return [op, identifier];
|
|
317
343
|
}
|
|
318
344
|
|
|
319
|
-
|
|
320
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Make a template
|
|
347
|
+
*
|
|
348
|
+
* @param {any} op
|
|
349
|
+
* @param {AnnotatedCode} head
|
|
350
|
+
* @param {AnnotatedCode} tail
|
|
351
|
+
* @param {CodeLocation} location
|
|
352
|
+
*/
|
|
353
|
+
export function makeTemplate(op, head, tail, location) {
|
|
354
|
+
const strings = [head[1]];
|
|
321
355
|
const values = [];
|
|
322
|
-
for (const [value,
|
|
323
|
-
|
|
324
|
-
|
|
356
|
+
for (const [value, literal] of tail) {
|
|
357
|
+
const concat = annotate([ops.concat, value], value.location);
|
|
358
|
+
values.push(concat);
|
|
359
|
+
strings.push(literal[1]);
|
|
325
360
|
}
|
|
326
|
-
|
|
361
|
+
const stringsCode = annotate(strings, location);
|
|
362
|
+
/** @type {AnnotatedCodeItem} */
|
|
363
|
+
const fn = ops.literal;
|
|
364
|
+
const literalCode = annotate([fn, stringsCode], location);
|
|
365
|
+
return annotate([op, literalCode, ...values], location);
|
|
327
366
|
}
|
|
328
367
|
|
|
329
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Make a unary operation.
|
|
370
|
+
*
|
|
371
|
+
* @param {AnnotatedCode} operator
|
|
372
|
+
* @param {AnnotatedCode} value
|
|
373
|
+
* @param {CodeLocation} location
|
|
374
|
+
*/
|
|
375
|
+
export function makeUnaryOperation(operator, value, location) {
|
|
330
376
|
const operators = {
|
|
331
377
|
"!": ops.logicalNot,
|
|
332
378
|
"+": ops.unaryPlus,
|
|
333
379
|
"-": ops.unaryMinus,
|
|
334
380
|
"~": ops.bitwiseNot,
|
|
335
381
|
};
|
|
336
|
-
return [operators[operator], value];
|
|
382
|
+
return annotate([operators[operator], value], location);
|
|
337
383
|
}
|
|
338
384
|
|
|
339
385
|
/**
|
|
340
386
|
* Upgrade a potential builtin reference to an actual builtin reference.
|
|
341
387
|
*
|
|
342
|
-
* @param {
|
|
388
|
+
* @param {AnnotatedCode} code
|
|
343
389
|
*/
|
|
344
390
|
export function upgradeReference(code) {
|
|
345
391
|
if (code.length === 2 && code[0] === undetermined) {
|
|
346
|
-
/** @type {Code} */
|
|
347
|
-
// @ts-ignore
|
|
348
392
|
const result = [ops.builtin, code[1]];
|
|
349
|
-
annotate(result, code.location);
|
|
350
|
-
return result;
|
|
393
|
+
return annotate(result, code.location);
|
|
351
394
|
} else {
|
|
352
395
|
return code;
|
|
353
396
|
}
|
package/src/runtime/errors.js
CHANGED
|
@@ -79,13 +79,16 @@ export function formatError(error) {
|
|
|
79
79
|
// Add location
|
|
80
80
|
if (location) {
|
|
81
81
|
let { source, start } = location;
|
|
82
|
+
// Adjust line number with offset if present (for example, if the code is in
|
|
83
|
+
// an Origami template document with front matter that was stripped)
|
|
84
|
+
let line = start.line + (source.offset ?? 0);
|
|
82
85
|
if (!fragmentInMessage) {
|
|
83
86
|
message += `\nevaluating: ${fragment}`;
|
|
84
87
|
}
|
|
85
88
|
if (typeof source === "object" && source.url) {
|
|
86
|
-
message += `\n at ${source.url.href}:${
|
|
89
|
+
message += `\n at ${source.url.href}:${line}:${start.column}`;
|
|
87
90
|
} else if (source.text.includes("\n")) {
|
|
88
|
-
message += `\n at line ${
|
|
91
|
+
message += `\n at line ${line}, column ${start.column}`;
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
|
package/src/runtime/evaluate.js
CHANGED
|
@@ -9,7 +9,7 @@ import { codeSymbol, scopeSymbol, sourceSymbol } from "./symbols.js";
|
|
|
9
9
|
* `this` should be the tree used as the context for the evaluation.
|
|
10
10
|
*
|
|
11
11
|
* @this {import("@weborigami/types").AsyncTree|null}
|
|
12
|
-
* @param {import("../../index.ts").
|
|
12
|
+
* @param {import("../../index.ts").AnnotatedCode} code
|
|
13
13
|
*/
|
|
14
14
|
export default async function evaluate(code) {
|
|
15
15
|
const tree = this;
|
|
@@ -20,7 +20,13 @@ export default async function evaluate(code) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
let evaluated;
|
|
23
|
-
const unevaluatedFns = [
|
|
23
|
+
const unevaluatedFns = [
|
|
24
|
+
ops.external,
|
|
25
|
+
ops.lambda,
|
|
26
|
+
ops.merge,
|
|
27
|
+
ops.object,
|
|
28
|
+
ops.literal,
|
|
29
|
+
];
|
|
24
30
|
if (unevaluatedFns.includes(code[0])) {
|
|
25
31
|
// Don't evaluate instructions, use as is.
|
|
26
32
|
evaluated = code;
|
|
@@ -48,15 +54,6 @@ export default async function evaluate(code) {
|
|
|
48
54
|
fn = await fn.unpack();
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
if (!Tree.isTreelike(fn)) {
|
|
52
|
-
const text = fn.toString?.() ?? codeFragment(code[0].location);
|
|
53
|
-
const error = new TypeError(
|
|
54
|
-
`Not a callable function or tree: ${text.slice(0, 80)}`
|
|
55
|
-
);
|
|
56
|
-
/** @type {any} */ (error).location = code.location;
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
57
|
// Execute the function or traverse the tree.
|
|
61
58
|
let result;
|
|
62
59
|
try {
|
|
@@ -5,7 +5,7 @@ import { evaluate } from "./internal.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Given parsed Origami code, return a function that executes that code.
|
|
7
7
|
*
|
|
8
|
-
* @param {import("../../index.js").
|
|
8
|
+
* @param {import("../../index.js").AnnotatedCode} code - parsed Origami expression
|
|
9
9
|
* @param {string} [name] - optional name of the function
|
|
10
10
|
*/
|
|
11
11
|
export function createExpressionFunction(code, name) {
|
|
@@ -34,11 +34,13 @@ export default async function expressionObject(entries, parent) {
|
|
|
34
34
|
|
|
35
35
|
let tree;
|
|
36
36
|
const eagerProperties = [];
|
|
37
|
+
const propertyIsEnumerable = {};
|
|
37
38
|
for (let [key, value] of entries) {
|
|
38
39
|
// Determine if we need to define a getter or a regular property. If the key
|
|
39
40
|
// has an extension, we need to define a getter. If the value is code (an
|
|
40
41
|
// array), we need to define a getter -- but if that code takes the form
|
|
41
|
-
// [ops.getter, <primitive>], we can define a
|
|
42
|
+
// [ops.getter, <primitive>] or [ops.literal, <value>], we can define a
|
|
43
|
+
// regular property.
|
|
42
44
|
let defineProperty;
|
|
43
45
|
const extname = extension.extname(key);
|
|
44
46
|
if (extname) {
|
|
@@ -48,6 +50,9 @@ export default async function expressionObject(entries, parent) {
|
|
|
48
50
|
} else if (value[0] === ops.getter && !(value[1] instanceof Array)) {
|
|
49
51
|
defineProperty = true;
|
|
50
52
|
value = value[1];
|
|
53
|
+
} else if (value[0] === ops.literal) {
|
|
54
|
+
defineProperty = true;
|
|
55
|
+
value = value[1];
|
|
51
56
|
} else {
|
|
52
57
|
defineProperty = false;
|
|
53
58
|
}
|
|
@@ -58,6 +63,7 @@ export default async function expressionObject(entries, parent) {
|
|
|
58
63
|
key = key.slice(1, -1);
|
|
59
64
|
enumerable = false;
|
|
60
65
|
}
|
|
66
|
+
propertyIsEnumerable[key] = enumerable;
|
|
61
67
|
|
|
62
68
|
if (defineProperty) {
|
|
63
69
|
// Define simple property
|
|
@@ -105,7 +111,7 @@ export default async function expressionObject(entries, parent) {
|
|
|
105
111
|
Object.defineProperty(object, symbols.keys, {
|
|
106
112
|
configurable: true,
|
|
107
113
|
enumerable: false,
|
|
108
|
-
value: () => keys(object, eagerProperties, entries),
|
|
114
|
+
value: () => keys(object, eagerProperties, propertyIsEnumerable, entries),
|
|
109
115
|
writable: true,
|
|
110
116
|
});
|
|
111
117
|
|
|
@@ -158,6 +164,8 @@ function entryKey(object, eagerProperties, entry) {
|
|
|
158
164
|
return trailingSlash.toggle(key, entryCreatesSubtree);
|
|
159
165
|
}
|
|
160
166
|
|
|
161
|
-
function keys(object, eagerProperties, entries) {
|
|
162
|
-
return entries
|
|
167
|
+
function keys(object, eagerProperties, propertyIsEnumerable, entries) {
|
|
168
|
+
return entries
|
|
169
|
+
.filter(([key]) => propertyIsEnumerable[key])
|
|
170
|
+
.map((entry) => entryKey(object, eagerProperties, entry));
|
|
163
171
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
isPlainObject,
|
|
3
3
|
isUnpackable,
|
|
4
4
|
merge,
|
|
5
|
+
setParent,
|
|
5
6
|
Tree,
|
|
6
7
|
} from "@weborigami/async-tree";
|
|
7
8
|
|
|
@@ -59,5 +60,6 @@ export default async function mergeTrees(...trees) {
|
|
|
59
60
|
|
|
60
61
|
// Merge the trees.
|
|
61
62
|
const result = merge(...unpacked);
|
|
63
|
+
setParent(result, this);
|
|
62
64
|
return result;
|
|
63
65
|
}
|