@graffiticode/parser 0.1.3 → 0.1.5
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/ast.js +4 -0
- package/src/folder.js +3 -2
- package/src/parse.js +54 -15
- package/src/parser.js +1 -1
- package/src/parser.spec.js +120 -0
package/package.json
CHANGED
package/src/ast.js
CHANGED
package/src/folder.js
CHANGED
|
@@ -184,7 +184,7 @@ export class Folder {
|
|
|
184
184
|
if (word) {
|
|
185
185
|
if (word.cls === "val") {
|
|
186
186
|
if (word.val) {
|
|
187
|
-
Ast.string(ctx, word.val); // strip quotes;
|
|
187
|
+
Ast.string(ctx, word.val, node.coord); // strip quotes;
|
|
188
188
|
} else if (word.nid) {
|
|
189
189
|
let wrd;
|
|
190
190
|
if ((wrd = Ast.node(ctx, word.nid)).tag === "LAMBDA") {
|
|
@@ -229,6 +229,7 @@ export class Folder {
|
|
|
229
229
|
Ast.push(ctx, {
|
|
230
230
|
tag: name,
|
|
231
231
|
elts: [],
|
|
232
|
+
coord: node.coord,
|
|
232
233
|
});
|
|
233
234
|
}
|
|
234
235
|
}
|
|
@@ -238,7 +239,7 @@ export class Folder {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
static str(node) {
|
|
241
|
-
Ast.string(Folder.#ctx, node.elts[0]);
|
|
242
|
+
Ast.string(Folder.#ctx, node.elts[0], node.coord);
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
static bool(node) {
|
package/src/parse.js
CHANGED
|
@@ -71,6 +71,16 @@ export function assertErr(ctx, b, str, coord) {
|
|
|
71
71
|
"assertErr()",
|
|
72
72
|
"str=" + str,
|
|
73
73
|
);
|
|
74
|
+
// Push error into state errors collection
|
|
75
|
+
if (!ctx.state.errors) {
|
|
76
|
+
ctx.state.errors = [];
|
|
77
|
+
}
|
|
78
|
+
ctx.state.errors.push({
|
|
79
|
+
message: str,
|
|
80
|
+
coord
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Create error node in AST
|
|
74
84
|
Ast.error(ctx, str, coord);
|
|
75
85
|
throw new Error(str);
|
|
76
86
|
}
|
|
@@ -79,7 +89,9 @@ export function assertErr(ctx, b, str, coord) {
|
|
|
79
89
|
export const parse = (function () {
|
|
80
90
|
function assert(b, str) {
|
|
81
91
|
if (!b) {
|
|
82
|
-
throw
|
|
92
|
+
// Just throw a simple error with the message "Syntax Error" if none provided
|
|
93
|
+
// This will be caught by our catch-all handler
|
|
94
|
+
throw new Error(str || "Syntax Error");
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -160,6 +172,7 @@ export const parse = (function () {
|
|
|
160
172
|
const TK_STRPREFIX = 0xB2;
|
|
161
173
|
const TK_STRMIDDLE = 0xB3;
|
|
162
174
|
const TK_STRSUFFIX = 0xB4;
|
|
175
|
+
const TK_DOTDOT = 0xB5;
|
|
163
176
|
|
|
164
177
|
function tokenToLexeme(tk) {
|
|
165
178
|
switch (tk) {
|
|
@@ -191,6 +204,7 @@ export const parse = (function () {
|
|
|
191
204
|
case TK_PLUS: return "a '+'";
|
|
192
205
|
case TK_MINUS: return "a '-'";
|
|
193
206
|
case TK_DOT: return "a '.'";
|
|
207
|
+
case TK_DOTDOT: return "a '..'";
|
|
194
208
|
case TK_COLON: return "a ':'";
|
|
195
209
|
case TK_COMMA: return "a ','";
|
|
196
210
|
case TK_BACKQUOTE: return "a '`'";
|
|
@@ -205,11 +219,14 @@ export const parse = (function () {
|
|
|
205
219
|
if (nextToken !== tk) {
|
|
206
220
|
const to = getPos(ctx);
|
|
207
221
|
const from = to - lexeme.length;
|
|
208
|
-
|
|
209
|
-
|
|
222
|
+
const errorMessage = "Expecting " + tokenToLexeme(tk) +
|
|
223
|
+
", found " + tokenToLexeme(nextToken) + ".";
|
|
224
|
+
|
|
225
|
+
// Use assertErr to ensure error is tracked consistently
|
|
226
|
+
assertErr(ctx, false, errorMessage, { from, to });
|
|
227
|
+
|
|
210
228
|
next(ctx); // Advance past error.
|
|
211
|
-
throw new Error(
|
|
212
|
-
", found " + tokenToLexeme(nextToken) + ".");
|
|
229
|
+
throw new Error(errorMessage);
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
|
|
@@ -283,20 +300,20 @@ export const parse = (function () {
|
|
|
283
300
|
function str(ctx, cc) {
|
|
284
301
|
if (match(ctx, TK_STR)) {
|
|
285
302
|
eat(ctx, TK_STR);
|
|
286
|
-
Ast.string(ctx, lexeme); // strip quotes;
|
|
303
|
+
Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
|
|
287
304
|
cc.cls = "string";
|
|
288
305
|
return cc;
|
|
289
306
|
} else if (match(ctx, TK_STRPREFIX)) {
|
|
290
307
|
ctx.state.inStr++;
|
|
291
308
|
eat(ctx, TK_STRPREFIX);
|
|
292
309
|
startCounter(ctx);
|
|
293
|
-
Ast.string(ctx, lexeme); // strip quotes;
|
|
310
|
+
Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
|
|
294
311
|
countCounter(ctx);
|
|
295
312
|
const ret = function (ctx) {
|
|
296
313
|
return strSuffix(ctx, function (ctx) {
|
|
297
314
|
ctx.state.inStr--;
|
|
298
315
|
eat(ctx, TK_STRSUFFIX);
|
|
299
|
-
Ast.string(ctx, lexeme); // strip quotes;
|
|
316
|
+
Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
|
|
300
317
|
countCounter(ctx);
|
|
301
318
|
Ast.list(ctx, ctx.state.exprc);
|
|
302
319
|
stopCounter(ctx);
|
|
@@ -320,7 +337,7 @@ export const parse = (function () {
|
|
|
320
337
|
if (match(ctx, TK_STRMIDDLE)) {
|
|
321
338
|
// Not done yet.
|
|
322
339
|
eat(ctx, TK_STRMIDDLE);
|
|
323
|
-
Ast.string(ctx, lexeme); // strip quotes;
|
|
340
|
+
Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
|
|
324
341
|
countCounter(ctx);
|
|
325
342
|
ret = function (ctx) {
|
|
326
343
|
return strSuffix(ctx, resume);
|
|
@@ -350,7 +367,7 @@ export const parse = (function () {
|
|
|
350
367
|
function bindingName(ctx, cc) {
|
|
351
368
|
if (match(ctx, TK_IDENT)) {
|
|
352
369
|
eat(ctx, TK_IDENT);
|
|
353
|
-
Ast.string(ctx, lexeme);
|
|
370
|
+
Ast.string(ctx, lexeme, getCoord(ctx));
|
|
354
371
|
cc.cls = "variable";
|
|
355
372
|
return cc;
|
|
356
373
|
}
|
|
@@ -409,8 +426,6 @@ export const parse = (function () {
|
|
|
409
426
|
}
|
|
410
427
|
}
|
|
411
428
|
} else {
|
|
412
|
-
// cc.cls = "error";
|
|
413
|
-
// Ast.error(ctx, "Name '" + lexeme + "' not found.", coord);
|
|
414
429
|
// Create a tag value.
|
|
415
430
|
Ast.name(ctx, lexeme, coord);
|
|
416
431
|
}
|
|
@@ -853,9 +868,17 @@ export const parse = (function () {
|
|
|
853
868
|
return exprsStart(ctx, TK_DOT, function (ctx) {
|
|
854
869
|
let nid;
|
|
855
870
|
while (Ast.peek(ctx) !== nid) {
|
|
871
|
+
console.log(
|
|
872
|
+
"program()",
|
|
873
|
+
"peek()=" + Ast.peek(ctx),
|
|
874
|
+
);
|
|
856
875
|
nid = Ast.pop(ctx);
|
|
857
876
|
folder.fold(ctx, nid); // Fold the exprs on top
|
|
858
877
|
}
|
|
878
|
+
console.log(
|
|
879
|
+
"program()",
|
|
880
|
+
"nodeStack.length=" + ctx.state.nodeStack.length,
|
|
881
|
+
);
|
|
859
882
|
Ast.exprs(ctx, ctx.state.nodeStack.length, true);
|
|
860
883
|
Ast.program(ctx);
|
|
861
884
|
assert(cc === null, "internal error, expecting null continuation");
|
|
@@ -988,17 +1011,28 @@ export const parse = (function () {
|
|
|
988
1011
|
cls = x;
|
|
989
1012
|
} else {
|
|
990
1013
|
console.log("catch() x=" + x.stack);
|
|
991
|
-
|
|
1014
|
+
|
|
1015
|
+
// Add generic "Syntax Error" message if not already handled
|
|
1016
|
+
if (!Ast.hasSyntaxError(ctx)) {
|
|
1017
|
+
// Create a generic syntax error
|
|
1018
|
+
const coord = getCoord(ctx) || { from: 0, to: stream.pos || 0 };
|
|
1019
|
+
Ast.error(ctx, "Syntax Error", coord);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
992
1022
|
state.cc = null; // done for now.
|
|
993
1023
|
return Ast.poolToJSON(ctx);
|
|
994
|
-
// cls = "error";
|
|
995
|
-
// throw new Error(JSON.stringify(window.gcexports.errors, null, 2));
|
|
996
1024
|
}
|
|
997
1025
|
} else {
|
|
998
1026
|
// throw x
|
|
999
1027
|
next(ctx);
|
|
1000
1028
|
cls = "error";
|
|
1001
1029
|
console.log(x.stack);
|
|
1030
|
+
|
|
1031
|
+
// Add generic "Syntax Error" message for non-Error exceptions too
|
|
1032
|
+
if (!Ast.hasSyntaxError(ctx)) {
|
|
1033
|
+
const coord = getCoord(ctx) || { from: 0, to: stream.pos || 0 };
|
|
1034
|
+
Ast.error(ctx, "Syntax Error", coord);
|
|
1035
|
+
}
|
|
1002
1036
|
}
|
|
1003
1037
|
}
|
|
1004
1038
|
const t1 = new Date();
|
|
@@ -1043,6 +1077,11 @@ export const parse = (function () {
|
|
|
1043
1077
|
case 46: // dot
|
|
1044
1078
|
if (isNumeric(stream.peek())) {
|
|
1045
1079
|
return number(c);
|
|
1080
|
+
// } else if (stream.peek() === 46) {
|
|
1081
|
+
// TODO
|
|
1082
|
+
// stream.next();
|
|
1083
|
+
// lexeme += String.fromCharCode(c);
|
|
1084
|
+
// return TK_DOTDOT;
|
|
1046
1085
|
}
|
|
1047
1086
|
lexeme += String.fromCharCode(c);
|
|
1048
1087
|
return TK_DOT;
|
package/src/parser.js
CHANGED
package/src/parser.spec.js
CHANGED
|
@@ -320,6 +320,126 @@ describe("parser integration tests", () => {
|
|
|
320
320
|
expect(identNodeB).not.toBeNull();
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
it("should handle syntax errors with generic error message", async () => {
|
|
324
|
+
// Test various syntax errors and confirm they're caught properly
|
|
325
|
+
let errorNode = null;
|
|
326
|
+
let result = null;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
// Unclosed string - missing closing quote
|
|
330
|
+
result = await parser.parse(0, "'unclosed string..");
|
|
331
|
+
} catch (e) {
|
|
332
|
+
// Check for expected error (we should now have a robust parser that doesn't throw)
|
|
333
|
+
console.error("Unexpected error:", e);
|
|
334
|
+
throw e;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Should get a result even with syntax error
|
|
338
|
+
expect(result).toHaveProperty("root");
|
|
339
|
+
|
|
340
|
+
// Find the ERROR node
|
|
341
|
+
errorNode = null;
|
|
342
|
+
for (const key in result) {
|
|
343
|
+
if (key !== "root") {
|
|
344
|
+
const node = result[key];
|
|
345
|
+
if (node.tag === "ERROR") {
|
|
346
|
+
errorNode = node;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Should have an ERROR node
|
|
353
|
+
expect(errorNode).not.toBeNull();
|
|
354
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
355
|
+
|
|
356
|
+
// The error message should be either the specific syntax error or the generic "Syntax Error"
|
|
357
|
+
// Verify that we have an ERROR node with the proper structure
|
|
358
|
+
expect(errorNode.elts.length).toBeGreaterThan(0);
|
|
359
|
+
|
|
360
|
+
// The error structure might be different based on implementation details
|
|
361
|
+
// We just want to ensure there's an error node in the result
|
|
362
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("should handle mismatched brackets with syntax error", async () => {
|
|
366
|
+
let result = null;
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
// Missing closing bracket
|
|
370
|
+
result = await parser.parse(0, "[1, 2, 3..");
|
|
371
|
+
} catch (e) {
|
|
372
|
+
console.error("Unexpected error:", e);
|
|
373
|
+
throw e;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Should get a result even with syntax error
|
|
377
|
+
expect(result).toHaveProperty("root");
|
|
378
|
+
|
|
379
|
+
// Find the ERROR node
|
|
380
|
+
let errorNode = null;
|
|
381
|
+
for (const key in result) {
|
|
382
|
+
if (key !== "root") {
|
|
383
|
+
const node = result[key];
|
|
384
|
+
if (node.tag === "ERROR") {
|
|
385
|
+
errorNode = node;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Should have an ERROR node
|
|
392
|
+
expect(errorNode).not.toBeNull();
|
|
393
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
394
|
+
|
|
395
|
+
// The error message should indicate the syntax error
|
|
396
|
+
// Verify that we have an ERROR node with the proper structure
|
|
397
|
+
expect(errorNode.elts.length).toBeGreaterThan(0);
|
|
398
|
+
|
|
399
|
+
// The error structure might be different based on implementation details
|
|
400
|
+
// We just want to ensure there's an error node in the result
|
|
401
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("should handle invalid token sequences with syntax error", async () => {
|
|
405
|
+
let result = null;
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
// Invalid sequence of tokens
|
|
409
|
+
result = await parser.parse(0, "if then else..");
|
|
410
|
+
} catch (e) {
|
|
411
|
+
console.error("Unexpected error:", e);
|
|
412
|
+
throw e;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Should get a result even with syntax error
|
|
416
|
+
expect(result).toHaveProperty("root");
|
|
417
|
+
|
|
418
|
+
// Find the ERROR node
|
|
419
|
+
let errorNode = null;
|
|
420
|
+
for (const key in result) {
|
|
421
|
+
if (key !== "root") {
|
|
422
|
+
const node = result[key];
|
|
423
|
+
if (node.tag === "ERROR") {
|
|
424
|
+
errorNode = node;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Should have an ERROR node
|
|
431
|
+
expect(errorNode).not.toBeNull();
|
|
432
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
433
|
+
|
|
434
|
+
// The error message should indicate the syntax error
|
|
435
|
+
// Verify that we have an ERROR node with the proper structure
|
|
436
|
+
expect(errorNode.elts.length).toBeGreaterThan(0);
|
|
437
|
+
|
|
438
|
+
// The error structure might be different based on implementation details
|
|
439
|
+
// We just want to ensure there's an error node in the result
|
|
440
|
+
expect(errorNode.tag).toBe("ERROR");
|
|
441
|
+
});
|
|
442
|
+
|
|
323
443
|
it("should perform parse-time evaluation for adding two numbers", async () => {
|
|
324
444
|
// Create parser with custom lexicon that defines 'add' function
|
|
325
445
|
const customLexiconCache = new Map();
|