@weborigami/language 0.1.0 → 0.2.1
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/package.json +6 -4
- package/src/compiler/compile.js +33 -15
- package/src/compiler/origami.pegjs +309 -195
- package/src/compiler/parse.js +2753 -1658
- package/src/compiler/parserHelpers.js +187 -45
- package/src/runtime/ops.js +246 -54
- package/test/cases/ReadMe.md +1 -0
- package/test/cases/conditionalExpression.yaml +101 -0
- package/test/cases/logicalAndExpression.yaml +146 -0
- package/test/cases/logicalOrExpression.yaml +145 -0
- package/test/cases/nullishCoalescingExpression.yaml +105 -0
- package/test/compiler/compile.test.js +3 -3
- package/test/compiler/parse.test.js +588 -359
- package/test/generated/conditionalExpression.test.js +58 -0
- package/test/generated/logicalAndExpression.test.js +80 -0
- package/test/generated/logicalOrExpression.test.js +78 -0
- package/test/generated/nullishCoalescingExpression.test.js +64 -0
- package/test/generator/generateTests.js +80 -0
- package/test/generator/oriEval.js +15 -0
- package/test/runtime/ops.test.js +242 -20
|
@@ -4,16 +4,26 @@ import * as ops from "../runtime/ops.js";
|
|
|
4
4
|
|
|
5
5
|
// Parser helpers
|
|
6
6
|
|
|
7
|
+
/** @typedef {import("../../index.ts").Code} Code */
|
|
8
|
+
|
|
9
|
+
// Marker for a reference that may be a builtin or a scope reference
|
|
10
|
+
export const undetermined = Symbol("undetermined");
|
|
11
|
+
|
|
12
|
+
const builtinRegex = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* If a parse result is an object that will be evaluated at runtime, attach the
|
|
9
16
|
* location of the source code that produced it for debugging and error messages.
|
|
17
|
+
*
|
|
18
|
+
* @param {Code} code
|
|
19
|
+
* @param {any} location
|
|
10
20
|
*/
|
|
11
|
-
export function annotate(
|
|
12
|
-
if (typeof
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
export function annotate(code, location) {
|
|
22
|
+
if (typeof code === "object" && code !== null && location) {
|
|
23
|
+
code.location = location;
|
|
24
|
+
code.source = codeFragment(location);
|
|
15
25
|
}
|
|
16
|
-
return
|
|
26
|
+
return code;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
/**
|
|
@@ -21,31 +31,51 @@ export function annotate(parseResult, location) {
|
|
|
21
31
|
* Rewrite any [ops.scope, key] calls to be [ops.inherited, key] to avoid
|
|
22
32
|
* infinite recursion.
|
|
23
33
|
*
|
|
24
|
-
* @param {
|
|
34
|
+
* @param {Code} code
|
|
25
35
|
* @param {string} key
|
|
26
36
|
*/
|
|
27
37
|
function avoidRecursivePropertyCalls(code, key) {
|
|
28
38
|
if (!(code instanceof Array)) {
|
|
29
39
|
return code;
|
|
30
40
|
}
|
|
41
|
+
/** @type {Code} */
|
|
31
42
|
let modified;
|
|
32
43
|
if (
|
|
33
44
|
code[0] === ops.scope &&
|
|
34
45
|
trailingSlash.remove(code[1]) === trailingSlash.remove(key)
|
|
35
46
|
) {
|
|
36
47
|
// Rewrite to avoid recursion
|
|
48
|
+
// @ts-ignore
|
|
37
49
|
modified = [ops.inherited, code[1]];
|
|
38
50
|
} else if (code[0] === ops.lambda && code[1].includes(key)) {
|
|
39
51
|
// Lambda that defines the key; don't rewrite
|
|
40
52
|
return code;
|
|
41
53
|
} else {
|
|
42
54
|
// Process any nested code
|
|
55
|
+
// @ts-ignore
|
|
43
56
|
modified = code.map((value) => avoidRecursivePropertyCalls(value, key));
|
|
44
57
|
}
|
|
45
58
|
annotate(modified, code.location);
|
|
46
59
|
return modified;
|
|
47
60
|
}
|
|
48
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Downgrade a potential builtin reference to a scope reference.
|
|
64
|
+
*
|
|
65
|
+
* @param {Code} code
|
|
66
|
+
*/
|
|
67
|
+
export function downgradeReference(code) {
|
|
68
|
+
if (code && code.length === 2 && code[0] === undetermined) {
|
|
69
|
+
/** @type {Code} */
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
const result = [ops.scope, code[1]];
|
|
72
|
+
annotate(result, code.location);
|
|
73
|
+
return result;
|
|
74
|
+
} else {
|
|
75
|
+
return code;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
49
79
|
// Return true if the code will generate an async object.
|
|
50
80
|
function isCodeForAsyncObject(code) {
|
|
51
81
|
if (!(code instanceof Array)) {
|
|
@@ -95,56 +125,124 @@ export function makeArray(entries) {
|
|
|
95
125
|
}
|
|
96
126
|
|
|
97
127
|
/**
|
|
98
|
-
*
|
|
128
|
+
* Create a chain of binary operators. The head is the first value, and the tail
|
|
129
|
+
* is an array of [operator, value] pairs.
|
|
99
130
|
*
|
|
131
|
+
* @param {Code} left
|
|
132
|
+
*/
|
|
133
|
+
export function makeBinaryOperation(left, [operatorToken, right]) {
|
|
134
|
+
const operators = {
|
|
135
|
+
"!=": ops.notEqual,
|
|
136
|
+
"!==": ops.notStrictEqual,
|
|
137
|
+
"%": ops.remainder,
|
|
138
|
+
"&": ops.bitwiseAnd,
|
|
139
|
+
"*": ops.multiplication,
|
|
140
|
+
"**": ops.exponentiation,
|
|
141
|
+
"+": ops.addition,
|
|
142
|
+
"-": ops.subtraction,
|
|
143
|
+
"/": ops.division,
|
|
144
|
+
"<": ops.lessThan,
|
|
145
|
+
"<<": ops.shiftLeft,
|
|
146
|
+
"<=": ops.lessThanOrEqual,
|
|
147
|
+
"==": ops.equal,
|
|
148
|
+
"===": ops.strictEqual,
|
|
149
|
+
">": ops.greaterThan,
|
|
150
|
+
">=": ops.greaterThanOrEqual,
|
|
151
|
+
">>": ops.shiftRightSigned,
|
|
152
|
+
">>>": ops.shiftRightUnsigned,
|
|
153
|
+
"^": ops.bitwiseXor,
|
|
154
|
+
"|": ops.bitwiseOr,
|
|
155
|
+
};
|
|
156
|
+
const op = operators[operatorToken];
|
|
157
|
+
|
|
158
|
+
/** @type {Code} */
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
const value = [op, left, right];
|
|
161
|
+
value.location = {
|
|
162
|
+
source: left.location.source,
|
|
163
|
+
start: left.location.start,
|
|
164
|
+
end: right.location.end,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
100
171
|
* @param {Code} target
|
|
101
|
-
* @param {
|
|
102
|
-
* @returns
|
|
172
|
+
* @param {any[]} args
|
|
103
173
|
*/
|
|
104
|
-
export function
|
|
174
|
+
export function makeCall(target, args) {
|
|
105
175
|
if (!(target instanceof Array)) {
|
|
106
176
|
const error = new SyntaxError(`Can't call this like a function: ${target}`);
|
|
107
|
-
/** @type {any} */ (error).location = location;
|
|
177
|
+
/** @type {any} */ (error).location = /** @type {any} */ (target).location;
|
|
108
178
|
throw error;
|
|
109
179
|
}
|
|
110
180
|
|
|
111
|
-
let value = target;
|
|
112
181
|
const source = target.location.source;
|
|
113
|
-
// The chain is an array of arguments (which are themselves arrays). We
|
|
114
|
-
// successively apply the top-level elements of that chain to build up the
|
|
115
|
-
// function composition.
|
|
116
182
|
let start = target.location.start;
|
|
117
183
|
let end = target.location.end;
|
|
118
|
-
for (const args of chain) {
|
|
119
|
-
/** @type {Code} */
|
|
120
|
-
let fnCall;
|
|
121
184
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Create a location spanning the newly-constructed function call.
|
|
134
|
-
if (args instanceof Array) {
|
|
135
|
-
if (args.location) {
|
|
136
|
-
end = args.location.end;
|
|
137
|
-
} else {
|
|
138
|
-
throw "Internal parser error: no location for function call argument";
|
|
185
|
+
let fnCall;
|
|
186
|
+
if (args[0] === ops.traverse) {
|
|
187
|
+
let tree = target;
|
|
188
|
+
|
|
189
|
+
if (tree[0] === undetermined) {
|
|
190
|
+
// In a traversal, downgrade ops.builtin references to ops.scope
|
|
191
|
+
tree = downgradeReference(tree);
|
|
192
|
+
if (tree[0] === ops.scope && !trailingSlash.has(tree[1])) {
|
|
193
|
+
// Target didn't parse with a trailing slash; add one
|
|
194
|
+
tree[1] = trailingSlash.add(tree[1]);
|
|
139
195
|
}
|
|
140
196
|
}
|
|
141
197
|
|
|
142
|
-
|
|
198
|
+
if (args.length > 1) {
|
|
199
|
+
// Regular traverse
|
|
200
|
+
const keys = args.slice(1);
|
|
201
|
+
fnCall = [ops.traverse, tree, ...keys];
|
|
202
|
+
} else {
|
|
203
|
+
// Traverse without arguments equates to unpack
|
|
204
|
+
fnCall = [ops.unpack, tree];
|
|
205
|
+
}
|
|
206
|
+
} else if (args[0] === ops.template) {
|
|
207
|
+
// Tagged template
|
|
208
|
+
fnCall = [upgradeReference(target), ...args.slice(1)];
|
|
209
|
+
} else {
|
|
210
|
+
// Function call with explicit or implicit parentheses
|
|
211
|
+
fnCall = [upgradeReference(target), ...args];
|
|
212
|
+
}
|
|
143
213
|
|
|
144
|
-
|
|
214
|
+
// Create a location spanning the newly-constructed function call.
|
|
215
|
+
if (args instanceof Array) {
|
|
216
|
+
// @ts-ignore
|
|
217
|
+
end = args.location?.end ?? args.at(-1)?.location?.end;
|
|
218
|
+
if (end === undefined) {
|
|
219
|
+
throw "Internal parser error: no location for function call argument";
|
|
220
|
+
}
|
|
145
221
|
}
|
|
146
222
|
|
|
147
|
-
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
annotate(fnCall, { start, source, end });
|
|
225
|
+
|
|
226
|
+
return fnCall;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* For functions that short-circuit arguments, we need to defer evaluation of
|
|
231
|
+
* the arguments until the function is called. Exception: if the argument is a
|
|
232
|
+
* literal, we leave it alone.
|
|
233
|
+
*
|
|
234
|
+
* @param {any[]} args
|
|
235
|
+
*/
|
|
236
|
+
export function makeDeferredArguments(args) {
|
|
237
|
+
return args.map((arg) => {
|
|
238
|
+
if (arg instanceof Array && arg[0] === ops.literal) {
|
|
239
|
+
return arg;
|
|
240
|
+
}
|
|
241
|
+
const fn = [ops.lambda, [], arg];
|
|
242
|
+
// @ts-ignore
|
|
243
|
+
annotate(fn, arg.location);
|
|
244
|
+
return fn;
|
|
245
|
+
});
|
|
148
246
|
}
|
|
149
247
|
|
|
150
248
|
export function makeObject(entries, op) {
|
|
@@ -195,13 +293,15 @@ export function makeObject(entries, op) {
|
|
|
195
293
|
}
|
|
196
294
|
|
|
197
295
|
// Similar to a function call, but the order is reversed.
|
|
198
|
-
export function makePipeline(
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
296
|
+
export function makePipeline(arg, fn) {
|
|
297
|
+
const upgraded = upgradeReference(fn);
|
|
298
|
+
const result = makeCall(upgraded, [arg]);
|
|
299
|
+
const source = fn.location.source;
|
|
300
|
+
let start = arg.location.start;
|
|
301
|
+
let end = fn.location.end;
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
annotate(result, { start, source, end });
|
|
304
|
+
return result;
|
|
205
305
|
}
|
|
206
306
|
|
|
207
307
|
// Define a property on an object.
|
|
@@ -210,6 +310,21 @@ export function makeProperty(key, value) {
|
|
|
210
310
|
return [key, modified];
|
|
211
311
|
}
|
|
212
312
|
|
|
313
|
+
export function makeReference(identifier) {
|
|
314
|
+
// We can't know for sure that an identifier is a builtin reference until we
|
|
315
|
+
// see whether it's being called as a function.
|
|
316
|
+
let op;
|
|
317
|
+
if (builtinRegex.test(identifier)) {
|
|
318
|
+
op = identifier.endsWith(":")
|
|
319
|
+
? // Namespace is always a builtin reference
|
|
320
|
+
ops.builtin
|
|
321
|
+
: undetermined;
|
|
322
|
+
} else {
|
|
323
|
+
op = ops.scope;
|
|
324
|
+
}
|
|
325
|
+
return [op, identifier];
|
|
326
|
+
}
|
|
327
|
+
|
|
213
328
|
export function makeTemplate(op, head, tail) {
|
|
214
329
|
const strings = [head];
|
|
215
330
|
const values = [];
|
|
@@ -219,3 +334,30 @@ export function makeTemplate(op, head, tail) {
|
|
|
219
334
|
}
|
|
220
335
|
return [op, [ops.literal, strings], ...values];
|
|
221
336
|
}
|
|
337
|
+
|
|
338
|
+
export function makeUnaryOperation(operator, value) {
|
|
339
|
+
const operators = {
|
|
340
|
+
"!": ops.logicalNot,
|
|
341
|
+
"+": ops.unaryPlus,
|
|
342
|
+
"-": ops.unaryMinus,
|
|
343
|
+
"~": ops.bitwiseNot,
|
|
344
|
+
};
|
|
345
|
+
return [operators[operator], value];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Upgrade a potential builtin reference to an actual builtin reference.
|
|
350
|
+
*
|
|
351
|
+
* @param {Code} code
|
|
352
|
+
*/
|
|
353
|
+
export function upgradeReference(code) {
|
|
354
|
+
if (code.length === 2 && code[0] === undetermined) {
|
|
355
|
+
/** @type {Code} */
|
|
356
|
+
// @ts-ignore
|
|
357
|
+
const result = [ops.builtin, code[1]];
|
|
358
|
+
annotate(result, code.location);
|
|
359
|
+
return result;
|
|
360
|
+
} else {
|
|
361
|
+
return code;
|
|
362
|
+
}
|
|
363
|
+
}
|