@graffiticode/parser 0.1.3 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiticode/parser",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/ast.js CHANGED
@@ -171,6 +171,10 @@ export class Ast {
171
171
  };
172
172
  }
173
173
 
174
+ static hasSyntaxError(ctx) {
175
+ return ctx.state.errors && ctx.state.errors.length > 0;
176
+ }
177
+
174
178
  static dump(n) {
175
179
  let s;
176
180
  if (typeof n === "object") {
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 new Error(str);
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
- assertErr(ctx, false, "Expecting " + tokenToLexeme(tk) +
209
- ", found " + tokenToLexeme(nextToken) + ".", { from, to });
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("Expecting " + tokenToLexeme(tk) +
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
- // next(ctx);
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
@@ -7,7 +7,7 @@ const main = {
7
7
  parse(src, lexicon) {
8
8
  const stream = new parse.StringStream(src);
9
9
  const state = {
10
- cc: parse.program, // top level parsing function
10
+ cc: parse.program, // top level parsig function
11
11
  argc: 0,
12
12
  argcStack: [0],
13
13
  paramc: 0,
@@ -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();