@graffiticode/basis 1.1.0 → 1.3.0
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/package.json +1 -1
- package/src/#compiler.js# +739 -0
- package/src/compiler.js +9 -2
package/package.json
CHANGED
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
/* Copyright (c) 2021, ARTCOMPILER INC */
|
|
2
|
+
import {assert, message, messages, reserveCodeRange} from "./share.js";
|
|
3
|
+
reserveCodeRange(1000, 1999, "compile");
|
|
4
|
+
messages[1001] = "Node ID %1 not found in pool.";
|
|
5
|
+
messages[1002] = "Invalid tag in node with Node ID %1.";
|
|
6
|
+
messages[1003] = "No async callback provided.";
|
|
7
|
+
messages[1004] = "No visitor method defined for '%1'.";
|
|
8
|
+
|
|
9
|
+
function error(msg, arg) {
|
|
10
|
+
return msg + arg;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function newNode(tag, elts) {
|
|
14
|
+
return {
|
|
15
|
+
tag: tag,
|
|
16
|
+
elts: elts,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ASYNC = true;
|
|
21
|
+
|
|
22
|
+
class Visitor {
|
|
23
|
+
constructor(code) {
|
|
24
|
+
this.nodePool = code;
|
|
25
|
+
this.root = code.root;
|
|
26
|
+
}
|
|
27
|
+
visit(nid, options, resume) {
|
|
28
|
+
assert(nid);
|
|
29
|
+
let node;
|
|
30
|
+
if (typeof nid === "object") {
|
|
31
|
+
node = nid;
|
|
32
|
+
} else {
|
|
33
|
+
node = this.nodePool[nid];
|
|
34
|
+
}
|
|
35
|
+
assert(node && node.tag && node.elts, "2000: Visitor.visit() tag=" + node.tag + " elts= " + JSON.stringify(node.elts));
|
|
36
|
+
assert(this[node.tag], "2000: Visitor function not defined for: " + node.tag);
|
|
37
|
+
assert(typeof resume === "function", message(1003));
|
|
38
|
+
if (!options.SYNC && ASYNC) {
|
|
39
|
+
setTimeout(() => this[node.tag](node, options, resume), 0);
|
|
40
|
+
} else {
|
|
41
|
+
this[node.tag](node, options, resume);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
node(nid) {
|
|
45
|
+
var n = this.nodePool[nid];
|
|
46
|
+
if (!nid) {
|
|
47
|
+
return null;
|
|
48
|
+
} else if (!n) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
var elts = [];
|
|
52
|
+
switch (n.tag) {
|
|
53
|
+
case "NULL":
|
|
54
|
+
break;
|
|
55
|
+
case "NUM":
|
|
56
|
+
case "STR":
|
|
57
|
+
case "IDENT":
|
|
58
|
+
case "BOOL":
|
|
59
|
+
elts[0] = n.elts[0];
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
for (var i=0; i < n.elts.length; i++) {
|
|
63
|
+
elts[i] = this.node(n.elts[i]);
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
tag: n.tag,
|
|
69
|
+
elts: elts,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class Checker extends Visitor {
|
|
75
|
+
constructor(nodePool) {
|
|
76
|
+
super(nodePool);
|
|
77
|
+
}
|
|
78
|
+
check(options, resume) {
|
|
79
|
+
const nid = this.root;
|
|
80
|
+
this.visit(nid, options, (err, data) => {
|
|
81
|
+
resume(err, data);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
PROG(node, options, resume) {
|
|
85
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
86
|
+
const err = [];
|
|
87
|
+
const val = node;
|
|
88
|
+
resume(err, val);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
EXPRS(node, options, resume) {
|
|
92
|
+
let err = [];
|
|
93
|
+
let val = [];
|
|
94
|
+
for (let elt of node.elts) {
|
|
95
|
+
this.visit(elt, options, (e0, v0) => {
|
|
96
|
+
err = err.concat(e0);
|
|
97
|
+
val = val.concat(v0);
|
|
98
|
+
if (val.length === node.elts.length) {
|
|
99
|
+
resume(err, val);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (node.elts.length === 0) {
|
|
104
|
+
resume(err, val);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
NUM(node, options, resume) {
|
|
108
|
+
const err = [];
|
|
109
|
+
const val = node;
|
|
110
|
+
resume(err, val);
|
|
111
|
+
}
|
|
112
|
+
LAMBDA(node, options, resume) {
|
|
113
|
+
const err = [];
|
|
114
|
+
const val = node;
|
|
115
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
116
|
+
resume(err, val);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
LIST(node, options, resume) {
|
|
120
|
+
const err = [];
|
|
121
|
+
const val = node;
|
|
122
|
+
if (node.elts.length === 0) {
|
|
123
|
+
resume(err, val);
|
|
124
|
+
} else {
|
|
125
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
126
|
+
resume(err, val);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
IDENT(node, options, resume) {
|
|
131
|
+
const err = [];
|
|
132
|
+
const val = node;
|
|
133
|
+
resume(err, val);
|
|
134
|
+
}
|
|
135
|
+
STR(node, options, resume) {
|
|
136
|
+
const err = [];
|
|
137
|
+
const val = node;
|
|
138
|
+
resume(err, val);
|
|
139
|
+
}
|
|
140
|
+
CONCAT(node, options, resume) {
|
|
141
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
142
|
+
const err = [];
|
|
143
|
+
const val = node;
|
|
144
|
+
resume(err, val);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
ADD(node, options, resume) {
|
|
148
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
149
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
150
|
+
if (isNaN(+val1)) {
|
|
151
|
+
err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
|
|
152
|
+
}
|
|
153
|
+
if (isNaN(+val2)) {
|
|
154
|
+
err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
|
|
155
|
+
}
|
|
156
|
+
const err = [].concat(err1).concat(err2);
|
|
157
|
+
const val = node;
|
|
158
|
+
resume(err, val);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
BOOL(node, options, resume) {
|
|
163
|
+
const err = [];
|
|
164
|
+
const val = node;
|
|
165
|
+
resume(err, val);
|
|
166
|
+
}
|
|
167
|
+
NULL(node, options, resume) {
|
|
168
|
+
const err = [];
|
|
169
|
+
const val = node;
|
|
170
|
+
resume(err, val);
|
|
171
|
+
}
|
|
172
|
+
RECORD(node, options, resume) {
|
|
173
|
+
const err = [];
|
|
174
|
+
const val = node;
|
|
175
|
+
resume(err, val);
|
|
176
|
+
}
|
|
177
|
+
BINDING(node, options, resume) {
|
|
178
|
+
const err = [];
|
|
179
|
+
const val = node;
|
|
180
|
+
resume(err, val);
|
|
181
|
+
}
|
|
182
|
+
MUL(node, options, resume) {
|
|
183
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
184
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
185
|
+
if (isNaN(+val1)) {
|
|
186
|
+
err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
|
|
187
|
+
}
|
|
188
|
+
if (isNaN(+val2)) {
|
|
189
|
+
err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
|
|
190
|
+
}
|
|
191
|
+
const err = [].concat(err1).concat(err2);
|
|
192
|
+
const val = node;
|
|
193
|
+
resume(err, val);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
POW(node, options, resume) {
|
|
198
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
199
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
200
|
+
if (isNaN(+val1)) {
|
|
201
|
+
err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
|
|
202
|
+
}
|
|
203
|
+
if (isNaN(+val2)) {
|
|
204
|
+
err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
|
|
205
|
+
}
|
|
206
|
+
const err = [].concat(err1).concat(err2);
|
|
207
|
+
const val = node;
|
|
208
|
+
resume(err, val);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
VAL(node, options, resume) {
|
|
213
|
+
const err = [];
|
|
214
|
+
const val = node;
|
|
215
|
+
resume(err, val);
|
|
216
|
+
}
|
|
217
|
+
KEY(node, options, resume) {
|
|
218
|
+
const err = [];
|
|
219
|
+
const val = node;
|
|
220
|
+
resume(err, val);
|
|
221
|
+
}
|
|
222
|
+
LEN(node, options, resume) {
|
|
223
|
+
const err = [];
|
|
224
|
+
const val = node;
|
|
225
|
+
resume(err, val);
|
|
226
|
+
}
|
|
227
|
+
ARG(node, options, resume) {
|
|
228
|
+
const err = [];
|
|
229
|
+
const val = node;
|
|
230
|
+
resume(err, val);
|
|
231
|
+
}
|
|
232
|
+
DATA(node, options, resume) {
|
|
233
|
+
const err = [];
|
|
234
|
+
const val = node;
|
|
235
|
+
resume(err, val);
|
|
236
|
+
}
|
|
237
|
+
PAREN(node, options, resume) {
|
|
238
|
+
const err = [];
|
|
239
|
+
const val = node;
|
|
240
|
+
resume(err, val);
|
|
241
|
+
}
|
|
242
|
+
APPLY(node, options, resume) {
|
|
243
|
+
const err = [];
|
|
244
|
+
const val = node;
|
|
245
|
+
resume(err, val);
|
|
246
|
+
}
|
|
247
|
+
MAP(node, options, resume) {
|
|
248
|
+
const err = [];
|
|
249
|
+
const val = node;
|
|
250
|
+
resume(err, val);
|
|
251
|
+
}
|
|
252
|
+
STYLE(node, options, resume) {
|
|
253
|
+
const err = [];
|
|
254
|
+
const val = node;
|
|
255
|
+
resume(err, val);
|
|
256
|
+
}
|
|
257
|
+
CASE(node, options, resume) {
|
|
258
|
+
const err = [];
|
|
259
|
+
const val = node;
|
|
260
|
+
resume(err, val);
|
|
261
|
+
}
|
|
262
|
+
OF(node, options, resume) {
|
|
263
|
+
const err = [];
|
|
264
|
+
const val = node;
|
|
265
|
+
resume(err, val);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function enterEnv(ctx, name, paramc) {
|
|
270
|
+
if (!ctx.env) {
|
|
271
|
+
ctx.env = [];
|
|
272
|
+
}
|
|
273
|
+
// recursion guard
|
|
274
|
+
if (ctx.env.length > 380) {
|
|
275
|
+
//return; // just stop recursing
|
|
276
|
+
throw new Error("runaway recursion");
|
|
277
|
+
}
|
|
278
|
+
ctx.env.push({
|
|
279
|
+
name: name,
|
|
280
|
+
paramc: paramc,
|
|
281
|
+
lexicon: {},
|
|
282
|
+
pattern: [],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function exitEnv(ctx) {
|
|
286
|
+
ctx.env.pop();
|
|
287
|
+
}
|
|
288
|
+
function findWord(ctx, lexeme) {
|
|
289
|
+
let env = ctx.env;
|
|
290
|
+
if (!env) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
for (var i = env.length-1; i >= 0; i--) {
|
|
294
|
+
var word = env[i].lexicon[lexeme];
|
|
295
|
+
if (word) {
|
|
296
|
+
return word;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function addWord(ctx, lexeme, entry) {
|
|
302
|
+
topEnv(ctx).lexicon[lexeme] = entry;
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
function topEnv(ctx) {
|
|
306
|
+
return ctx.env[ctx.env.length-1]
|
|
307
|
+
}
|
|
308
|
+
export class Transformer extends Visitor {
|
|
309
|
+
constructor(nodePool) {
|
|
310
|
+
super(nodePool);
|
|
311
|
+
this.patternNodePool = ['unused'];
|
|
312
|
+
this.patternNodeMap = {};
|
|
313
|
+
}
|
|
314
|
+
transform(options, resume) {
|
|
315
|
+
const nid = this.root;
|
|
316
|
+
this.visit(nid, options, (err, data) => {
|
|
317
|
+
resume(err, data);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
internPattern(n) {
|
|
321
|
+
if (!n) {
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
const nodeMap = this.patternNodeMap;
|
|
325
|
+
const nodePool = this.patternNodePool;
|
|
326
|
+
const tag = n.tag;
|
|
327
|
+
const elts_nids = [];
|
|
328
|
+
const count = n.elts.length;
|
|
329
|
+
let elts = "";
|
|
330
|
+
for (let i = 0; i < count; i++) {
|
|
331
|
+
if (typeof n.elts[i] === "object") {
|
|
332
|
+
n.elts[i] = this.internPattern(n.elts[i]);
|
|
333
|
+
}
|
|
334
|
+
elts += n.elts[i];
|
|
335
|
+
}
|
|
336
|
+
const key = tag+count+elts;
|
|
337
|
+
let nid = nodeMap[key];
|
|
338
|
+
if (nid === void 0) {
|
|
339
|
+
nodePool.push({tag: tag, elts: n.elts});
|
|
340
|
+
nid = nodePool.length - 1;
|
|
341
|
+
nodeMap[key] = nid;
|
|
342
|
+
if (n.coord) {
|
|
343
|
+
ctx.state.coords[nid] = n.coord;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return nid;
|
|
347
|
+
}
|
|
348
|
+
match(options, patterns, node) {
|
|
349
|
+
if (patterns.size === 0 || node === undefined) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
let matches = patterns.filter((pattern) => {
|
|
353
|
+
if (pattern.tag === undefined || node.tag === undefined) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
const patternNid = this.internPattern(pattern);
|
|
357
|
+
if (patternNid === this.internPattern(node) ||
|
|
358
|
+
patternNid === this.internPattern(newNode('IDENT', ['_']))) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
if (pattern.tag === node.tag) {
|
|
362
|
+
if (pattern.elts.length === node.elts.length) {
|
|
363
|
+
// Same number of args, so see if each matches.
|
|
364
|
+
return pattern.elts.every((arg, i) => {
|
|
365
|
+
if (pattern.tag === 'VAR') {
|
|
366
|
+
if (arg === node.elts[i]) {
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
let result = this.match(options, [arg], node.elts[i]);
|
|
372
|
+
return result.length === 1;
|
|
373
|
+
});
|
|
374
|
+
} else if (pattern.elts.length < node.elts.length) {
|
|
375
|
+
// Different number of args, then see if there is a wildcard match.
|
|
376
|
+
let nargs = node.elts.slice(1);
|
|
377
|
+
if (pattern.elts.length === 2) {
|
|
378
|
+
// Binary node pattern
|
|
379
|
+
let result = (
|
|
380
|
+
this.match(options, [pattern.elts[0]], node.elts[0]).length > 0 &&
|
|
381
|
+
this.match(options, [pattern.elts[1]], newNode(node.tag, nargs)).length > 0
|
|
382
|
+
// Match rest of the node against the second pattern argument.
|
|
383
|
+
);
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
});
|
|
390
|
+
// if (true || matches.length > 0) {
|
|
391
|
+
// console.log("match() node: " + JSON.stringify(node, null, 2));
|
|
392
|
+
// console.log("match() matches: " + JSON.stringify(matches, null, 2));
|
|
393
|
+
// }
|
|
394
|
+
return matches;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
PROG(node, options, resume) {
|
|
399
|
+
if (!options) {
|
|
400
|
+
options = {};
|
|
401
|
+
}
|
|
402
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
403
|
+
const err = e0;
|
|
404
|
+
const val = v0.pop(); // Return the value of the last expression.
|
|
405
|
+
resume(err, val);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
EXPRS(node, options, resume) {
|
|
409
|
+
let err = [];
|
|
410
|
+
let val = [];
|
|
411
|
+
for (let elt of node.elts) {
|
|
412
|
+
this.visit(elt, options, (e0, v0) => {
|
|
413
|
+
err = err.concat(e0);
|
|
414
|
+
val.push(v0);
|
|
415
|
+
if (val.length === node.elts.length) {
|
|
416
|
+
resume(err, val);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
if (node.elts.length === 0) {
|
|
421
|
+
val.push("");
|
|
422
|
+
resume(err, val);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
NUM(node, options, resume) {
|
|
426
|
+
const err = [];
|
|
427
|
+
const val = +node.elts[0];
|
|
428
|
+
resume(err, val);
|
|
429
|
+
}
|
|
430
|
+
LAMBDA(node, options, resume) {
|
|
431
|
+
// Return a function value.
|
|
432
|
+
this.visit(node.elts[0], options, (err0, params) => {
|
|
433
|
+
let args = [].concat(options.args);
|
|
434
|
+
enterEnv(options, "lambda", params.length);
|
|
435
|
+
params.forEach((param, i) => {
|
|
436
|
+
// let inits = this.nodePool[node.elts[3]].elts;
|
|
437
|
+
if (args[i]) {
|
|
438
|
+
// Got an arg so use it.
|
|
439
|
+
addWord(options, param, {
|
|
440
|
+
name: param,
|
|
441
|
+
val: args[i],
|
|
442
|
+
});
|
|
443
|
+
// } else {
|
|
444
|
+
// // Don't got an arg so use the init.
|
|
445
|
+
// this.visit(inits[i], options, (err, val) => {
|
|
446
|
+
// addWord(options, param, {
|
|
447
|
+
// name: param,
|
|
448
|
+
// val: val,
|
|
449
|
+
// });
|
|
450
|
+
// });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
this.visit(node.elts[1], options, (err, val) => {
|
|
454
|
+
exitEnv(options);
|
|
455
|
+
resume([].concat(err0).concat(err).concat(err), val)
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
LIST(node, options, resume) {
|
|
460
|
+
let err = [];
|
|
461
|
+
let val = [];
|
|
462
|
+
if (node.elts.length === 0) {
|
|
463
|
+
resume(err, val);
|
|
464
|
+
} else {
|
|
465
|
+
for (let elt of node.elts) {
|
|
466
|
+
this.visit(elt, options, (e0, v0) => {
|
|
467
|
+
err = err.concat(e0);
|
|
468
|
+
val.push(v0);
|
|
469
|
+
if (val.length === node.elts.length) {
|
|
470
|
+
resume(err, val);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
IDENT(node, options, resume) {
|
|
477
|
+
let word = findWord(options, node.elts[0]);
|
|
478
|
+
const err = [];
|
|
479
|
+
const val = word && word.val || node.elts[0];
|
|
480
|
+
resume(err, val);
|
|
481
|
+
}
|
|
482
|
+
STR(node, options, resume) {
|
|
483
|
+
const err = [];
|
|
484
|
+
const val = node.elts[0];
|
|
485
|
+
resume(err, val);
|
|
486
|
+
}
|
|
487
|
+
CONCAT(node, options, resume) {
|
|
488
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
489
|
+
const err = [];
|
|
490
|
+
const val = ([].concat(v0)).join('');
|
|
491
|
+
resume(err, val);
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
ADD(node, options, resume) {
|
|
495
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
496
|
+
this.visit(node.elts[1], options, (e1, v1) => {
|
|
497
|
+
const err = [].concat(e0).concat(e1);
|
|
498
|
+
const val = +v0 + +v1;
|
|
499
|
+
resume(err, val);
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
BOOL(node, options, resume) {
|
|
504
|
+
const err = [];
|
|
505
|
+
const val = node;
|
|
506
|
+
resume(err, val);
|
|
507
|
+
}
|
|
508
|
+
NULL(node, options, resume) {
|
|
509
|
+
const err = [];
|
|
510
|
+
const val = null;
|
|
511
|
+
resume(err, val);
|
|
512
|
+
}
|
|
513
|
+
BINDING(node, options, resume) {
|
|
514
|
+
const err = [];
|
|
515
|
+
const val = node;
|
|
516
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
517
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
518
|
+
resume([].concat(err1).concat(err2), {key: val1, val: val2});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
RECORD(node, options, resume) {
|
|
523
|
+
let err = [];
|
|
524
|
+
let val = {};
|
|
525
|
+
let len = 0;
|
|
526
|
+
if (node.elts.length === 0) {
|
|
527
|
+
resume(err, val);
|
|
528
|
+
} else {
|
|
529
|
+
for (let elt of node.elts.reverse()) {
|
|
530
|
+
// For historical reasons, the bindings are reversed in the AST.
|
|
531
|
+
this.visit(elt, options, (e0, v0) => {
|
|
532
|
+
err = err.concat(e0);
|
|
533
|
+
val[v0.key] = v0.val;
|
|
534
|
+
if (++len === node.elts.length) {
|
|
535
|
+
resume(err, val);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
MUL(node, options, resume) {
|
|
542
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
543
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
544
|
+
if (isNaN(+val1)) {
|
|
545
|
+
err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
|
|
546
|
+
}
|
|
547
|
+
if (isNaN(+val2)) {
|
|
548
|
+
err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
|
|
549
|
+
}
|
|
550
|
+
const err = [].concat(err1).concat(err2);
|
|
551
|
+
const val = +val1 * +val2;
|
|
552
|
+
resume(err, val);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
POW(node, options, resume) {
|
|
557
|
+
this.visit(node.elts[0], options, (err1, val1) => {
|
|
558
|
+
this.visit(node.elts[1], options, (err2, val2) => {
|
|
559
|
+
if (isNaN(+val1)) {
|
|
560
|
+
err1 = err1.concat(error("Argument must be a number.", node.elts[0]));
|
|
561
|
+
}
|
|
562
|
+
if (isNaN(+val2)) {
|
|
563
|
+
err2 = err2.concat(error("Argument must be a number.", node.elts[1]));
|
|
564
|
+
}
|
|
565
|
+
const err = [].concat(err1).concat(err2);
|
|
566
|
+
const val = node;
|
|
567
|
+
resume(err, val);
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
VAL(node, options, resume) {
|
|
572
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
573
|
+
this.visit(node.elts[1], options, (e1, v1) => {
|
|
574
|
+
const err = [].concat(e0).concat(e1);
|
|
575
|
+
const val = v1[v0];
|
|
576
|
+
resume(err, val);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
KEY(node, options, resume) {
|
|
581
|
+
const err = [];
|
|
582
|
+
const val = node;
|
|
583
|
+
resume(err, val);
|
|
584
|
+
}
|
|
585
|
+
LEN(node, options, resume) {
|
|
586
|
+
const err = [];
|
|
587
|
+
const val = node;
|
|
588
|
+
resume(err, val);
|
|
589
|
+
}
|
|
590
|
+
ARG(node, options, resume) {
|
|
591
|
+
const err = [];
|
|
592
|
+
const val = node;
|
|
593
|
+
resume(err, val);
|
|
594
|
+
}
|
|
595
|
+
DATA(node, options, resume) {
|
|
596
|
+
if (options.data && Object.keys(options.data).length !== 0) {
|
|
597
|
+
// Got external data, so use it.
|
|
598
|
+
const err = [];
|
|
599
|
+
const val = options.data;
|
|
600
|
+
resume(err, val);
|
|
601
|
+
} else {
|
|
602
|
+
// Otherwise, use the default data.
|
|
603
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
604
|
+
const err = e0;
|
|
605
|
+
const val = v0;
|
|
606
|
+
resume(err, val);
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
PAREN(node, options, resume) {
|
|
611
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
612
|
+
const err = [].concat(e0);
|
|
613
|
+
const val = v0;
|
|
614
|
+
resume(err, val);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
APPLY(node, options, resume) {
|
|
618
|
+
// Apply a function to arguments.
|
|
619
|
+
this.visit(node.elts[1], options, (e1, v1) => {
|
|
620
|
+
options.args = v1;
|
|
621
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
622
|
+
//exitEnv(options);
|
|
623
|
+
const err = [].concat(e1).concat(e0);
|
|
624
|
+
const val = v0;
|
|
625
|
+
resume(err, val);
|
|
626
|
+
});
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
MAP(node, options, resume) {
|
|
630
|
+
this.visit(node.elts[1], options, (e1, v1) => {
|
|
631
|
+
let err = [];
|
|
632
|
+
let val = [];
|
|
633
|
+
v1.forEach(args => {
|
|
634
|
+
options.args = args;
|
|
635
|
+
options = JSON.parse(JSON.stringify(options)); // Copy option arg support async.
|
|
636
|
+
this.visit(node.elts[0], options, (e0, v0) => {
|
|
637
|
+
val.push(v0);
|
|
638
|
+
err = err.concat(e0);
|
|
639
|
+
if (val.length === v1.length) {
|
|
640
|
+
resume(err, val);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
STYLE(node, options, resume) {
|
|
647
|
+
const err = [];
|
|
648
|
+
const val = node;
|
|
649
|
+
resume(err, val);
|
|
650
|
+
}
|
|
651
|
+
CASE(node, options, resume) {
|
|
652
|
+
// FIXME this isn't ASYNC compatible
|
|
653
|
+
options.SYNC = true;
|
|
654
|
+
this.visit(node.elts[0], options, (err, e0) => {
|
|
655
|
+
const e0Node = this.node(node.elts[0]);
|
|
656
|
+
const expr = (e0Node.tag === 'NUM' || e0Node.tag === 'NUM') && e0Node || {tag: 'STR', elts: [`${e0}`]};
|
|
657
|
+
let foundMatch = false;
|
|
658
|
+
const patterns = [];
|
|
659
|
+
for (var i = 1; i < node.elts.length; i++) {
|
|
660
|
+
this.visit(node.elts[i], options, (err, val) => {
|
|
661
|
+
if (this.match(options, [this.node(node.elts[i]).elts[0]], expr).length) {
|
|
662
|
+
this.visit(val.exprElt, options, resume);
|
|
663
|
+
foundMatch = true;
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
if (foundMatch) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (!foundMatch) {
|
|
671
|
+
resume([], {})
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
options.SYNC = false;
|
|
675
|
+
}
|
|
676
|
+
OF(node, options, resume) {
|
|
677
|
+
this.visit(node.elts[0], options, (err0, pattern) => {
|
|
678
|
+
resume([].concat(err0), {
|
|
679
|
+
pattern: pattern,
|
|
680
|
+
exprElt: node.elts[1],
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export class Renderer {
|
|
687
|
+
constructor(data) {
|
|
688
|
+
this.data = data;
|
|
689
|
+
}
|
|
690
|
+
render(options, resume) {
|
|
691
|
+
// Do some rendering here.
|
|
692
|
+
const err = [];
|
|
693
|
+
const val = this.data;
|
|
694
|
+
resume(err, val);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export class Compiler {
|
|
699
|
+
constructor(config) {
|
|
700
|
+
this.langID = config.langID;
|
|
701
|
+
this.version = config.version;
|
|
702
|
+
this.Checker = config.Checker || Checker;
|
|
703
|
+
this.Transformer = config.Transformer || Transformer;
|
|
704
|
+
this.Renderer = config.Renderer || Renderer;
|
|
705
|
+
}
|
|
706
|
+
compile(code, data, config, resume) {
|
|
707
|
+
// Compiler takes an AST in the form of a node pool (code) and transforms it
|
|
708
|
+
// into an object to be rendered on the client by the viewer for this
|
|
709
|
+
// language.
|
|
710
|
+
try {
|
|
711
|
+
let options = {
|
|
712
|
+
data: data,
|
|
713
|
+
config: config,
|
|
714
|
+
result: '',
|
|
715
|
+
};
|
|
716
|
+
const checker = new this.Checker(code);
|
|
717
|
+
checker.check(options, (err, val) => {
|
|
718
|
+
const transformer = new this.Transformer(code);
|
|
719
|
+
transformer.transform(options, (err, val) => {
|
|
720
|
+
if (err && err.length) {
|
|
721
|
+
resume(err, val);
|
|
722
|
+
} else {
|
|
723
|
+
const renderer = new this.Renderer(val);
|
|
724
|
+
renderer.render(options, (err, val) => {
|
|
725
|
+
resume(err, val);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
} catch (x) {
|
|
731
|
+
console.log("ERROR with code");
|
|
732
|
+
console.log(x.stack);
|
|
733
|
+
resume([{
|
|
734
|
+
statusCode: 500,
|
|
735
|
+
error: "Compiler error"
|
|
736
|
+
}]);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
package/src/compiler.js
CHANGED
|
@@ -100,6 +100,9 @@ export class Checker extends Visitor {
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
if (node.elts.length === 0) {
|
|
104
|
+
resume(err, val);
|
|
105
|
+
}
|
|
103
106
|
}
|
|
104
107
|
NUM(node, options, resume) {
|
|
105
108
|
const err = [];
|
|
@@ -414,6 +417,10 @@ export class Transformer extends Visitor {
|
|
|
414
417
|
}
|
|
415
418
|
});
|
|
416
419
|
}
|
|
420
|
+
if (node.elts.length === 0) {
|
|
421
|
+
val.push("");
|
|
422
|
+
resume(err, val);
|
|
423
|
+
}
|
|
417
424
|
}
|
|
418
425
|
NUM(node, options, resume) {
|
|
419
426
|
const err = [];
|
|
@@ -495,7 +502,7 @@ export class Transformer extends Visitor {
|
|
|
495
502
|
}
|
|
496
503
|
BOOL(node, options, resume) {
|
|
497
504
|
const err = [];
|
|
498
|
-
const val = node;
|
|
505
|
+
const val = node.elts[0];
|
|
499
506
|
resume(err, val);
|
|
500
507
|
}
|
|
501
508
|
NULL(node, options, resume) {
|
|
@@ -586,7 +593,7 @@ export class Transformer extends Visitor {
|
|
|
586
593
|
resume(err, val);
|
|
587
594
|
}
|
|
588
595
|
DATA(node, options, resume) {
|
|
589
|
-
if (options.data && Object.keys(options.data).length
|
|
596
|
+
if (options.data && Object.keys(options.data).length !== 0) {
|
|
590
597
|
// Got external data, so use it.
|
|
591
598
|
const err = [];
|
|
592
599
|
const val = options.data;
|