@graffiticode/parser 0.1.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 +27 -0
- package/src/fold.js +235 -0
- package/src/index.js +1 -0
- package/src/parse.js +2177 -0
- package/src/parser.js +97 -0
- package/src/parser.js~ +97 -0
- package/src/parser.spec.js +175 -0
- package/src/parser.spec.js~ +175 -0
- package/src/testing/index.js +16 -0
package/src/parse.js
ADDED
|
@@ -0,0 +1,2177 @@
|
|
|
1
|
+
// Copyright 2021, ARTCOMPILER INC
|
|
2
|
+
/*
|
|
3
|
+
Error handling
|
|
4
|
+
-- every range points to a node
|
|
5
|
+
-- multiple ranges can point to the same node
|
|
6
|
+
-- errors are associated with ranges
|
|
7
|
+
-- errors are determined by nodes in context
|
|
8
|
+
|
|
9
|
+
-- parser returns an ast which might compile to a list of errors of the form
|
|
10
|
+
{from, to, message, severity}
|
|
11
|
+
-- compiler checkers report these errors in the err callback arg
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import assert from "assert";
|
|
15
|
+
import { folder } from "./fold.js";
|
|
16
|
+
|
|
17
|
+
let CodeMirror;
|
|
18
|
+
if (typeof CodeMirror === "undefined") {
|
|
19
|
+
CodeMirror = {
|
|
20
|
+
Pos: function () {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let window;
|
|
27
|
+
if (typeof window === "undefined") {
|
|
28
|
+
window = {};
|
|
29
|
+
window = {
|
|
30
|
+
gcexports: {
|
|
31
|
+
coords: {},
|
|
32
|
+
errors: []
|
|
33
|
+
},
|
|
34
|
+
isSynthetic: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ast module
|
|
39
|
+
|
|
40
|
+
export const Ast = (function () {
|
|
41
|
+
const ASSERT = true;
|
|
42
|
+
const assert = function (val, str) {
|
|
43
|
+
if (!ASSERT) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (str === undefined) {
|
|
47
|
+
str = "failed!";
|
|
48
|
+
}
|
|
49
|
+
if (!val) {
|
|
50
|
+
throw new Error(str);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const AstClass = function () { };
|
|
55
|
+
|
|
56
|
+
AstClass.prototype = {
|
|
57
|
+
intern,
|
|
58
|
+
node,
|
|
59
|
+
dump,
|
|
60
|
+
dumpAll,
|
|
61
|
+
poolToJSON,
|
|
62
|
+
number,
|
|
63
|
+
string,
|
|
64
|
+
name,
|
|
65
|
+
apply,
|
|
66
|
+
fold,
|
|
67
|
+
expr,
|
|
68
|
+
binaryExpr,
|
|
69
|
+
unaryExpr,
|
|
70
|
+
parenExpr,
|
|
71
|
+
prefixExpr,
|
|
72
|
+
lambda,
|
|
73
|
+
applyLate,
|
|
74
|
+
letDef,
|
|
75
|
+
ifExpr,
|
|
76
|
+
caseExpr,
|
|
77
|
+
ofClause,
|
|
78
|
+
record,
|
|
79
|
+
binding,
|
|
80
|
+
exprs,
|
|
81
|
+
program,
|
|
82
|
+
pop,
|
|
83
|
+
topNode,
|
|
84
|
+
peek,
|
|
85
|
+
push,
|
|
86
|
+
mod,
|
|
87
|
+
add,
|
|
88
|
+
sub,
|
|
89
|
+
// mul,
|
|
90
|
+
div,
|
|
91
|
+
pow,
|
|
92
|
+
concat,
|
|
93
|
+
eq,
|
|
94
|
+
ne,
|
|
95
|
+
lt,
|
|
96
|
+
gt,
|
|
97
|
+
le,
|
|
98
|
+
ge,
|
|
99
|
+
neg,
|
|
100
|
+
list,
|
|
101
|
+
bool,
|
|
102
|
+
nul,
|
|
103
|
+
error,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return new AstClass();
|
|
107
|
+
|
|
108
|
+
// private implementation
|
|
109
|
+
|
|
110
|
+
function push(ctx, node) {
|
|
111
|
+
let nid;
|
|
112
|
+
if (typeof node === "number") { // if already interned
|
|
113
|
+
nid = node;
|
|
114
|
+
} else {
|
|
115
|
+
nid = intern(ctx, node);
|
|
116
|
+
}
|
|
117
|
+
ctx.state.nodeStack.push(nid);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function topNode(ctx) {
|
|
121
|
+
const nodeStack = ctx.state.nodeStack;
|
|
122
|
+
return nodeStack[nodeStack.length - 1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function pop(ctx) {
|
|
126
|
+
const nodeStack = ctx.state.nodeStack;
|
|
127
|
+
return nodeStack.pop();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function peek(ctx, n) {
|
|
131
|
+
if (n === undefined) {
|
|
132
|
+
n = 0;
|
|
133
|
+
}
|
|
134
|
+
const nodeStack = ctx.state.nodeStack;
|
|
135
|
+
return nodeStack[nodeStack.length - 1 - n];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function intern(ctx, n) {
|
|
139
|
+
if (!n) {
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
const nodeMap = ctx.state.nodeMap;
|
|
143
|
+
const nodePool = ctx.state.nodePool;
|
|
144
|
+
const tag = n.tag;
|
|
145
|
+
let elts = "";
|
|
146
|
+
const count = n.elts.length;
|
|
147
|
+
for (let i = 0; i < count; i++) {
|
|
148
|
+
if (typeof n.elts[i] === "object") {
|
|
149
|
+
n.elts[i] = intern(ctx, n.elts[i]);
|
|
150
|
+
}
|
|
151
|
+
elts += n.elts[i];
|
|
152
|
+
}
|
|
153
|
+
const key = tag + count + elts;
|
|
154
|
+
let nid = nodeMap[key];
|
|
155
|
+
if (nid === undefined) {
|
|
156
|
+
nodePool.push({ tag, elts: n.elts });
|
|
157
|
+
nid = nodePool.length - 1;
|
|
158
|
+
nodeMap[key] = nid;
|
|
159
|
+
if (n.coord) {
|
|
160
|
+
ctx.state.coords[nid] = n.coord;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return nid;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function node(ctx, nid) {
|
|
167
|
+
const n = ctx.state.nodePool[nid];
|
|
168
|
+
if (!nid) {
|
|
169
|
+
return null;
|
|
170
|
+
} else if (!n) {
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
const elts = [];
|
|
174
|
+
switch (n.tag) {
|
|
175
|
+
case "NULL":
|
|
176
|
+
break;
|
|
177
|
+
case "NUM":
|
|
178
|
+
case "STR":
|
|
179
|
+
case "IDENT":
|
|
180
|
+
case "BOOL":
|
|
181
|
+
elts[0] = n.elts[0];
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
for (let i = 0; i < n.elts.length; i++) {
|
|
185
|
+
elts[i] = node(ctx, n.elts[i]);
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
tag: n.tag,
|
|
191
|
+
elts,
|
|
192
|
+
coord: getCoord(ctx)
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function dumpAll(ctx) {
|
|
197
|
+
const nodePool = ctx.state.nodePool;
|
|
198
|
+
let s = "\n{";
|
|
199
|
+
for (let i = 1; i < nodePool.length; i++) {
|
|
200
|
+
const n = nodePool[i];
|
|
201
|
+
s = s + "\n " + i + ": " + dump(n) + ",";
|
|
202
|
+
}
|
|
203
|
+
s += "\n root: " + (nodePool.length - 1);
|
|
204
|
+
s += "\n}\n";
|
|
205
|
+
return s;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function poolToJSON(ctx) {
|
|
209
|
+
const nodePool = ctx.state.nodePool;
|
|
210
|
+
const obj = {};
|
|
211
|
+
for (let i = 1; i < nodePool.length; i++) {
|
|
212
|
+
const n = nodePool[i];
|
|
213
|
+
obj[i] = nodeToJSON(n);
|
|
214
|
+
}
|
|
215
|
+
obj.root = (nodePool.length - 1);
|
|
216
|
+
return obj;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function nodeToJSON(n) {
|
|
220
|
+
let obj;
|
|
221
|
+
if (typeof n === "object") {
|
|
222
|
+
switch (n.tag) {
|
|
223
|
+
case "num":
|
|
224
|
+
obj = n.elts[0];
|
|
225
|
+
break;
|
|
226
|
+
case "str":
|
|
227
|
+
obj = n.elts[0];
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
obj = {};
|
|
231
|
+
obj.tag = n.tag;
|
|
232
|
+
obj.elts = [];
|
|
233
|
+
for (let i = 0; i < n.elts.length; i++) {
|
|
234
|
+
obj.elts[i] = nodeToJSON(n.elts[i]);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
} else if (typeof n === "string") {
|
|
239
|
+
obj = n;
|
|
240
|
+
} else {
|
|
241
|
+
obj = n;
|
|
242
|
+
}
|
|
243
|
+
return obj;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function dump(n) {
|
|
247
|
+
let s;
|
|
248
|
+
if (typeof n === "object") {
|
|
249
|
+
switch (n.tag) {
|
|
250
|
+
case "num":
|
|
251
|
+
s = n.elts[0];
|
|
252
|
+
break;
|
|
253
|
+
case "str":
|
|
254
|
+
s = "\"" + n.elts[0] + "\"";
|
|
255
|
+
break;
|
|
256
|
+
default:
|
|
257
|
+
if (!n.elts) {
|
|
258
|
+
s += "<invalid>";
|
|
259
|
+
} else {
|
|
260
|
+
s = "{ tag: \"" + n.tag + "\", elts: [ ";
|
|
261
|
+
for (let i = 0; i < n.elts.length; i++) {
|
|
262
|
+
if (i > 0) {
|
|
263
|
+
s += " , ";
|
|
264
|
+
}
|
|
265
|
+
s += dump(n.elts[i]);
|
|
266
|
+
}
|
|
267
|
+
s += " ] }";
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
} else if (typeof n === "string") {
|
|
272
|
+
s = "\"" + n + "\"";
|
|
273
|
+
} else {
|
|
274
|
+
s = n;
|
|
275
|
+
}
|
|
276
|
+
return s;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function fold(ctx, fn, args) {
|
|
280
|
+
// Local defs:
|
|
281
|
+
// -- put bindings in env
|
|
282
|
+
// Three cases:
|
|
283
|
+
// -- full application, all args are available at parse time
|
|
284
|
+
// -- partial application, only some args are available at parse time
|
|
285
|
+
// -- late application, args are available at compile time (not parse time)
|
|
286
|
+
// apply <[x y]: add x y> data..
|
|
287
|
+
// x: val 0 data
|
|
288
|
+
// y: val 1 data
|
|
289
|
+
env.enterEnv(ctx, fn.name);
|
|
290
|
+
if (fn.env) {
|
|
291
|
+
const lexicon = fn.env.lexicon;
|
|
292
|
+
const pattern = Ast.node(ctx, fn.env.pattern);
|
|
293
|
+
let outerEnv = null;
|
|
294
|
+
// let isListPattern;
|
|
295
|
+
// setup inner environment record (lexicon)
|
|
296
|
+
if (pattern && pattern.elts &&
|
|
297
|
+
pattern.elts.length === 1 &&
|
|
298
|
+
pattern.elts[0].tag === "LIST") {
|
|
299
|
+
// For now we only support one pattern per param list.
|
|
300
|
+
// isListPattern = true;
|
|
301
|
+
}
|
|
302
|
+
for (const id in lexicon) {
|
|
303
|
+
// For each parameter, get its definition assign the value of the argument
|
|
304
|
+
// used on the current function application.
|
|
305
|
+
if (!id) continue;
|
|
306
|
+
const word = JSON.parse(JSON.stringify(lexicon[id])); // poor man's copy.
|
|
307
|
+
const index = args.length - word.offset - 1;
|
|
308
|
+
// TODO we currently ignore list patterns
|
|
309
|
+
// if (isListPattern) {
|
|
310
|
+
// // <[x y]: ...> foo..
|
|
311
|
+
// word.nid = Ast.intern(ctx, {
|
|
312
|
+
// tag: "VAL",
|
|
313
|
+
// elts: [{
|
|
314
|
+
// tag: "NUM",
|
|
315
|
+
// elts: [
|
|
316
|
+
// String(word.offset),
|
|
317
|
+
// ]}, {
|
|
318
|
+
// tag: "ARG",
|
|
319
|
+
// elts: [{
|
|
320
|
+
// tag: "NUM",
|
|
321
|
+
// elts: ["0"]
|
|
322
|
+
// }]
|
|
323
|
+
// }]
|
|
324
|
+
// });
|
|
325
|
+
// } else
|
|
326
|
+
if (index >= 0 && index < args.length) {
|
|
327
|
+
word.nid = args[index];
|
|
328
|
+
}
|
|
329
|
+
if (index < 0) {
|
|
330
|
+
// We've got an unbound variable or a variable with a default value,
|
|
331
|
+
// so add it to the new variable list.
|
|
332
|
+
// <x:x> => <x:x>
|
|
333
|
+
// (<x y: add x y> 10) => <y: add 10 y>
|
|
334
|
+
// (<y: let x = 10.. add x y>) => <y: add 10 y>
|
|
335
|
+
if (!outerEnv) {
|
|
336
|
+
outerEnv = {};
|
|
337
|
+
}
|
|
338
|
+
outerEnv[id] = word;
|
|
339
|
+
}
|
|
340
|
+
env.addWord(ctx, id, word);
|
|
341
|
+
}
|
|
342
|
+
folder.fold(ctx, fn.nid);
|
|
343
|
+
if (outerEnv) {
|
|
344
|
+
lambda(ctx, {
|
|
345
|
+
lexicon: outerEnv,
|
|
346
|
+
pattern // FIXME need to trim pattern if some args where applied.
|
|
347
|
+
}, pop(ctx));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
env.exitEnv(ctx);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function applyLate(ctx, count) {
|
|
354
|
+
// Ast.applyLate
|
|
355
|
+
const elts = [];
|
|
356
|
+
for (let i = count; i > 0; i--) {
|
|
357
|
+
elts.push(pop(ctx));
|
|
358
|
+
}
|
|
359
|
+
push(ctx, {
|
|
360
|
+
tag: "APPLY",
|
|
361
|
+
elts
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function apply(ctx, fnId, argc) {
|
|
366
|
+
// Construct function and apply available arguments.
|
|
367
|
+
const fn = node(ctx, fnId);
|
|
368
|
+
// if (fn.tag !== "LAMBDA") {
|
|
369
|
+
// // Construct an APPLY node for compiling later.
|
|
370
|
+
// return {
|
|
371
|
+
// tag: "APPLY",
|
|
372
|
+
// elts: [
|
|
373
|
+
// fnId,
|
|
374
|
+
// ]
|
|
375
|
+
// };
|
|
376
|
+
// }
|
|
377
|
+
// Construct a lexicon
|
|
378
|
+
const lexicon = {};
|
|
379
|
+
let paramc = 0;
|
|
380
|
+
fn.elts[0].elts.forEach(function (n, i) {
|
|
381
|
+
const name = n.elts[0];
|
|
382
|
+
const nid = Ast.intern(ctx, fn.elts[3].elts[i]);
|
|
383
|
+
lexicon[name] = {
|
|
384
|
+
cls: "val",
|
|
385
|
+
name,
|
|
386
|
+
offset: i,
|
|
387
|
+
nid
|
|
388
|
+
};
|
|
389
|
+
if (!nid) {
|
|
390
|
+
// Parameters don't have nids.
|
|
391
|
+
// assert that there are parameters after a binding without a nid.
|
|
392
|
+
paramc++;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
const def = {
|
|
396
|
+
name: "lambda",
|
|
397
|
+
nid: Ast.intern(ctx, fn.elts[1]),
|
|
398
|
+
env: {
|
|
399
|
+
lexicon,
|
|
400
|
+
pattern: Ast.intern(ctx, fn.elts[2])
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const elts = [];
|
|
404
|
+
// While there are args on the stack, pop them.
|
|
405
|
+
while (argc-- > 0 && paramc-- > 0) {
|
|
406
|
+
const elt = pop(ctx);
|
|
407
|
+
elts.unshift(elt); // Get the order right.
|
|
408
|
+
}
|
|
409
|
+
fold(ctx, def, elts);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Node constructors
|
|
413
|
+
|
|
414
|
+
function error(ctx, str, coord) {
|
|
415
|
+
console.log(
|
|
416
|
+
"error()",
|
|
417
|
+
"str=" + str,
|
|
418
|
+
"coord=" + JSON.stringify(coord),
|
|
419
|
+
);
|
|
420
|
+
const from = coord?.from !== undefined ? coord.from : -1;
|
|
421
|
+
const to = coord?.to !== undefined ? coord.to : -1;
|
|
422
|
+
number(ctx, to);
|
|
423
|
+
number(ctx, from);
|
|
424
|
+
string(ctx, str, coord);
|
|
425
|
+
push(ctx, {
|
|
426
|
+
tag: "ERROR",
|
|
427
|
+
elts: [
|
|
428
|
+
pop(ctx),
|
|
429
|
+
pop(ctx),
|
|
430
|
+
pop(ctx),
|
|
431
|
+
],
|
|
432
|
+
coord
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function bool(ctx, val) {
|
|
437
|
+
let b;
|
|
438
|
+
if (val) {
|
|
439
|
+
b = true;
|
|
440
|
+
} else {
|
|
441
|
+
b = false;
|
|
442
|
+
}
|
|
443
|
+
push(ctx, {
|
|
444
|
+
tag: "BOOL",
|
|
445
|
+
elts: [b]
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function nul(ctx) {
|
|
450
|
+
push(ctx, {
|
|
451
|
+
tag: "NULL",
|
|
452
|
+
elts: []
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function number(ctx, num, coord) {
|
|
457
|
+
assert(typeof num === "string" || typeof num === "number");
|
|
458
|
+
push(ctx, {
|
|
459
|
+
tag: "NUM",
|
|
460
|
+
elts: [String(num)],
|
|
461
|
+
coord
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function string(ctx, str, coord) {
|
|
466
|
+
push(ctx, {
|
|
467
|
+
tag: "STR",
|
|
468
|
+
elts: [str],
|
|
469
|
+
coord
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function name(ctx, name, coord) {
|
|
474
|
+
push(ctx, {
|
|
475
|
+
tag: "IDENT",
|
|
476
|
+
elts: [name],
|
|
477
|
+
coord
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function expr(ctx, argc) {
|
|
482
|
+
// Ast.expr -- construct a expr node for the compiler.
|
|
483
|
+
const elts = [];
|
|
484
|
+
const pos = getPos(ctx);
|
|
485
|
+
console.trace(
|
|
486
|
+
"expr()",
|
|
487
|
+
"argc=" + argc,
|
|
488
|
+
"nodeStack=" + JSON.stringify(ctx.state.nodeStack, null, 2),
|
|
489
|
+
);
|
|
490
|
+
assertErr(ctx, argc <= ctx.state.nodeStack.length - 1, `Too few arguments. Expected ${argc} got ${ctx.state.nodeStack.length - 1}.`, {
|
|
491
|
+
from: pos - 1, to: pos
|
|
492
|
+
});
|
|
493
|
+
while (argc--) {
|
|
494
|
+
const elt = pop(ctx);
|
|
495
|
+
elts.push(elt);
|
|
496
|
+
}
|
|
497
|
+
const nameId = pop(ctx);
|
|
498
|
+
console.log(
|
|
499
|
+
"expr()",
|
|
500
|
+
"nameId=" + nameId,
|
|
501
|
+
);
|
|
502
|
+
assertErr(ctx, nameId, "Ill formed node.");
|
|
503
|
+
const e = node(ctx, nameId).elts;
|
|
504
|
+
assertErr(ctx, e && e.length > 0, "Ill formed node.");
|
|
505
|
+
const name = e[0];
|
|
506
|
+
push(ctx, {
|
|
507
|
+
tag: name,
|
|
508
|
+
elts,
|
|
509
|
+
coord: getCoord(ctx)
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function parenExpr(ctx, coord) {
|
|
514
|
+
// Ast.parenExpr
|
|
515
|
+
const elts = [];
|
|
516
|
+
const elt = pop(ctx);
|
|
517
|
+
elts.push(elt);
|
|
518
|
+
push(ctx, {
|
|
519
|
+
tag: "PAREN",
|
|
520
|
+
elts,
|
|
521
|
+
coord
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function list(ctx, count, coord, reverse) {
|
|
526
|
+
// Ast.list
|
|
527
|
+
const elts = [];
|
|
528
|
+
for (let i = count; i > 0; i--) {
|
|
529
|
+
const elt = pop(ctx);
|
|
530
|
+
if (elt !== undefined) {
|
|
531
|
+
elts.push(elt);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
push(ctx, {
|
|
535
|
+
tag: "LIST",
|
|
536
|
+
elts: reverse ? elts : elts.reverse(),
|
|
537
|
+
coord
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function binaryExpr(ctx, name) {
|
|
542
|
+
const elts = [];
|
|
543
|
+
// args are in the order produced by the parser
|
|
544
|
+
elts.push(pop(ctx));
|
|
545
|
+
elts.push(pop(ctx));
|
|
546
|
+
push(ctx, {
|
|
547
|
+
tag: name,
|
|
548
|
+
elts: elts.reverse()
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function unaryExpr(ctx, name) {
|
|
552
|
+
const elts = [];
|
|
553
|
+
elts.push(pop(ctx));
|
|
554
|
+
push(ctx, {
|
|
555
|
+
tag: name,
|
|
556
|
+
elts
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function prefixExpr(ctx, name) {
|
|
561
|
+
const elts = [];
|
|
562
|
+
elts.push(pop(ctx));
|
|
563
|
+
push(ctx, {
|
|
564
|
+
tag: name,
|
|
565
|
+
elts
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function neg(ctx) {
|
|
570
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
571
|
+
number(ctx, -1 * v1);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function add(ctx, coord) {
|
|
575
|
+
const n2 = node(ctx, pop(ctx));
|
|
576
|
+
const n1 = node(ctx, pop(ctx));
|
|
577
|
+
const v2 = n2.elts[0];
|
|
578
|
+
const v1 = n1.elts[0];
|
|
579
|
+
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
580
|
+
push(ctx, {
|
|
581
|
+
tag: "ADD",
|
|
582
|
+
elts: [n1, n2],
|
|
583
|
+
coord
|
|
584
|
+
});
|
|
585
|
+
} else {
|
|
586
|
+
number(ctx, +v1 + +v2);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function sub(ctx) {
|
|
591
|
+
const n2 = node(ctx, pop(ctx));
|
|
592
|
+
const n1 = node(ctx, pop(ctx));
|
|
593
|
+
const v2 = n2.elts[0];
|
|
594
|
+
const v1 = n1.elts[0];
|
|
595
|
+
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
596
|
+
push(ctx, { tag: "SUB", elts: [n1, n2] });
|
|
597
|
+
} else {
|
|
598
|
+
number(ctx, +v1 - +v2);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// function mul(ctx) {
|
|
603
|
+
// let n2 = node(ctx, pop(ctx));
|
|
604
|
+
// let n1 = node(ctx, pop(ctx));
|
|
605
|
+
// const v2 = n2.elts[0];
|
|
606
|
+
// const v1 = n1.elts[0];
|
|
607
|
+
// if (n1.tag === undefined) {
|
|
608
|
+
// n1 = n1.elts[0];
|
|
609
|
+
// }
|
|
610
|
+
// if (n2.tag === undefined) {
|
|
611
|
+
// n2 = n2.elts[0];
|
|
612
|
+
// }
|
|
613
|
+
// if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
614
|
+
// push(ctx, { tag: "MUL", elts: [n2, n1] });
|
|
615
|
+
// } else {
|
|
616
|
+
// number(ctx, +v1 * +v2);
|
|
617
|
+
// }
|
|
618
|
+
// }
|
|
619
|
+
|
|
620
|
+
function div(ctx) {
|
|
621
|
+
const n2 = node(ctx, pop(ctx));
|
|
622
|
+
const n1 = node(ctx, pop(ctx));
|
|
623
|
+
const v2 = n2.elts[0];
|
|
624
|
+
const v1 = n1.elts[0];
|
|
625
|
+
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
626
|
+
push(ctx, { tag: "DIV", elts: [n1, n2] });
|
|
627
|
+
} else {
|
|
628
|
+
number(ctx, +v1 / +v2);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function mod(ctx) {
|
|
633
|
+
const n2 = node(ctx, pop(ctx));
|
|
634
|
+
const n1 = node(ctx, pop(ctx));
|
|
635
|
+
const v1 = n1.elts[0];
|
|
636
|
+
const v2 = n2.elts[0];
|
|
637
|
+
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
638
|
+
push(ctx, { tag: "MOD", elts: [n1, n2] });
|
|
639
|
+
} else {
|
|
640
|
+
number(ctx, +v1 % +v2);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function pow(ctx) {
|
|
645
|
+
const n2 = node(ctx, pop(ctx));
|
|
646
|
+
const n1 = node(ctx, pop(ctx));
|
|
647
|
+
const v2 = n2.elts[0];
|
|
648
|
+
const v1 = n1.elts[0];
|
|
649
|
+
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
650
|
+
push(ctx, { tag: "POW", elts: [n1, n2] });
|
|
651
|
+
} else {
|
|
652
|
+
number(ctx, Math.pow(+v1, +v2));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function concat(ctx) {
|
|
657
|
+
const n1 = node(ctx, pop(ctx));
|
|
658
|
+
push(ctx, {
|
|
659
|
+
tag: "CONCAT",
|
|
660
|
+
elts: [n1]
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function eq(ctx) {
|
|
665
|
+
const v2 = node(ctx, pop(ctx)).elts[0];
|
|
666
|
+
const v1 = node(ctx, pop(ctx)).elts[0];
|
|
667
|
+
bool(ctx, v1 === v2);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function ne(ctx) {
|
|
671
|
+
const v2 = +node(ctx, pop(ctx)).elts[0];
|
|
672
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
673
|
+
bool(ctx, v1 !== v2);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function lt(ctx) {
|
|
677
|
+
const v2 = +node(ctx, pop(ctx)).elts[0];
|
|
678
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
679
|
+
bool(ctx, v1 < v2);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function gt(ctx) {
|
|
683
|
+
const v2 = +node(ctx, pop(ctx)).elts[0];
|
|
684
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
685
|
+
bool(ctx, v1 > v2);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function le(ctx) {
|
|
689
|
+
const v2 = +node(ctx, pop(ctx)).elts[0];
|
|
690
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
691
|
+
bool(ctx, v1 <= v2);
|
|
692
|
+
}
|
|
693
|
+
function ge(ctx) {
|
|
694
|
+
const v2 = +node(ctx, pop(ctx)).elts[0];
|
|
695
|
+
const v1 = +node(ctx, pop(ctx)).elts[0];
|
|
696
|
+
bool(ctx, v1 >= v2);
|
|
697
|
+
}
|
|
698
|
+
function ifExpr(ctx) {
|
|
699
|
+
const elts = [];
|
|
700
|
+
elts.push(pop(ctx)); // if
|
|
701
|
+
elts.push(pop(ctx)); // then
|
|
702
|
+
elts.push(pop(ctx)); // else
|
|
703
|
+
push(ctx, { tag: "IF", elts: elts.reverse() });
|
|
704
|
+
}
|
|
705
|
+
function caseExpr(ctx, n) {
|
|
706
|
+
const elts = [];
|
|
707
|
+
elts.push(pop(ctx)); // val
|
|
708
|
+
for (let i = n; i > 0; i--) {
|
|
709
|
+
elts.push(pop(ctx)); // of
|
|
710
|
+
}
|
|
711
|
+
push(ctx, { tag: "CASE", elts: elts.reverse() });
|
|
712
|
+
}
|
|
713
|
+
function ofClause(ctx) {
|
|
714
|
+
const elts = [];
|
|
715
|
+
elts.push(pop(ctx));
|
|
716
|
+
elts.push(pop(ctx));
|
|
717
|
+
push(ctx, { tag: "OF", elts: elts.reverse() });
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function record(ctx) {
|
|
721
|
+
// Ast.record
|
|
722
|
+
const count = ctx.state.exprc;
|
|
723
|
+
const elts = [];
|
|
724
|
+
for (let i = count; i > 0; i--) {
|
|
725
|
+
const elt = pop(ctx);
|
|
726
|
+
if (elt !== undefined) {
|
|
727
|
+
elts.push(elt);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
push(ctx, {
|
|
731
|
+
tag: "RECORD",
|
|
732
|
+
elts
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function binding(ctx) {
|
|
737
|
+
// Ast.binding
|
|
738
|
+
const elts = [];
|
|
739
|
+
elts.push(pop(ctx));
|
|
740
|
+
elts.push(pop(ctx));
|
|
741
|
+
push(ctx, {
|
|
742
|
+
tag: "BINDING",
|
|
743
|
+
elts: elts.reverse()
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function lambda(ctx, env, nid) {
|
|
748
|
+
// Ast.lambda
|
|
749
|
+
const names = [];
|
|
750
|
+
const nids = [];
|
|
751
|
+
for (const id in env.lexicon) {
|
|
752
|
+
const word = env.lexicon[id];
|
|
753
|
+
names.push({
|
|
754
|
+
tag: "IDENT",
|
|
755
|
+
elts: [word.name],
|
|
756
|
+
coord: getCoord(ctx)
|
|
757
|
+
});
|
|
758
|
+
nids.push(word.nid || 0);
|
|
759
|
+
}
|
|
760
|
+
const pattern = env.pattern;
|
|
761
|
+
push(ctx, {
|
|
762
|
+
tag: "LAMBDA",
|
|
763
|
+
elts: [{
|
|
764
|
+
tag: "LIST",
|
|
765
|
+
elts: names
|
|
766
|
+
}, nid, {
|
|
767
|
+
tag: "LIST",
|
|
768
|
+
elts: pattern
|
|
769
|
+
}, {
|
|
770
|
+
tag: "LIST",
|
|
771
|
+
elts: nids
|
|
772
|
+
}]
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function exprs(ctx, count, inReverse) {
|
|
777
|
+
// Ast.exprs
|
|
778
|
+
let elts = [];
|
|
779
|
+
assert(ctx.state.nodeStack.length >= count);
|
|
780
|
+
if (inReverse) {
|
|
781
|
+
for (let i = count; i > 0; i--) {
|
|
782
|
+
const elt = pop(ctx);
|
|
783
|
+
elts.push(elt); // Reverse order.
|
|
784
|
+
}
|
|
785
|
+
} else {
|
|
786
|
+
for (let i = count; i > 0; i--) {
|
|
787
|
+
const elt = pop(ctx);
|
|
788
|
+
elts.push(elt); // Reverse order.
|
|
789
|
+
}
|
|
790
|
+
elts = elts.reverse();
|
|
791
|
+
}
|
|
792
|
+
push(ctx, {
|
|
793
|
+
tag: "EXPRS",
|
|
794
|
+
elts
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function letDef(ctx) {
|
|
799
|
+
// Clean up stack and produce initializer.
|
|
800
|
+
pop(ctx); // body
|
|
801
|
+
pop(ctx); // name
|
|
802
|
+
for (let i = 0; i < ctx.state.paramc; i++) {
|
|
803
|
+
pop(ctx); // params
|
|
804
|
+
}
|
|
805
|
+
ctx.state.exprc--; // don't count as expr.
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function program(ctx) {
|
|
809
|
+
const elts = [];
|
|
810
|
+
elts.push(pop(ctx));
|
|
811
|
+
push(ctx, {
|
|
812
|
+
tag: "PROG",
|
|
813
|
+
elts
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
})();
|
|
817
|
+
|
|
818
|
+
// The following code for StreamString was copied from CodeMirror.
|
|
819
|
+
|
|
820
|
+
const StringStream = (function () {
|
|
821
|
+
// The character stream used by a mode's parser.
|
|
822
|
+
function StringStream(string, tabSize) {
|
|
823
|
+
this.pos = this.start = 0;
|
|
824
|
+
this.string = string;
|
|
825
|
+
this.tabSize = tabSize || 8;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
StringStream.prototype = {
|
|
829
|
+
eol: function () { return this.pos >= this.string.length; },
|
|
830
|
+
sol: function () { return this.pos === 0; },
|
|
831
|
+
peek: function () { return this.string.charAt(this.pos) || undefined; },
|
|
832
|
+
next: function () {
|
|
833
|
+
if (this.pos < this.string.length) { return this.string.charAt(this.pos++); }
|
|
834
|
+
},
|
|
835
|
+
eat: function (match) {
|
|
836
|
+
const ch = this.string.charAt(this.pos);
|
|
837
|
+
let ok;
|
|
838
|
+
if (typeof match === "string") {
|
|
839
|
+
ok = ch === match;
|
|
840
|
+
} else {
|
|
841
|
+
ok = ch && (match.test ? match.test(ch) : match(ch));
|
|
842
|
+
}
|
|
843
|
+
if (ok) { ++this.pos; return ch; }
|
|
844
|
+
},
|
|
845
|
+
eatWhile: function (match) {
|
|
846
|
+
const start = this.pos;
|
|
847
|
+
while (this.eat(match));
|
|
848
|
+
return this.pos > start;
|
|
849
|
+
},
|
|
850
|
+
eatSpace: function () {
|
|
851
|
+
const start = this.pos;
|
|
852
|
+
while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
|
|
853
|
+
return this.pos > start;
|
|
854
|
+
},
|
|
855
|
+
skipToEnd: function () { this.pos = this.string.length; },
|
|
856
|
+
skipTo: function (ch) {
|
|
857
|
+
const found = this.string.indexOf(ch, this.pos);
|
|
858
|
+
if (found > -1) { this.pos = found; return true; }
|
|
859
|
+
},
|
|
860
|
+
backUp: function (n) { this.pos -= n; },
|
|
861
|
+
match: function (pattern, consume, caseInsensitive) {
|
|
862
|
+
assert(false, "Should not get here");
|
|
863
|
+
if (typeof pattern === "string") {
|
|
864
|
+
const cased = function (str) { return caseInsensitive ? str.toLowerCase() : str; };
|
|
865
|
+
if (cased(this.string).indexOf(cased(pattern), this.pos) === this.pos) {
|
|
866
|
+
if (consume !== false) this.pos += pattern.length;
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
const match = this.string.slice(this.pos).match(pattern);
|
|
871
|
+
if (match && match.index > 0) return null;
|
|
872
|
+
if (match && consume !== false) this.pos += match[0].length;
|
|
873
|
+
return match;
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
current: function () { return this.string.slice(this.start, this.pos); }
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
return StringStream;
|
|
880
|
+
})();
|
|
881
|
+
|
|
882
|
+
// env
|
|
883
|
+
|
|
884
|
+
export const env = (function () {
|
|
885
|
+
return {
|
|
886
|
+
findWord,
|
|
887
|
+
addWord,
|
|
888
|
+
enterEnv,
|
|
889
|
+
exitEnv,
|
|
890
|
+
addPattern
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// private functions
|
|
894
|
+
|
|
895
|
+
function findWord(ctx, lexeme) {
|
|
896
|
+
const env = ctx.state.env;
|
|
897
|
+
for (let i = env.length - 1; i >= 0; i--) {
|
|
898
|
+
const word = env[i].lexicon[lexeme];
|
|
899
|
+
if (word) {
|
|
900
|
+
return word;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function addWord(ctx, lexeme, entry) {
|
|
907
|
+
window.gcexports.topEnv(ctx).lexicon[lexeme] = entry;
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function addPattern(ctx, pattern) {
|
|
912
|
+
window.gcexports.topEnv(ctx).pattern.push(pattern);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function enterEnv(ctx, name) {
|
|
916
|
+
// recursion guard
|
|
917
|
+
if (ctx.state.env.length > 380) {
|
|
918
|
+
console.trace(
|
|
919
|
+
"enterEnv()",
|
|
920
|
+
"name=" + name,
|
|
921
|
+
);
|
|
922
|
+
// return; // just stop recursing
|
|
923
|
+
throw new Error("runaway recursion");
|
|
924
|
+
}
|
|
925
|
+
window.gcexports.topEnv(ctx).paramc = ctx.state.paramc;
|
|
926
|
+
ctx.state.env.push({
|
|
927
|
+
name,
|
|
928
|
+
lexicon: {},
|
|
929
|
+
pattern: []
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function exitEnv(ctx) {
|
|
934
|
+
ctx.state.env.pop();
|
|
935
|
+
ctx.state.paramc = window.gcexports.topEnv(ctx).paramc;
|
|
936
|
+
}
|
|
937
|
+
})();
|
|
938
|
+
|
|
939
|
+
let scanTime = 0;
|
|
940
|
+
let scanCount = 0;
|
|
941
|
+
window.gcexports.scanTime = function () {
|
|
942
|
+
return scanTime;
|
|
943
|
+
};
|
|
944
|
+
window.gcexports.scanCount = function () {
|
|
945
|
+
return scanCount;
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
let parseTime = 0;
|
|
949
|
+
|
|
950
|
+
window.gcexports.parseTime = function () {
|
|
951
|
+
return parseTime;
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
let parseCount = 0;
|
|
955
|
+
window.gcexports.parseCount = function () {
|
|
956
|
+
return parseCount;
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
function getCoord(ctx) {
|
|
960
|
+
return {
|
|
961
|
+
from: ctx.scan.stream.start,
|
|
962
|
+
to: ctx.scan.stream.pos,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function getPos(ctx) {
|
|
967
|
+
return ctx.scan.stream.pos;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function assertErr(ctx, b, str, coord) {
|
|
971
|
+
console.log(
|
|
972
|
+
"assertErr()",
|
|
973
|
+
"str=" + str,
|
|
974
|
+
);
|
|
975
|
+
if (!b) {
|
|
976
|
+
const pos = getPos(ctx);
|
|
977
|
+
Ast.error(ctx, str, { from: pos - 1, to: pos });
|
|
978
|
+
throw new Error(str);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
export const parse = (function () {
|
|
983
|
+
function assert(b, str) {
|
|
984
|
+
if (!b) {
|
|
985
|
+
throw new Error(str);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const keywords = window.gcexports.keywords = {
|
|
990
|
+
let: { tk: 0x12, cls: "keyword" },
|
|
991
|
+
if: { tk: 0x05, cls: "keyword" },
|
|
992
|
+
then: { tk: 0x06, cls: "keyword" },
|
|
993
|
+
else: { tk: 0x07, cls: "keyword" },
|
|
994
|
+
case: { tk: 0x0F, cls: "keyword" },
|
|
995
|
+
of: { tk: 0x10, cls: "keyword" },
|
|
996
|
+
end: { tk: 0x11, cls: "keyword", length: 0 },
|
|
997
|
+
true: { tk: 0x14, cls: "val", length: 0 },
|
|
998
|
+
false: { tk: 0x14, cls: "val", length: 0 },
|
|
999
|
+
null: { tk: 0x15, cls: "val", length: 0 },
|
|
1000
|
+
val: { tk: 1, name: "VAL", cls: "function", length: 2, arity: 2 },
|
|
1001
|
+
key: { tk: 1, name: "KEY", cls: "function", length: 2, arity: 2 },
|
|
1002
|
+
len: { tk: 1, name: "LEN", cls: "function", length: 1, arity: 1 },
|
|
1003
|
+
concat: { tk: 1, name: "CONCAT", cls: "function", length: 1, arity: 1 },
|
|
1004
|
+
add: { tk: 1, name: "ADD", cls: "function", length: 2, arity: 2 },
|
|
1005
|
+
mul: { tk: 1, name: "MUL", cls: "function", length: 2, arity: 2 },
|
|
1006
|
+
pow: { tk: 1, name: "POW", cls: "function", length: 2, arity: 2 },
|
|
1007
|
+
style: { tk: 1, name: "STYLE", cls: "function", length: 2, arity: 2 },
|
|
1008
|
+
map: { tk: 1, name: "MAP", cls: "function", length: 2, arity: 2 },
|
|
1009
|
+
apply: { tk: 1, name: "APPLY", cls: "function", length: 2, arity: 2 },
|
|
1010
|
+
in: { tk: 1, name: "IN", cls: "function", length: 0, arity: 0 },
|
|
1011
|
+
arg: { tk: 1, name: "ARG", cls: "function", length: 1, arity: 1 },
|
|
1012
|
+
data: { tk: 1, name: "DATA", cls: "function", length: 1, arity: 1 },
|
|
1013
|
+
json: { tk: 1, name: "JSON", cls: "function", length: 1, arity: 1 },
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
const CC_DOUBLEQUOTE = 0x22;
|
|
1017
|
+
const CC_DOLLAR = 0x24;
|
|
1018
|
+
const CC_SINGLEQUOTE = 0x27;
|
|
1019
|
+
const CC_BACKTICK = 0x60;
|
|
1020
|
+
const CC_LEFTBRACE = 0x7B;
|
|
1021
|
+
// const CC_RIGHTBRACE = 0x7D;
|
|
1022
|
+
|
|
1023
|
+
const TK_IDENT = 0x01;
|
|
1024
|
+
const TK_NUM = 0x02;
|
|
1025
|
+
const TK_STR = 0x03;
|
|
1026
|
+
const TK_EQUAL = 0x04;
|
|
1027
|
+
const TK_IF = 0x05;
|
|
1028
|
+
const TK_THEN = 0x06;
|
|
1029
|
+
const TK_ELSE = 0x07;
|
|
1030
|
+
const TK_RETURN = 0x08;
|
|
1031
|
+
const TK_IS = 0x09;
|
|
1032
|
+
const TK_POSTOP = 0x0A;
|
|
1033
|
+
const TK_PREOP = 0x0B;
|
|
1034
|
+
const TK_FUN = 0x0C;
|
|
1035
|
+
const TK_VAL = 0x0D;
|
|
1036
|
+
const TK_BINOP = 0x0E;
|
|
1037
|
+
const TK_CASE = 0x0F;
|
|
1038
|
+
const TK_OF = 0x10;
|
|
1039
|
+
const TK_END = 0x11;
|
|
1040
|
+
const TK_LET = 0x12;
|
|
1041
|
+
const TK_OR = 0x13;
|
|
1042
|
+
const TK_BOOL = 0x14;
|
|
1043
|
+
const TK_NULL = 0x15;
|
|
1044
|
+
// const TK_IN = 0x16;
|
|
1045
|
+
|
|
1046
|
+
const TK_LEFTPAREN = 0xA1;
|
|
1047
|
+
const TK_RIGHTPAREN = 0xA2;
|
|
1048
|
+
const TK_LEFTBRACKET = 0xA3;
|
|
1049
|
+
const TK_RIGHTBRACKET = 0xA4;
|
|
1050
|
+
const TK_LEFTBRACE = 0xA5;
|
|
1051
|
+
const TK_RIGHTBRACE = 0xA6;
|
|
1052
|
+
const TK_PLUS = 0xA7;
|
|
1053
|
+
const TK_MINUS = 0xA8;
|
|
1054
|
+
const TK_DOT = 0xA9;
|
|
1055
|
+
const TK_COLON = 0xAA;
|
|
1056
|
+
const TK_COMMA = 0xAB;
|
|
1057
|
+
const TK_BACKQUOTE = 0xAC;
|
|
1058
|
+
const TK_COMMENT = 0xAD;
|
|
1059
|
+
const TK_LEFTANGLE = 0xAE;
|
|
1060
|
+
const TK_RIGHTANGLE = 0xAF;
|
|
1061
|
+
// const TK_DOUBLELEFTBRACE = 0xB0;
|
|
1062
|
+
// const TK_DOUBLERIGHTBRACE = 0xB1;
|
|
1063
|
+
const TK_STRPREFIX = 0xB2;
|
|
1064
|
+
const TK_STRMIDDLE = 0xB3;
|
|
1065
|
+
const TK_STRSUFFIX = 0xB4;
|
|
1066
|
+
|
|
1067
|
+
function tokenToLexeme(tk) {
|
|
1068
|
+
switch (tk) {
|
|
1069
|
+
case TK_EQUAL: return "a '=' symbol";
|
|
1070
|
+
case TK_IF: return "the 'if' keyword";
|
|
1071
|
+
case TK_THEN: return "the 'then' keyword";
|
|
1072
|
+
case TK_ELSE: return "the 'else' keyword";
|
|
1073
|
+
case TK_RETURN: return "the 'return' keyword";
|
|
1074
|
+
case TK_IS: return "the 'is' keyword";
|
|
1075
|
+
case TK_FUN: return "the 'fun' keyword";
|
|
1076
|
+
case TK_VAL: return "the 'val' keyword";
|
|
1077
|
+
case TK_CASE: return "the 'case' keyword";
|
|
1078
|
+
case TK_OF: return "the 'of' keyword";
|
|
1079
|
+
case TK_END: return "the 'end' keyword";
|
|
1080
|
+
case TK_LET: return "the 'let' keyword";
|
|
1081
|
+
case TK_OR: return "the 'or' keyword";
|
|
1082
|
+
case TK_POSTOP:
|
|
1083
|
+
case TK_PREOP:
|
|
1084
|
+
case TK_BINOP:
|
|
1085
|
+
return "an operator";
|
|
1086
|
+
case TK_LEFTPAREN: return "a '('";
|
|
1087
|
+
case TK_RIGHTPAREN: return "a ')'";
|
|
1088
|
+
case TK_LEFTBRACKET: return "a '['";
|
|
1089
|
+
case TK_RIGHTBRACKET: return "a ']'";
|
|
1090
|
+
case TK_LEFTBRACE: return "a '{'";
|
|
1091
|
+
case TK_RIGHTBRACE: return "a '}'";
|
|
1092
|
+
case TK_LEFTANGLE: return "a '<'";
|
|
1093
|
+
case TK_RIGHTANGLE: return "a '>'";
|
|
1094
|
+
case TK_PLUS: return "a '+'";
|
|
1095
|
+
case TK_MINUS: return "a '-'";
|
|
1096
|
+
case TK_DOT: return "a '.'";
|
|
1097
|
+
case TK_COLON: return "a ':'";
|
|
1098
|
+
case TK_COMMA: return "a ','";
|
|
1099
|
+
case TK_BACKQUOTE: return "a '`'";
|
|
1100
|
+
case TK_COMMENT: return "a comment";
|
|
1101
|
+
case 0: return "the end of the program";
|
|
1102
|
+
}
|
|
1103
|
+
return "an expression";
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
function eat(ctx, tk) {
|
|
1107
|
+
const nextToken = next(ctx);
|
|
1108
|
+
if (nextToken !== tk) {
|
|
1109
|
+
const to = getPos(ctx);
|
|
1110
|
+
const from = to - lexeme.length;
|
|
1111
|
+
Ast.error(ctx, "Expecting " + tokenToLexeme(tk) +
|
|
1112
|
+
", found " + tokenToLexeme(nextToken) + ".", { from, to });
|
|
1113
|
+
next(ctx); // Advance past error.
|
|
1114
|
+
throw new Error("Expecting " + tokenToLexeme(tk) +
|
|
1115
|
+
", found " + tokenToLexeme(nextToken) + ".");
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
function match(ctx, tk) {
|
|
1120
|
+
if (peek(ctx) === tk) {
|
|
1121
|
+
return true;
|
|
1122
|
+
} else {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function next(ctx) {
|
|
1128
|
+
const tk = peek(ctx);
|
|
1129
|
+
ctx.state.nextToken = -1;
|
|
1130
|
+
scanCount++;
|
|
1131
|
+
return tk;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
function peek(ctx) {
|
|
1135
|
+
let tk;
|
|
1136
|
+
const nextToken = ctx.state.nextToken;
|
|
1137
|
+
if (nextToken < 0) {
|
|
1138
|
+
const t0 = new Date();
|
|
1139
|
+
tk = ctx.scan.start(ctx);
|
|
1140
|
+
const t1 = new Date();
|
|
1141
|
+
scanTime += (t1 - t0);
|
|
1142
|
+
ctx.state.nextToken = tk;
|
|
1143
|
+
} else {
|
|
1144
|
+
tk = nextToken;
|
|
1145
|
+
}
|
|
1146
|
+
return tk;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Parsing functions -- each parsing function consumes a single token and
|
|
1150
|
+
// returns a continuation function for parsing the rest of the string.
|
|
1151
|
+
|
|
1152
|
+
function nul(ctx, cc) {
|
|
1153
|
+
eat(ctx, TK_NULL);
|
|
1154
|
+
cc.cls = "number";
|
|
1155
|
+
Ast.nul(ctx);
|
|
1156
|
+
return cc;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
function bool(ctx, cc) {
|
|
1160
|
+
eat(ctx, TK_BOOL);
|
|
1161
|
+
cc.cls = "number";
|
|
1162
|
+
Ast.bool(ctx, lexeme === "true");
|
|
1163
|
+
return cc;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function number(ctx, cc) {
|
|
1167
|
+
eat(ctx, TK_NUM);
|
|
1168
|
+
cc.cls = "number";
|
|
1169
|
+
Ast.number(ctx, lexeme, getCoord(ctx));
|
|
1170
|
+
return cc;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/*
|
|
1174
|
+
Str :
|
|
1175
|
+
STR
|
|
1176
|
+
STRPREFIX StrSuffix
|
|
1177
|
+
|
|
1178
|
+
StrSuffix :
|
|
1179
|
+
Expr STRMIDDLE StrSuffix
|
|
1180
|
+
Expr STRSUFFIX
|
|
1181
|
+
*/
|
|
1182
|
+
|
|
1183
|
+
function str(ctx, cc) {
|
|
1184
|
+
let coord;
|
|
1185
|
+
if (match(ctx, TK_STR)) {
|
|
1186
|
+
eat(ctx, TK_STR);
|
|
1187
|
+
coord = getCoord(ctx);
|
|
1188
|
+
Ast.string(ctx, lexeme, coord); // strip quotes;
|
|
1189
|
+
cc.cls = "string";
|
|
1190
|
+
return cc;
|
|
1191
|
+
} else if (match(ctx, TK_STRPREFIX)) {
|
|
1192
|
+
ctx.state.inStr++;
|
|
1193
|
+
eat(ctx, TK_STRPREFIX);
|
|
1194
|
+
startCounter(ctx);
|
|
1195
|
+
coord = getCoord(ctx);
|
|
1196
|
+
Ast.string(ctx, lexeme, coord); // strip quotes;
|
|
1197
|
+
countCounter(ctx);
|
|
1198
|
+
const ret = function (ctx) {
|
|
1199
|
+
return strSuffix(ctx, function (ctx) {
|
|
1200
|
+
ctx.state.inStr--;
|
|
1201
|
+
eat(ctx, TK_STRSUFFIX);
|
|
1202
|
+
const coord = getCoord(ctx);
|
|
1203
|
+
Ast.string(ctx, lexeme, coord); // strip quotes;
|
|
1204
|
+
countCounter(ctx);
|
|
1205
|
+
Ast.list(ctx, ctx.state.exprc, getCoord(ctx));
|
|
1206
|
+
stopCounter(ctx);
|
|
1207
|
+
Ast.concat(ctx);
|
|
1208
|
+
cc.cls = "string";
|
|
1209
|
+
return cc;
|
|
1210
|
+
});
|
|
1211
|
+
};
|
|
1212
|
+
ret.cls = "string";
|
|
1213
|
+
return ret;
|
|
1214
|
+
}
|
|
1215
|
+
assert(false);
|
|
1216
|
+
}
|
|
1217
|
+
function strSuffix(ctx, resume) {
|
|
1218
|
+
if (match(ctx, TK_STRSUFFIX)) {
|
|
1219
|
+
// We have a STRSUFFIX so we are done.
|
|
1220
|
+
return resume;
|
|
1221
|
+
}
|
|
1222
|
+
return strPart(ctx, function (ctx) {
|
|
1223
|
+
let ret;
|
|
1224
|
+
if (match(ctx, TK_STRMIDDLE)) {
|
|
1225
|
+
// Not done yet.
|
|
1226
|
+
eat(ctx, TK_STRMIDDLE);
|
|
1227
|
+
const coord = getCoord(ctx);
|
|
1228
|
+
Ast.string(ctx, lexeme, coord); // strip quotes;
|
|
1229
|
+
countCounter(ctx);
|
|
1230
|
+
ret = function (ctx) {
|
|
1231
|
+
return strSuffix(ctx, resume);
|
|
1232
|
+
};
|
|
1233
|
+
ret.cls = "string";
|
|
1234
|
+
return ret;
|
|
1235
|
+
}
|
|
1236
|
+
ret = function (ctx) {
|
|
1237
|
+
return strSuffix(ctx, resume);
|
|
1238
|
+
};
|
|
1239
|
+
ret.cls = "string";
|
|
1240
|
+
return ret;
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
function strPart(ctx, resume) {
|
|
1244
|
+
return expr(ctx, function (ctx) {
|
|
1245
|
+
countCounter(ctx);
|
|
1246
|
+
return resume(ctx);
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function ident(ctx, cc) {
|
|
1250
|
+
eat(ctx, TK_IDENT);
|
|
1251
|
+
Ast.name(ctx, lexeme, getCoord(ctx));
|
|
1252
|
+
cc.cls = "variable";
|
|
1253
|
+
return cc;
|
|
1254
|
+
}
|
|
1255
|
+
function bindingName(ctx, cc) {
|
|
1256
|
+
if (match(ctx, TK_IDENT)) {
|
|
1257
|
+
eat(ctx, TK_IDENT);
|
|
1258
|
+
Ast.string(ctx, lexeme, getCoord(ctx));
|
|
1259
|
+
cc.cls = "variable";
|
|
1260
|
+
return cc;
|
|
1261
|
+
}
|
|
1262
|
+
if (match(ctx, TK_NUM)) {
|
|
1263
|
+
return number(ctx, cc);
|
|
1264
|
+
}
|
|
1265
|
+
return str(ctx, cc);
|
|
1266
|
+
}
|
|
1267
|
+
function defList(ctx, cc) {
|
|
1268
|
+
eat(ctx, TK_LEFTBRACKET);
|
|
1269
|
+
const ret = (ctx) => {
|
|
1270
|
+
return params(ctx, TK_RIGHTBRACKET, (ctx) => {
|
|
1271
|
+
eat(ctx, TK_RIGHTBRACKET);
|
|
1272
|
+
Ast.list(ctx, ctx.state.paramc, null, true);
|
|
1273
|
+
ctx.state.paramc = 1;
|
|
1274
|
+
return cc;
|
|
1275
|
+
});
|
|
1276
|
+
};
|
|
1277
|
+
ret.cls = "punc";
|
|
1278
|
+
return ret;
|
|
1279
|
+
}
|
|
1280
|
+
function defName(ctx, cc) {
|
|
1281
|
+
if (match(ctx, TK_LEFTBRACKET)) {
|
|
1282
|
+
return defList(ctx, cc);
|
|
1283
|
+
} else {
|
|
1284
|
+
eat(ctx, TK_IDENT);
|
|
1285
|
+
env.addWord(ctx, lexeme, {
|
|
1286
|
+
tk: TK_IDENT,
|
|
1287
|
+
cls: "val",
|
|
1288
|
+
name: lexeme,
|
|
1289
|
+
offset: ctx.state.paramc,
|
|
1290
|
+
nid: 0
|
|
1291
|
+
});
|
|
1292
|
+
Ast.name(ctx, lexeme, getCoord(ctx));
|
|
1293
|
+
cc.cls = "val";
|
|
1294
|
+
return cc;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function name(ctx, cc) {
|
|
1298
|
+
eat(ctx, TK_IDENT);
|
|
1299
|
+
const to = getPos(ctx);
|
|
1300
|
+
const from = to - lexeme.length;
|
|
1301
|
+
const coord = { from, to };
|
|
1302
|
+
const word = env.findWord(ctx, lexeme);
|
|
1303
|
+
if (word) {
|
|
1304
|
+
cc.cls = word.cls;
|
|
1305
|
+
if (word.cls === "number" && word.val) {
|
|
1306
|
+
Ast.number(ctx, word.val, coord);
|
|
1307
|
+
} else if (word.cls === "string" && word.val) {
|
|
1308
|
+
Ast.string(ctx, word.val, coord);
|
|
1309
|
+
} else {
|
|
1310
|
+
if (word.nid) {
|
|
1311
|
+
Ast.push(ctx, word.nid);
|
|
1312
|
+
} else {
|
|
1313
|
+
Ast.name(ctx, lexeme, coord);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
} else {
|
|
1317
|
+
cc.cls = "error";
|
|
1318
|
+
console.log(
|
|
1319
|
+
"name()",
|
|
1320
|
+
"lexeme=" + lexeme,
|
|
1321
|
+
"coord=" + JSON.stringify(coord),
|
|
1322
|
+
);
|
|
1323
|
+
Ast.error(ctx, "Name '" + lexeme + "' not found.", coord);
|
|
1324
|
+
}
|
|
1325
|
+
// assert(cc, "name");
|
|
1326
|
+
return cc;
|
|
1327
|
+
}
|
|
1328
|
+
function record(ctx, cc) {
|
|
1329
|
+
// Parse record
|
|
1330
|
+
eat(ctx, TK_LEFTBRACE);
|
|
1331
|
+
startCounter(ctx);
|
|
1332
|
+
const ret = function (ctx) {
|
|
1333
|
+
return bindings(ctx, function (ctx) {
|
|
1334
|
+
eat(ctx, TK_RIGHTBRACE);
|
|
1335
|
+
Ast.record(ctx);
|
|
1336
|
+
stopCounter(ctx);
|
|
1337
|
+
cc.cls = "punc";
|
|
1338
|
+
return cc;
|
|
1339
|
+
});
|
|
1340
|
+
};
|
|
1341
|
+
ret.cls = "punc";
|
|
1342
|
+
return ret;
|
|
1343
|
+
}
|
|
1344
|
+
function bindings(ctx, cc) {
|
|
1345
|
+
if (match(ctx, TK_RIGHTBRACE)) {
|
|
1346
|
+
return cc;
|
|
1347
|
+
}
|
|
1348
|
+
return binding(ctx, function (ctx) {
|
|
1349
|
+
if (match(ctx, TK_COMMA)) {
|
|
1350
|
+
eat(ctx, TK_COMMA);
|
|
1351
|
+
Ast.binding(ctx);
|
|
1352
|
+
const ret = function (ctx) {
|
|
1353
|
+
return bindings(ctx, cc);
|
|
1354
|
+
};
|
|
1355
|
+
ret.cls = "punc";
|
|
1356
|
+
return ret;
|
|
1357
|
+
}
|
|
1358
|
+
return function (ctx) {
|
|
1359
|
+
Ast.binding(ctx);
|
|
1360
|
+
return bindings(ctx, cc);
|
|
1361
|
+
};
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
function binding(ctx, cc) {
|
|
1365
|
+
return bindingName(ctx, function (ctx) {
|
|
1366
|
+
eat(ctx, TK_COLON);
|
|
1367
|
+
const ret = function (ctx) {
|
|
1368
|
+
countCounter(ctx);
|
|
1369
|
+
return expr(ctx, cc);
|
|
1370
|
+
};
|
|
1371
|
+
ret.cls = "punc";
|
|
1372
|
+
return ret;
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
function lambda(ctx, cc) {
|
|
1376
|
+
eat(ctx, TK_LEFTANGLE);
|
|
1377
|
+
const ret = function (ctx) {
|
|
1378
|
+
ctx.state.paramc = 0;
|
|
1379
|
+
env.enterEnv(ctx, "lambda");
|
|
1380
|
+
return params(ctx, TK_COLON, function (ctx) {
|
|
1381
|
+
eat(ctx, TK_COLON);
|
|
1382
|
+
const ret = function (ctx) {
|
|
1383
|
+
return exprsStart(ctx, TK_RIGHTANGLE, function (ctx) {
|
|
1384
|
+
eat(ctx, TK_RIGHTANGLE);
|
|
1385
|
+
const nid = Ast.pop(ctx); // save body node id for aliased code
|
|
1386
|
+
Ast.lambda(ctx, topEnv(ctx), nid);
|
|
1387
|
+
env.exitEnv(ctx);
|
|
1388
|
+
return cc;
|
|
1389
|
+
});
|
|
1390
|
+
};
|
|
1391
|
+
ret.cls = "punc";
|
|
1392
|
+
return ret;
|
|
1393
|
+
});
|
|
1394
|
+
};
|
|
1395
|
+
return ret;
|
|
1396
|
+
}
|
|
1397
|
+
function parenExpr(ctx, cc) {
|
|
1398
|
+
const coord = getCoord(ctx);
|
|
1399
|
+
eat(ctx, TK_LEFTPAREN);
|
|
1400
|
+
const ret = function (ctx) {
|
|
1401
|
+
return exprsStart(ctx, TK_RIGHTPAREN, function (ctx) {
|
|
1402
|
+
eat(ctx, TK_RIGHTPAREN);
|
|
1403
|
+
coord.to = getCoord(ctx).to;
|
|
1404
|
+
Ast.parenExpr(ctx, coord);
|
|
1405
|
+
cc.cls = "punc";
|
|
1406
|
+
return cc;
|
|
1407
|
+
});
|
|
1408
|
+
};
|
|
1409
|
+
ret.cls = "punc";
|
|
1410
|
+
return ret;
|
|
1411
|
+
}
|
|
1412
|
+
function list(ctx, cc) {
|
|
1413
|
+
const coord = getCoord(ctx);
|
|
1414
|
+
eat(ctx, TK_LEFTBRACKET);
|
|
1415
|
+
startCounter(ctx);
|
|
1416
|
+
const ret = function (ctx) {
|
|
1417
|
+
return elements(ctx, function (ctx) {
|
|
1418
|
+
eat(ctx, TK_RIGHTBRACKET);
|
|
1419
|
+
coord.to = getCoord(ctx).to;
|
|
1420
|
+
Ast.list(ctx, ctx.state.exprc, coord);
|
|
1421
|
+
stopCounter(ctx);
|
|
1422
|
+
cc.cls = "punc";
|
|
1423
|
+
return cc;
|
|
1424
|
+
});
|
|
1425
|
+
};
|
|
1426
|
+
ret.cls = "punc";
|
|
1427
|
+
return ret;
|
|
1428
|
+
}
|
|
1429
|
+
function elements(ctx, cc) {
|
|
1430
|
+
if (match(ctx, TK_RIGHTBRACKET)) {
|
|
1431
|
+
return cc;
|
|
1432
|
+
}
|
|
1433
|
+
return element(ctx, function (ctx) {
|
|
1434
|
+
if (match(ctx, TK_COMMA)) {
|
|
1435
|
+
eat(ctx, TK_COMMA);
|
|
1436
|
+
const ret = function (ctx) {
|
|
1437
|
+
return elements(ctx, cc);
|
|
1438
|
+
};
|
|
1439
|
+
ret.cls = "punc";
|
|
1440
|
+
return ret;
|
|
1441
|
+
}
|
|
1442
|
+
return function (ctx) {
|
|
1443
|
+
return elements(ctx, cc);
|
|
1444
|
+
};
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function element(ctx, cc) {
|
|
1448
|
+
return expr(ctx, function (ctx) {
|
|
1449
|
+
countCounter(ctx);
|
|
1450
|
+
return cc(ctx);
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
function primaryExpr(ctx, cc) {
|
|
1454
|
+
if (match(ctx, TK_NUM)) {
|
|
1455
|
+
return number(ctx, cc);
|
|
1456
|
+
} else if (match(ctx, TK_STR) || match(ctx, TK_STRPREFIX)) {
|
|
1457
|
+
return str(ctx, cc);
|
|
1458
|
+
} else if (match(ctx, TK_BOOL)) {
|
|
1459
|
+
return bool(ctx, cc);
|
|
1460
|
+
} else if (match(ctx, TK_NULL)) {
|
|
1461
|
+
return nul(ctx, cc);
|
|
1462
|
+
} else if (match(ctx, TK_LEFTBRACE)) {
|
|
1463
|
+
return record(ctx, cc);
|
|
1464
|
+
} else if (match(ctx, TK_LEFTPAREN)) {
|
|
1465
|
+
return parenExpr(ctx, cc);
|
|
1466
|
+
} else if (match(ctx, TK_LEFTBRACKET)) {
|
|
1467
|
+
return list(ctx, cc);
|
|
1468
|
+
} else if (match(ctx, TK_LEFTANGLE)) {
|
|
1469
|
+
return lambda(ctx, cc);
|
|
1470
|
+
}
|
|
1471
|
+
return name(ctx, cc);
|
|
1472
|
+
}
|
|
1473
|
+
function postfixExpr(ctx, cc) {
|
|
1474
|
+
return primaryExpr(ctx, function (ctx) {
|
|
1475
|
+
if (match(ctx, TK_POSTOP)) {
|
|
1476
|
+
eat(ctx, TK_POSTOP);
|
|
1477
|
+
cc.cls = "operator";
|
|
1478
|
+
Ast.postfixExpr(ctx, lexeme);
|
|
1479
|
+
return cc;
|
|
1480
|
+
}
|
|
1481
|
+
return cc(ctx);
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function prefixExpr(ctx, cc) {
|
|
1486
|
+
if (match(ctx, TK_MINUS)) {
|
|
1487
|
+
eat(ctx, TK_MINUS);
|
|
1488
|
+
const ret = function (ctx) {
|
|
1489
|
+
return postfixExpr(ctx, function (ctx) {
|
|
1490
|
+
Ast.prefixExpr(ctx, "NEG");
|
|
1491
|
+
return cc;
|
|
1492
|
+
});
|
|
1493
|
+
};
|
|
1494
|
+
ret.cls = "number"; // use number because of convention
|
|
1495
|
+
return ret;
|
|
1496
|
+
}
|
|
1497
|
+
return postfixExpr(ctx, cc);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function getPrecedence(op) {
|
|
1501
|
+
return {
|
|
1502
|
+
"": 0,
|
|
1503
|
+
OR: 1,
|
|
1504
|
+
AND: 2,
|
|
1505
|
+
EQ: 3,
|
|
1506
|
+
NE: 3,
|
|
1507
|
+
LT: 4,
|
|
1508
|
+
GT: 4,
|
|
1509
|
+
LE: 4,
|
|
1510
|
+
GE: 4,
|
|
1511
|
+
CONCAT: 5,
|
|
1512
|
+
ADD: 5,
|
|
1513
|
+
SUB: 5,
|
|
1514
|
+
MUL: 6,
|
|
1515
|
+
DIV: 6,
|
|
1516
|
+
MOD: 6,
|
|
1517
|
+
POW: 7
|
|
1518
|
+
}[op];
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function binaryExpr(ctx, prevOp, cc) {
|
|
1522
|
+
return prefixExpr(ctx, function (ctx) {
|
|
1523
|
+
if (match(ctx, TK_BINOP)) {
|
|
1524
|
+
eat(ctx, TK_BINOP);
|
|
1525
|
+
const ret = function (ctx) {
|
|
1526
|
+
let op = env.findWord(ctx, lexeme).name;
|
|
1527
|
+
if (getPrecedence(prevOp) < getPrecedence(op)) {
|
|
1528
|
+
return binaryExpr(ctx, op, function (ctx, prevOp) {
|
|
1529
|
+
// This continuation's purpose is to construct a right recursive
|
|
1530
|
+
// binary expression node. If the previous node is a binary node
|
|
1531
|
+
// with equal or higher precedence, then we get here from the left
|
|
1532
|
+
// recursive branch below and there is no way to know the current
|
|
1533
|
+
// operator unless it gets passed as an argument, which is what
|
|
1534
|
+
// prevOp is for.
|
|
1535
|
+
if (prevOp !== undefined) {
|
|
1536
|
+
op = prevOp;
|
|
1537
|
+
}
|
|
1538
|
+
Ast.binaryExpr(ctx, op);
|
|
1539
|
+
return cc(ctx);
|
|
1540
|
+
});
|
|
1541
|
+
} else {
|
|
1542
|
+
Ast.binaryExpr(ctx, prevOp);
|
|
1543
|
+
return binaryExpr(ctx, op, function (ctx, prevOp) {
|
|
1544
|
+
if (prevOp !== undefined) {
|
|
1545
|
+
op = prevOp;
|
|
1546
|
+
}
|
|
1547
|
+
return cc(ctx, op);
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
ret.cls = "operator";
|
|
1552
|
+
return ret;
|
|
1553
|
+
}
|
|
1554
|
+
return cc(ctx);
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function relationalExpr(ctx, cc) {
|
|
1559
|
+
return binaryExpr(ctx, "", function (ctx) {
|
|
1560
|
+
return cc(ctx);
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function condExpr(ctx, cc) {
|
|
1565
|
+
if (match(ctx, TK_CASE)) {
|
|
1566
|
+
return caseExpr(ctx, cc);
|
|
1567
|
+
}
|
|
1568
|
+
if (match(ctx, TK_IF)) {
|
|
1569
|
+
return ifExpr(ctx, cc);
|
|
1570
|
+
}
|
|
1571
|
+
return relationalExpr(ctx, cc);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function ifExpr(ctx, cc) {
|
|
1575
|
+
eat(ctx, TK_IF);
|
|
1576
|
+
const ret = function (ctx) {
|
|
1577
|
+
return exprsStart(ctx, TK_THEN, function (ctx) {
|
|
1578
|
+
eat(ctx, TK_THEN);
|
|
1579
|
+
return exprsStart(ctx, TK_ELSE, function (ctx) {
|
|
1580
|
+
eat(ctx, TK_ELSE);
|
|
1581
|
+
return expr(ctx, function (ctx) {
|
|
1582
|
+
Ast.ifExpr(ctx);
|
|
1583
|
+
cc.cls = "keyword";
|
|
1584
|
+
return cc;
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
};
|
|
1589
|
+
ret.cls = "keyword";
|
|
1590
|
+
return ret;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function caseExpr(ctx, cc) {
|
|
1594
|
+
eat(ctx, TK_CASE);
|
|
1595
|
+
const ret = function (ctx) {
|
|
1596
|
+
return exprsStart(ctx, TK_OF, function (ctx) {
|
|
1597
|
+
eat(ctx, TK_OF);
|
|
1598
|
+
startCounter(ctx);
|
|
1599
|
+
return ofClauses(ctx, function (ctx) {
|
|
1600
|
+
Ast.caseExpr(ctx, ctx.state.exprc);
|
|
1601
|
+
stopCounter(ctx);
|
|
1602
|
+
eat(ctx, TK_END);
|
|
1603
|
+
cc.cls = "keyword";
|
|
1604
|
+
return cc;
|
|
1605
|
+
});
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
ret.cls = "keyword";
|
|
1609
|
+
return ret;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function ofClauses(ctx, cc) {
|
|
1613
|
+
return ofClause(ctx, function (ctx) {
|
|
1614
|
+
countCounter(ctx);
|
|
1615
|
+
if (!match(ctx, TK_END)) {
|
|
1616
|
+
return ofClauses(ctx, cc);
|
|
1617
|
+
}
|
|
1618
|
+
return cc(ctx);
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function ofClause(ctx, cc) {
|
|
1623
|
+
const ret = function (ctx) {
|
|
1624
|
+
return pattern(ctx, function (ctx) {
|
|
1625
|
+
eat(ctx, TK_COLON);
|
|
1626
|
+
const ret = function (ctx) {
|
|
1627
|
+
return expr(ctx, function (ctx) {
|
|
1628
|
+
Ast.ofClause(ctx);
|
|
1629
|
+
return cc(ctx);
|
|
1630
|
+
});
|
|
1631
|
+
};
|
|
1632
|
+
ret.cls = "punc";
|
|
1633
|
+
return ret;
|
|
1634
|
+
});
|
|
1635
|
+
};
|
|
1636
|
+
ret.cls = "keyword";
|
|
1637
|
+
return ret;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function pattern(ctx, cc) {
|
|
1641
|
+
// FIXME only matches idents and literals for now
|
|
1642
|
+
if (match(ctx, TK_IDENT)) {
|
|
1643
|
+
return ident(ctx, cc);
|
|
1644
|
+
}
|
|
1645
|
+
if (match(ctx, TK_NUM)) {
|
|
1646
|
+
return number(ctx, cc);
|
|
1647
|
+
}
|
|
1648
|
+
if (match(ctx, TK_BOOL)) {
|
|
1649
|
+
return bool(ctx, cc);
|
|
1650
|
+
}
|
|
1651
|
+
return str(ctx, cc);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// function thenClause(ctx, cc) {
|
|
1655
|
+
// eat(ctx, TK_THEN);
|
|
1656
|
+
// const ret = function (ctx) {
|
|
1657
|
+
// return exprsStart(ctx, TK_ELSE, function (ctx) {
|
|
1658
|
+
// if (match(ctx, TK_ELSE)) {
|
|
1659
|
+
// return elseClause(ctx, cc);
|
|
1660
|
+
// } else {
|
|
1661
|
+
// return cc(ctx);
|
|
1662
|
+
// }
|
|
1663
|
+
// });
|
|
1664
|
+
// };
|
|
1665
|
+
// ret.cls = "keyword";
|
|
1666
|
+
// return ret;
|
|
1667
|
+
// }
|
|
1668
|
+
|
|
1669
|
+
// function elseClause(ctx, cc) {
|
|
1670
|
+
// eat(ctx, TK_ELSE);
|
|
1671
|
+
// const ret = function (ctx) {
|
|
1672
|
+
// return exprsStart(ctx, TK_END, cc);
|
|
1673
|
+
// };
|
|
1674
|
+
// ret.cls = "keyword";
|
|
1675
|
+
// return ret;
|
|
1676
|
+
// }
|
|
1677
|
+
|
|
1678
|
+
function expr(ctx, cc) {
|
|
1679
|
+
let ret;
|
|
1680
|
+
if (match(ctx, TK_LET)) {
|
|
1681
|
+
ret = letDef(ctx, cc);
|
|
1682
|
+
} else {
|
|
1683
|
+
ret = condExpr(ctx, cc);
|
|
1684
|
+
}
|
|
1685
|
+
return ret;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function emptyInput(ctx) {
|
|
1689
|
+
return peek(ctx) === 0;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function emptyExpr(ctx) {
|
|
1693
|
+
return emptyInput(ctx) ||
|
|
1694
|
+
match(ctx, TK_THEN) ||
|
|
1695
|
+
match(ctx, TK_ELSE) ||
|
|
1696
|
+
match(ctx, TK_OR) ||
|
|
1697
|
+
match(ctx, TK_END) ||
|
|
1698
|
+
match(ctx, TK_DOT);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function countCounter(ctx) {
|
|
1702
|
+
ctx.state.exprc++;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function startCounter(ctx) {
|
|
1706
|
+
ctx.state.exprcStack.push(ctx.state.exprc);
|
|
1707
|
+
ctx.state.exprc = 0;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function stopCounter(ctx) {
|
|
1711
|
+
ctx.state.exprc = ctx.state.exprcStack.pop();
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function exprsStart(ctx, brk, cc) {
|
|
1715
|
+
startCounter(ctx);
|
|
1716
|
+
return exprs(ctx, brk, cc);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function exprsFinish(ctx, cc) {
|
|
1720
|
+
Ast.exprs(ctx, ctx.state.exprc);
|
|
1721
|
+
stopCounter(ctx);
|
|
1722
|
+
return cc(ctx);
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
function exprs(ctx, brk, cc) {
|
|
1726
|
+
if (match(ctx, TK_DOT)) { // second dot
|
|
1727
|
+
eat(ctx, TK_DOT);
|
|
1728
|
+
const ret = function (ctx) {
|
|
1729
|
+
return exprsFinish(ctx, cc);
|
|
1730
|
+
};
|
|
1731
|
+
ret.cls = "punc";
|
|
1732
|
+
return ret;
|
|
1733
|
+
}
|
|
1734
|
+
return expr(ctx, function (ctx) {
|
|
1735
|
+
countCounter(ctx);
|
|
1736
|
+
let ret;
|
|
1737
|
+
if (match(ctx, TK_DOT)) {
|
|
1738
|
+
eat(ctx, TK_DOT);
|
|
1739
|
+
ret = function (ctx) {
|
|
1740
|
+
if (emptyInput(ctx) || emptyExpr(ctx)) {
|
|
1741
|
+
return exprsFinish(ctx, cc);
|
|
1742
|
+
}
|
|
1743
|
+
return exprs(ctx, brk, cc);
|
|
1744
|
+
};
|
|
1745
|
+
ret.cls = "punc";
|
|
1746
|
+
return ret;
|
|
1747
|
+
} else if (match(ctx, brk)) {
|
|
1748
|
+
ret = function (ctx) {
|
|
1749
|
+
return exprsFinish(ctx, cc);
|
|
1750
|
+
};
|
|
1751
|
+
ret.cls = "punc";
|
|
1752
|
+
return ret;
|
|
1753
|
+
} else {
|
|
1754
|
+
if (emptyInput(ctx) || emptyExpr(ctx)) {
|
|
1755
|
+
return exprsFinish(ctx, cc);
|
|
1756
|
+
}
|
|
1757
|
+
return exprs(ctx, brk, cc);
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
function program(ctx, cc) {
|
|
1763
|
+
return exprsStart(ctx, TK_DOT, function (ctx) {
|
|
1764
|
+
let nid;
|
|
1765
|
+
while (Ast.peek(ctx) !== nid) {
|
|
1766
|
+
nid = Ast.pop(ctx);
|
|
1767
|
+
folder.fold(ctx, nid); // fold the exprs on top
|
|
1768
|
+
}
|
|
1769
|
+
Ast.exprs(ctx, ctx.state.nodeStack.length, true);
|
|
1770
|
+
Ast.program(ctx);
|
|
1771
|
+
assert(cc === null, "internal error, expecting null continuation");
|
|
1772
|
+
return cc;
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
window.gcexports.program = program;
|
|
1777
|
+
|
|
1778
|
+
/*
|
|
1779
|
+
|
|
1780
|
+
fn = { head, body }
|
|
1781
|
+
|
|
1782
|
+
*/
|
|
1783
|
+
|
|
1784
|
+
function letDef(ctx, cc) {
|
|
1785
|
+
if (match(ctx, TK_LET)) {
|
|
1786
|
+
eat(ctx, TK_LET);
|
|
1787
|
+
const ret = function (ctx) {
|
|
1788
|
+
const ret = defName(ctx, function (ctx) {
|
|
1789
|
+
const name = Ast.node(ctx, Ast.pop(ctx)).elts[0];
|
|
1790
|
+
// nid=0 means def not finished yet
|
|
1791
|
+
env.addWord(ctx, name, {
|
|
1792
|
+
tk: TK_IDENT,
|
|
1793
|
+
cls: "function",
|
|
1794
|
+
length: 0,
|
|
1795
|
+
nid: 0,
|
|
1796
|
+
name
|
|
1797
|
+
});
|
|
1798
|
+
ctx.state.paramc = 0;
|
|
1799
|
+
env.enterEnv(ctx, name); // FIXME need to link to outer env
|
|
1800
|
+
return params(ctx, TK_EQUAL, function (ctx) {
|
|
1801
|
+
const func = env.findWord(ctx, topEnv(ctx).name);
|
|
1802
|
+
func.length = ctx.state.paramc;
|
|
1803
|
+
func.env = topEnv(ctx);
|
|
1804
|
+
eat(ctx, TK_EQUAL);
|
|
1805
|
+
const ret = function (ctx) {
|
|
1806
|
+
return exprsStart(ctx, TK_DOT, function (ctx) {
|
|
1807
|
+
const def = env.findWord(ctx, topEnv(ctx).name);
|
|
1808
|
+
def.nid = Ast.peek(ctx); // save node id for aliased code
|
|
1809
|
+
env.exitEnv(ctx);
|
|
1810
|
+
Ast.letDef(ctx); // Clean up stack
|
|
1811
|
+
return cc;
|
|
1812
|
+
});
|
|
1813
|
+
};
|
|
1814
|
+
ret.cls = "punc";
|
|
1815
|
+
return ret;
|
|
1816
|
+
});
|
|
1817
|
+
});
|
|
1818
|
+
ret.cls = "def";
|
|
1819
|
+
return ret;
|
|
1820
|
+
};
|
|
1821
|
+
ret.cls = "keyword";
|
|
1822
|
+
return ret;
|
|
1823
|
+
}
|
|
1824
|
+
return name(ctx, cc);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// TODO add argument for specifying the break token.
|
|
1828
|
+
// e.g. TK_EQUAL | TK_VERTICALBAR
|
|
1829
|
+
// params(ctx, brk, cc) {..}
|
|
1830
|
+
function params(ctx, brk, cc) {
|
|
1831
|
+
if (match(ctx, brk)) {
|
|
1832
|
+
return cc;
|
|
1833
|
+
}
|
|
1834
|
+
const ret = function (ctx) {
|
|
1835
|
+
const ret = defName(ctx, (ctx) => {
|
|
1836
|
+
Ast.pop(ctx); // Throw away name.
|
|
1837
|
+
ctx.state.paramc++;
|
|
1838
|
+
return params(ctx, brk, cc);
|
|
1839
|
+
});
|
|
1840
|
+
ret.cls = "param";
|
|
1841
|
+
return ret;
|
|
1842
|
+
};
|
|
1843
|
+
ret.cls = "param";
|
|
1844
|
+
return ret;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function topEnv(ctx) {
|
|
1848
|
+
return ctx.state.env[ctx.state.env.length - 1];
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
window.gcexports.topEnv = topEnv;
|
|
1852
|
+
window.gcexports.firstTime = true;
|
|
1853
|
+
function parse(stream, state) {
|
|
1854
|
+
const ctx = {
|
|
1855
|
+
scan: scanner(stream, state.env[0].lexicon),
|
|
1856
|
+
state
|
|
1857
|
+
};
|
|
1858
|
+
let cls;
|
|
1859
|
+
let t0;
|
|
1860
|
+
try {
|
|
1861
|
+
let c;
|
|
1862
|
+
while ((c = stream.peek()) && (c === " " || c === "\t")) {
|
|
1863
|
+
stream.next();
|
|
1864
|
+
}
|
|
1865
|
+
// if this is a blank line, treat it as a comment
|
|
1866
|
+
if (stream.peek() === undefined) {
|
|
1867
|
+
throw new Error("comment");
|
|
1868
|
+
}
|
|
1869
|
+
// call the continuation and store the next continuation
|
|
1870
|
+
if (state.cc === null) {
|
|
1871
|
+
next(ctx);
|
|
1872
|
+
return "comment";
|
|
1873
|
+
}
|
|
1874
|
+
t0 = new Date();
|
|
1875
|
+
const cc = state.cc = state.cc(ctx, null);
|
|
1876
|
+
if (cc) {
|
|
1877
|
+
cls = cc.cls;
|
|
1878
|
+
}
|
|
1879
|
+
if (cc === null) {
|
|
1880
|
+
// FIXME make all paths go through a resume function.
|
|
1881
|
+
if (state.errors.length > 0) {
|
|
1882
|
+
throw new Error(state.errors);
|
|
1883
|
+
} else {
|
|
1884
|
+
return Ast.poolToJSON(ctx);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
while ((c = stream.peek()) &&
|
|
1888
|
+
(c === " " || c === "\t")) {
|
|
1889
|
+
stream.next();
|
|
1890
|
+
}
|
|
1891
|
+
} catch (x) {
|
|
1892
|
+
console.log("catch() x=" + x);
|
|
1893
|
+
if (x instanceof Error) {
|
|
1894
|
+
if (x.message === "comment") {
|
|
1895
|
+
cls = x;
|
|
1896
|
+
} else {
|
|
1897
|
+
console.log("catch() x=" + x.stack);
|
|
1898
|
+
// next(ctx);
|
|
1899
|
+
return Ast.poolToJSON(ctx);
|
|
1900
|
+
// state.cc = null; // done for now.
|
|
1901
|
+
// cls = "error";
|
|
1902
|
+
// throw new Error(JSON.stringify(window.gcexports.errors, null, 2));
|
|
1903
|
+
}
|
|
1904
|
+
} else {
|
|
1905
|
+
// throw x
|
|
1906
|
+
next(ctx);
|
|
1907
|
+
cls = "error";
|
|
1908
|
+
console.log(x.stack);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
const t1 = new Date();
|
|
1912
|
+
parseCount++;
|
|
1913
|
+
parseTime += t1 - t0;
|
|
1914
|
+
window.gcexports.coords = state.coords;
|
|
1915
|
+
return cls;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
let lexeme = "";
|
|
1919
|
+
|
|
1920
|
+
function scanner(stream, globalLexicon) {
|
|
1921
|
+
return {
|
|
1922
|
+
start,
|
|
1923
|
+
stream,
|
|
1924
|
+
lexeme: function () {
|
|
1925
|
+
return lexeme;
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
// begin private functions
|
|
1930
|
+
|
|
1931
|
+
function peekCC() {
|
|
1932
|
+
return stream.peek() && stream.peek().charCodeAt(0) || 0;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function nextCC() {
|
|
1936
|
+
return stream.peek() && stream.next().charCodeAt(0) || 0;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function start(ctx) {
|
|
1940
|
+
let c;
|
|
1941
|
+
lexeme = "";
|
|
1942
|
+
while (stream.peek() !== undefined) {
|
|
1943
|
+
switch ((c = stream.next().charCodeAt(0))) {
|
|
1944
|
+
case 32: // space
|
|
1945
|
+
case 9: // tab
|
|
1946
|
+
case 10: // new line
|
|
1947
|
+
case 13: // carriage return
|
|
1948
|
+
c = " ";
|
|
1949
|
+
continue;
|
|
1950
|
+
case 46: // dot
|
|
1951
|
+
if (isNumeric(stream.peek())) {
|
|
1952
|
+
return number(c);
|
|
1953
|
+
}
|
|
1954
|
+
lexeme += String.fromCharCode(c);
|
|
1955
|
+
return TK_DOT;
|
|
1956
|
+
case 44: // comma
|
|
1957
|
+
lexeme += String.fromCharCode(c);
|
|
1958
|
+
return TK_COMMA;
|
|
1959
|
+
case 58: // colon
|
|
1960
|
+
lexeme += String.fromCharCode(c);
|
|
1961
|
+
return TK_COLON;
|
|
1962
|
+
case 61: // equal
|
|
1963
|
+
lexeme += String.fromCharCode(c);
|
|
1964
|
+
return TK_EQUAL;
|
|
1965
|
+
case 40: // left paren
|
|
1966
|
+
lexeme += String.fromCharCode(c);
|
|
1967
|
+
return TK_LEFTPAREN;
|
|
1968
|
+
case 41: // right paren
|
|
1969
|
+
lexeme += String.fromCharCode(c);
|
|
1970
|
+
return TK_RIGHTPAREN;
|
|
1971
|
+
case 45: // dash
|
|
1972
|
+
lexeme += String.fromCharCode(c);
|
|
1973
|
+
return TK_MINUS;
|
|
1974
|
+
case 60: // left angle
|
|
1975
|
+
lexeme += String.fromCharCode(c);
|
|
1976
|
+
return TK_LEFTANGLE;
|
|
1977
|
+
case 62: // right angle
|
|
1978
|
+
lexeme += String.fromCharCode(c);
|
|
1979
|
+
return TK_RIGHTANGLE;
|
|
1980
|
+
case 91: // left bracket
|
|
1981
|
+
lexeme += String.fromCharCode(c);
|
|
1982
|
+
return TK_LEFTBRACKET;
|
|
1983
|
+
case 93: // right bracket
|
|
1984
|
+
lexeme += String.fromCharCode(c);
|
|
1985
|
+
return TK_RIGHTBRACKET;
|
|
1986
|
+
case 123: // left brace
|
|
1987
|
+
lexeme += String.fromCharCode(c);
|
|
1988
|
+
return TK_LEFTBRACE;
|
|
1989
|
+
case 125: // right brace
|
|
1990
|
+
lexeme += String.fromCharCode(c);
|
|
1991
|
+
if (ctx.state.inStr) {
|
|
1992
|
+
return stringSuffix(ctx);
|
|
1993
|
+
}
|
|
1994
|
+
return TK_RIGHTBRACE;
|
|
1995
|
+
case CC_DOUBLEQUOTE:
|
|
1996
|
+
case CC_SINGLEQUOTE:
|
|
1997
|
+
case CC_BACKTICK:
|
|
1998
|
+
return string(ctx, c);
|
|
1999
|
+
case 96: // backquote
|
|
2000
|
+
case 47: // slash
|
|
2001
|
+
case 92: // backslash
|
|
2002
|
+
case 33: // !
|
|
2003
|
+
case 124: // |
|
|
2004
|
+
comment(c);
|
|
2005
|
+
throw new Error("comment");
|
|
2006
|
+
case 94: // caret
|
|
2007
|
+
case 42: // asterisk
|
|
2008
|
+
lexeme += String.fromCharCode(c);
|
|
2009
|
+
return c; // char code is the token id
|
|
2010
|
+
default:
|
|
2011
|
+
if ((c >= "A".charCodeAt(0) && c <= "Z".charCodeAt(0)) ||
|
|
2012
|
+
(c >= "a".charCodeAt(0) && c <= "z".charCodeAt(0)) ||
|
|
2013
|
+
(c === "_".charCodeAt(0))) {
|
|
2014
|
+
return ident(c);
|
|
2015
|
+
} else if (isNumeric(c) || c === ".".charCodeAt(0) && isNumeric(stream.peek())) {
|
|
2016
|
+
// lex += String.fromCharCode(c);
|
|
2017
|
+
// c = src.charCodeAt(curIndex++);
|
|
2018
|
+
// return TK_NUM;
|
|
2019
|
+
return number(c);
|
|
2020
|
+
} else {
|
|
2021
|
+
return 0;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
return 0;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
function isNumeric(c) {
|
|
2030
|
+
if (typeof c === "string") {
|
|
2031
|
+
c = c.charCodeAt(0);
|
|
2032
|
+
}
|
|
2033
|
+
return c >= "0".charCodeAt(0) && c <= "9".charCodeAt(0);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
function number(c) {
|
|
2037
|
+
// 123, 1.23, .123
|
|
2038
|
+
while (isNumeric(c) || c === ".".charCodeAt(0) && isNumeric(stream.peek())) {
|
|
2039
|
+
lexeme += String.fromCharCode(c);
|
|
2040
|
+
const s = stream.next();
|
|
2041
|
+
c = s ? s.charCodeAt(0) : 0;
|
|
2042
|
+
}
|
|
2043
|
+
if (c) {
|
|
2044
|
+
stream.backUp(1);
|
|
2045
|
+
} // otherwise, we are at the end of stream
|
|
2046
|
+
return TK_NUM;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// `abc` --> "abc"
|
|
2050
|
+
// `a${x}c` --> concat ["a", x, "b"]
|
|
2051
|
+
function string(ctx, c) {
|
|
2052
|
+
const quoteChar = c;
|
|
2053
|
+
ctx.state.quoteCharStack.push(c);
|
|
2054
|
+
lexeme += String.fromCharCode(c);
|
|
2055
|
+
c = nextCC();
|
|
2056
|
+
const inTemplateLiteral = quoteChar === CC_BACKTICK;
|
|
2057
|
+
if (inTemplateLiteral) {
|
|
2058
|
+
while (
|
|
2059
|
+
c !== quoteChar &&
|
|
2060
|
+
c !== 0 &&
|
|
2061
|
+
!(c === CC_DOLLAR && peekCC() === CC_LEFTBRACE)) {
|
|
2062
|
+
lexeme += String.fromCharCode(c);
|
|
2063
|
+
c = nextCC();
|
|
2064
|
+
}
|
|
2065
|
+
} else {
|
|
2066
|
+
while (c !== quoteChar && c !== 0) {
|
|
2067
|
+
lexeme += String.fromCharCode(c);
|
|
2068
|
+
c = nextCC();
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
if (quoteChar === CC_BACKTICK && c === CC_DOLLAR &&
|
|
2072
|
+
peekCC() === CC_LEFTBRACE) {
|
|
2073
|
+
nextCC(); // Eat CC_LEFTBRACE
|
|
2074
|
+
lexeme = lexeme.substring(1); // Strip off punct.
|
|
2075
|
+
return TK_STRPREFIX;
|
|
2076
|
+
} else if (c) {
|
|
2077
|
+
lexeme = lexeme.substring(1); // Strip off leading quote.
|
|
2078
|
+
return TK_STR;
|
|
2079
|
+
} else {
|
|
2080
|
+
return 0;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
function stringSuffix(ctx) {
|
|
2085
|
+
let c;
|
|
2086
|
+
const quoteCharStack = ctx.state.quoteCharStack;
|
|
2087
|
+
const quoteChar = quoteCharStack[quoteCharStack.length - 1];
|
|
2088
|
+
c = nextCC();
|
|
2089
|
+
const inTemplateLiteral = quoteChar === CC_BACKTICK;
|
|
2090
|
+
if (inTemplateLiteral) {
|
|
2091
|
+
while (c !== quoteChar && c !== 0 &&
|
|
2092
|
+
!(c === CC_DOLLAR &&
|
|
2093
|
+
peekCC() === CC_LEFTBRACE)) {
|
|
2094
|
+
lexeme += String.fromCharCode(c);
|
|
2095
|
+
c = nextCC();
|
|
2096
|
+
}
|
|
2097
|
+
} else {
|
|
2098
|
+
while (c !== quoteChar && c !== 0) {
|
|
2099
|
+
lexeme += String.fromCharCode(c);
|
|
2100
|
+
c = nextCC();
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
if (quoteChar === CC_BACKTICK && c === CC_DOLLAR &&
|
|
2104
|
+
peekCC() === CC_LEFTBRACE) {
|
|
2105
|
+
nextCC(); // Eat brace.
|
|
2106
|
+
lexeme = lexeme.substring(1); // Strip off leading brace and trailing brace.
|
|
2107
|
+
return TK_STRMIDDLE;
|
|
2108
|
+
} else if (c) {
|
|
2109
|
+
quoteCharStack.pop();
|
|
2110
|
+
lexeme = lexeme.substring(1); // Strip off leading braces.
|
|
2111
|
+
return TK_STRSUFFIX;
|
|
2112
|
+
} else {
|
|
2113
|
+
return 0;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
function comment(c) {
|
|
2118
|
+
const quoteChar = c;
|
|
2119
|
+
let s = stream.next();
|
|
2120
|
+
c = (s && s.charCodeAt(0)) || 0;
|
|
2121
|
+
|
|
2122
|
+
while (c !== quoteChar && c !== 10 && c !== 13 && c !== 0) {
|
|
2123
|
+
s = stream.next();
|
|
2124
|
+
c = (s && s.charCodeAt(0)) || 0;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
return TK_COMMENT;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
function ident(c) {
|
|
2131
|
+
while ((c >= "A".charCodeAt(0) && c <= "Z".charCodeAt(0)) ||
|
|
2132
|
+
(c >= "a".charCodeAt(0) && c <= "z".charCodeAt(0)) ||
|
|
2133
|
+
(c === "-".charCodeAt(0)) ||
|
|
2134
|
+
(c === "@".charCodeAt(0)) ||
|
|
2135
|
+
(c === "+".charCodeAt(0)) ||
|
|
2136
|
+
(c === "#".charCodeAt(0)) ||
|
|
2137
|
+
(c === "_".charCodeAt(0)) ||
|
|
2138
|
+
(c === "~".charCodeAt(0)) ||
|
|
2139
|
+
(c >= "0".charCodeAt(0) && c <= "9".charCodeAt(0))) {
|
|
2140
|
+
lexeme += String.fromCharCode(c);
|
|
2141
|
+
c = stream.peek() ? stream.next().charCodeAt(0) : 0;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
if (c) {
|
|
2145
|
+
stream.backUp(1);
|
|
2146
|
+
} // otherwise, we are at the end of stream
|
|
2147
|
+
|
|
2148
|
+
let tk = TK_IDENT;
|
|
2149
|
+
if (keywords[lexeme]) {
|
|
2150
|
+
tk = keywords[lexeme].tk;
|
|
2151
|
+
} else if (globalLexicon[lexeme]) {
|
|
2152
|
+
tk = globalLexicon[lexeme].tk;
|
|
2153
|
+
}
|
|
2154
|
+
return tk;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
const parser = {
|
|
2159
|
+
token: function (stream, state) {
|
|
2160
|
+
return parse(stream, state);
|
|
2161
|
+
},
|
|
2162
|
+
|
|
2163
|
+
parse,
|
|
2164
|
+
program,
|
|
2165
|
+
StringStream
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
window.gcexports.parse = parser.parse;
|
|
2169
|
+
if (window.isSynthetic) {
|
|
2170
|
+
// Export in node.
|
|
2171
|
+
// exports.parse = window.gcexports.parse;
|
|
2172
|
+
// exports.StringStream = window.gcexports.StringStream;
|
|
2173
|
+
// exports.program = program;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
return parser;
|
|
2177
|
+
})(); // end parser
|