@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.
@@ -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(parseResult, location) {
12
- if (typeof parseResult === "object" && parseResult !== null && location) {
13
- parseResult.location = location;
14
- parseResult.source = codeFragment(location);
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 parseResult;
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 {import("../../index.ts").Code} code
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
- * @typedef {import("../../index.ts").Code} Code
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 {Code[]} chain
102
- * @returns
172
+ * @param {any[]} args
103
173
  */
104
- export function makeFunctionCall(target, chain, location) {
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
- // @ts-ignore
123
- fnCall =
124
- args[0] !== ops.traverse
125
- ? // Function call
126
- [value, ...args]
127
- : args.length > 1
128
- ? // Traverse
129
- [ops.traverse, value, ...args.slice(1)]
130
- : // Traverse without arguments equates to unpack
131
- [ops.unpack, value];
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
- annotate(fnCall, { start, source, end });
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
- value = fnCall;
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
- return value;
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(steps) {
199
- const [first, ...rest] = steps;
200
- let value = first;
201
- for (const args of rest) {
202
- value = [args, value];
203
- }
204
- return value;
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
+ }