@graffiticode/parser 0.1.2 → 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/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ export default {
2
+ testEnvironment: "node",
3
+ forceExit: true,
4
+ verbose: true,
5
+ };
@@ -0,0 +1,6 @@
1
+ export default {
2
+ testEnvironment: "node",
3
+ forceExit: true,
4
+ // Set to true to see all test details
5
+ verbose: false
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiticode/parser",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -14,7 +14,8 @@
14
14
  "scripts": {
15
15
  "lint": "eslint src/",
16
16
  "lint:fix": "eslint --fix src/",
17
- "test": "NODE_OPTIONS=--experimental-vm-modules jest"
17
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
18
+ "console-test": "node console-output-test.js"
18
19
  },
19
20
  "keywords": [],
20
21
  "author": "",
package/src/ast.js CHANGED
@@ -148,13 +148,31 @@ export class Ast {
148
148
 
149
149
  static poolToJSON(ctx) {
150
150
  const nodePool = ctx.state.nodePool;
151
- const obj = {};
152
- for (let i = 1; i < nodePool.length; i++) {
153
- const n = nodePool[i];
154
- obj[i] = nodeToJSON(n);
155
- }
156
- obj.root = (nodePool.length - 1);
157
- return obj;
151
+ const rootId = nodePool.length - 1;
152
+ // Find all nodes transitively referenced from root
153
+ const referencedNodes = new Set();
154
+ const collectReferences = (nodeId) => {
155
+ if (!nodeId || referencedNodes.has(nodeId)) return;
156
+ referencedNodes.add(nodeId);
157
+
158
+ const node = nodePool[nodeId];
159
+ if (node && node.elts) {
160
+ node.elts.forEach(elt => {
161
+ if (typeof elt === "number") collectReferences(elt);
162
+ });
163
+ }
164
+ };
165
+ collectReferences(rootId);
166
+ return {
167
+ ...Object.fromEntries(
168
+ Array.from(referencedNodes).map(id => [id, nodeToJSON(nodePool[id])])
169
+ ),
170
+ root: rootId
171
+ };
172
+ }
173
+
174
+ static hasSyntaxError(ctx) {
175
+ return ctx.state.errors && ctx.state.errors.length > 0;
158
176
  }
159
177
 
160
178
  static dump(n) {
@@ -666,11 +684,7 @@ export class Ast {
666
684
  });
667
685
  nids.push(word.nid || 0);
668
686
  }
669
- const pattern = env.pattern;
670
- console.log(
671
- "lambda()",
672
- "pattern=" + JSON.stringify(pattern, null, 2),
673
- );
687
+ // const pattern = env.pattern;
674
688
  Ast.push(ctx, {
675
689
  tag: "LAMBDA",
676
690
  elts: [{
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") {
@@ -225,8 +225,12 @@ export class Folder {
225
225
  assert(false);
226
226
  }
227
227
  } else {
228
- // assertErr(ctx, false, "unresolved ident " + name, node.coord);
229
- Ast.push(ctx, node);
228
+ // Tag value.
229
+ Ast.push(ctx, {
230
+ tag: name,
231
+ elts: [],
232
+ coord: node.coord,
233
+ });
230
234
  }
231
235
  }
232
236
 
@@ -235,7 +239,7 @@ export class Folder {
235
239
  }
236
240
 
237
241
  static str(node) {
238
- Ast.string(Folder.#ctx, node.elts[0]);
242
+ Ast.string(Folder.#ctx, node.elts[0], node.coord);
239
243
  }
240
244
 
241
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
 
@@ -266,7 +283,7 @@ export const parse = (function () {
266
283
  function number(ctx, cc) {
267
284
  eat(ctx, TK_NUM);
268
285
  cc.cls = "number";
269
- Ast.number(ctx, lexeme, getCoord(ctx));
286
+ Ast.number(ctx, lexeme);
270
287
  return cc;
271
288
  }
272
289
 
@@ -281,28 +298,24 @@ export const parse = (function () {
281
298
  */
282
299
 
283
300
  function str(ctx, cc) {
284
- let coord;
285
301
  if (match(ctx, TK_STR)) {
286
302
  eat(ctx, TK_STR);
287
- coord = getCoord(ctx);
288
- Ast.string(ctx, lexeme, coord); // strip quotes;
303
+ Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
289
304
  cc.cls = "string";
290
305
  return cc;
291
306
  } else if (match(ctx, TK_STRPREFIX)) {
292
307
  ctx.state.inStr++;
293
308
  eat(ctx, TK_STRPREFIX);
294
309
  startCounter(ctx);
295
- coord = getCoord(ctx);
296
- Ast.string(ctx, lexeme, coord); // strip quotes;
310
+ Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
297
311
  countCounter(ctx);
298
312
  const ret = function (ctx) {
299
313
  return strSuffix(ctx, function (ctx) {
300
314
  ctx.state.inStr--;
301
315
  eat(ctx, TK_STRSUFFIX);
302
- const coord = getCoord(ctx);
303
- Ast.string(ctx, lexeme, coord); // strip quotes;
316
+ Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
304
317
  countCounter(ctx);
305
- Ast.list(ctx, ctx.state.exprc, getCoord(ctx));
318
+ Ast.list(ctx, ctx.state.exprc);
306
319
  stopCounter(ctx);
307
320
  Ast.concat(ctx);
308
321
  cc.cls = "string";
@@ -324,8 +337,7 @@ export const parse = (function () {
324
337
  if (match(ctx, TK_STRMIDDLE)) {
325
338
  // Not done yet.
326
339
  eat(ctx, TK_STRMIDDLE);
327
- const coord = getCoord(ctx);
328
- Ast.string(ctx, lexeme, coord); // strip quotes;
340
+ Ast.string(ctx, lexeme, getCoord(ctx)); // strip quotes;
329
341
  countCounter(ctx);
330
342
  ret = function (ctx) {
331
343
  return strSuffix(ctx, resume);
@@ -389,7 +401,7 @@ export const parse = (function () {
389
401
  offset: ctx.state.paramc,
390
402
  nid: 0
391
403
  });
392
- Ast.name(ctx, lexeme, getCoord(ctx));
404
+ Ast.name(ctx, lexeme);
393
405
  cc.cls = "val";
394
406
  return cc;
395
407
  }
@@ -414,8 +426,8 @@ export const parse = (function () {
414
426
  }
415
427
  }
416
428
  } else {
417
- cc.cls = "error";
418
- Ast.error(ctx, "Name '" + lexeme + "' not found.", coord);
429
+ // Create a tag value.
430
+ Ast.name(ctx, lexeme, coord);
419
431
  }
420
432
  // assert(cc, "name");
421
433
  return cc;
@@ -505,14 +517,12 @@ export const parse = (function () {
505
517
  return ret;
506
518
  }
507
519
  function list(ctx, cc) {
508
- const coord = getCoord(ctx);
509
520
  eat(ctx, TK_LEFTBRACKET);
510
521
  startCounter(ctx);
511
522
  const ret = function (ctx) {
512
523
  return elements(ctx, function (ctx) {
513
524
  eat(ctx, TK_RIGHTBRACKET);
514
- coord.to = getCoord(ctx).to;
515
- Ast.list(ctx, ctx.state.exprc, coord);
525
+ Ast.list(ctx, ctx.state.exprc);
516
526
  stopCounter(ctx);
517
527
  cc.cls = "punc";
518
528
  return cc;
@@ -858,9 +868,17 @@ export const parse = (function () {
858
868
  return exprsStart(ctx, TK_DOT, function (ctx) {
859
869
  let nid;
860
870
  while (Ast.peek(ctx) !== nid) {
871
+ console.log(
872
+ "program()",
873
+ "peek()=" + Ast.peek(ctx),
874
+ );
861
875
  nid = Ast.pop(ctx);
862
876
  folder.fold(ctx, nid); // Fold the exprs on top
863
877
  }
878
+ console.log(
879
+ "program()",
880
+ "nodeStack.length=" + ctx.state.nodeStack.length,
881
+ );
864
882
  Ast.exprs(ctx, ctx.state.nodeStack.length, true);
865
883
  Ast.program(ctx);
866
884
  assert(cc === null, "internal error, expecting null continuation");
@@ -984,7 +1002,7 @@ export const parse = (function () {
984
1002
  stream.next();
985
1003
  }
986
1004
  if (cc && !stream.peek()) {
987
- assertErr(ctx, false, "End of progam reached.", getCoord(ctx));
1005
+ assertErr(ctx, false, "Missing program terminator.", getCoord(ctx));
988
1006
  }
989
1007
  } catch (x) {
990
1008
  // console.log("catch() x=" + x);
@@ -993,17 +1011,28 @@ export const parse = (function () {
993
1011
  cls = x;
994
1012
  } else {
995
1013
  console.log("catch() x=" + x.stack);
996
- // 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
+
997
1022
  state.cc = null; // done for now.
998
1023
  return Ast.poolToJSON(ctx);
999
- // cls = "error";
1000
- // throw new Error(JSON.stringify(window.gcexports.errors, null, 2));
1001
1024
  }
1002
1025
  } else {
1003
1026
  // throw x
1004
1027
  next(ctx);
1005
1028
  cls = "error";
1006
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
+ }
1007
1036
  }
1008
1037
  }
1009
1038
  const t1 = new Date();
@@ -1048,6 +1077,11 @@ export const parse = (function () {
1048
1077
  case 46: // dot
1049
1078
  if (isNumeric(stream.peek())) {
1050
1079
  return number(c);
1080
+ // } else if (stream.peek() === 46) {
1081
+ // TODO
1082
+ // stream.next();
1083
+ // lexeme += String.fromCharCode(c);
1084
+ // return TK_DOTDOT;
1051
1085
  }
1052
1086
  lexeme += String.fromCharCode(c);
1053
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,
@@ -35,10 +35,6 @@ const main = {
35
35
  if (state.cc) {
36
36
  throw new Error("End of program reached.");
37
37
  }
38
- console.log(
39
- "parse()",
40
- "ast=" + JSON.stringify(ast, null, 2),
41
- );
42
38
  return ast;
43
39
  }
44
40
  };
@@ -1,6 +1,7 @@
1
1
  import { jest } from "@jest/globals";
2
- import { buildParser } from "./parser.js";
2
+ import { buildParser, parser } from "./parser.js";
3
3
  import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
4
+ import vm from "vm";
4
5
 
5
6
  describe("lang/parser", () => {
6
7
  const log = jest.fn();
@@ -149,3 +150,373 @@ describe("lang/parser", () => {
149
150
  await expect(parser.parse(lang, src)).rejects.toBe(err);
150
151
  });
151
152
  });
153
+
154
+ describe("parser integration tests", () => {
155
+ // Tests using the actual parser
156
+ it("should parse string literals", async () => {
157
+ // Arrange & Act
158
+ const result = await parser.parse(0, "'hello, world'..");
159
+
160
+ // Assert
161
+ expect(result).toHaveProperty("root");
162
+
163
+ // Find the STR node
164
+ let strNode = null;
165
+ for (const key in result) {
166
+ if (key !== "root") {
167
+ const node = result[key];
168
+ if (node.tag === "STR" && node.elts[0] === "hello, world") {
169
+ strNode = node;
170
+ break;
171
+ }
172
+ }
173
+ }
174
+
175
+ expect(strNode).not.toBeNull();
176
+ expect(strNode.tag).toBe("STR");
177
+ expect(strNode.elts).toEqual(["hello, world"]);
178
+
179
+ // Program structure verification
180
+ const rootId = result.root;
181
+ const rootNode = result[rootId];
182
+ expect(rootNode.tag).toBe("PROG");
183
+ });
184
+
185
+ it("should parse numeric literals", async () => {
186
+ // Arrange & Act
187
+ const result = await parser.parse(0, "42..");
188
+
189
+ // Assert
190
+ expect(result).toHaveProperty("root");
191
+
192
+ // Find the NUM node
193
+ let numNode = null;
194
+ for (const key in result) {
195
+ if (key !== "root") {
196
+ const node = result[key];
197
+ if (node.tag === "NUM" && node.elts[0] === "42") {
198
+ numNode = node;
199
+ break;
200
+ }
201
+ }
202
+ }
203
+
204
+ expect(numNode).not.toBeNull();
205
+ expect(numNode.tag).toBe("NUM");
206
+ expect(numNode.elts).toEqual(["42"]);
207
+ });
208
+
209
+ it("should have a PROG node at the root", async () => {
210
+ // Let's test the most basic structure that should always work
211
+ const result = await parser.parse(0, "123..");
212
+
213
+ // Assert
214
+ expect(result).toHaveProperty("root");
215
+
216
+ // Verify the structure: we need to have a PROG node at the root
217
+ const rootId = result.root;
218
+ const rootNode = result[rootId];
219
+ expect(rootNode.tag).toBe("PROG");
220
+
221
+ // Find the NUM node for 123
222
+ let numNode = null;
223
+ for (const key in result) {
224
+ if (key !== "root") {
225
+ const node = result[key];
226
+ if (node.tag === "NUM" && node.elts[0] === "123") {
227
+ numNode = node;
228
+ break;
229
+ }
230
+ }
231
+ }
232
+
233
+ expect(numNode).not.toBeNull();
234
+ expect(numNode.tag).toBe("NUM");
235
+ expect(numNode.elts[0]).toBe("123");
236
+ });
237
+
238
+ it("should parse complex program: apply (<a b: add a b>) [10 20]..", async () => {
239
+ // Create parser with custom lexicon
240
+ const customLexiconCache = new Map();
241
+ customLexiconCache.set(0, {
242
+ add: {
243
+ tk: 2,
244
+ name: "add",
245
+ cls: "function",
246
+ length: 2
247
+ },
248
+ apply: {
249
+ tk: 40,
250
+ name: "apply",
251
+ cls: "function",
252
+ length: 2
253
+ }
254
+ });
255
+
256
+ // Use the parser with our custom cache
257
+ const customParser = buildParser({
258
+ log: console.log,
259
+ cache: customLexiconCache,
260
+ getLangAsset: async () => ({}),
261
+ main: {
262
+ parse: (src, lexicon) => {
263
+ return Promise.resolve(parser.parse(0, src));
264
+ }
265
+ },
266
+ vm
267
+ });
268
+
269
+ // Act
270
+ const result = await customParser.parse(0, "apply (<a b: add a b>) [10 20]..");
271
+
272
+ // Assert
273
+ expect(result).toHaveProperty("root");
274
+
275
+ // Verify basic structure
276
+ const rootId = result.root;
277
+ const rootNode = result[rootId];
278
+ expect(rootNode.tag).toBe("PROG");
279
+
280
+ // Find NUM nodes with values 10 and 20
281
+ let num10Node = null;
282
+ let num20Node = null;
283
+
284
+ for (const key in result) {
285
+ if (key !== "root") {
286
+ const node = result[key];
287
+ if (node.tag === "NUM") {
288
+ if (node.elts[0] === "10") {
289
+ num10Node = node;
290
+ } else if (node.elts[0] === "20") {
291
+ num20Node = node;
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ // At minimum, we should be able to find the number values
298
+ expect(num10Node).not.toBeNull();
299
+ expect(num20Node).not.toBeNull();
300
+
301
+ // Find IDENT nodes with names 'a' and 'b'
302
+ let identNodeA = null;
303
+ let identNodeB = null;
304
+
305
+ for (const key in result) {
306
+ if (key !== "root") {
307
+ const node = result[key];
308
+ if (node.tag === "IDENT") {
309
+ if (node.elts[0] === "a") {
310
+ identNodeA = node;
311
+ } else if (node.elts[0] === "b") {
312
+ identNodeB = node;
313
+ }
314
+ }
315
+ }
316
+ }
317
+
318
+ // Check that we found the identifiers
319
+ expect(identNodeA).not.toBeNull();
320
+ expect(identNodeB).not.toBeNull();
321
+ });
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
+
443
+ it("should perform parse-time evaluation for adding two numbers", async () => {
444
+ // Create parser with custom lexicon that defines 'add' function
445
+ const customLexiconCache = new Map();
446
+ customLexiconCache.set(0, {
447
+ add: {
448
+ tk: 2,
449
+ name: "add",
450
+ cls: "function",
451
+ length: 2
452
+ }
453
+ });
454
+
455
+ // Use the parser with our custom cache
456
+ const customParser = buildParser({
457
+ log: console.log,
458
+ cache: customLexiconCache,
459
+ getLangAsset: async () => ({}),
460
+ main: {
461
+ parse: (src, lexicon) => {
462
+ return Promise.resolve(parser.parse(0, src));
463
+ }
464
+ },
465
+ vm
466
+ });
467
+
468
+ // Act - parse a simple addition expression
469
+ const result = await customParser.parse(0, "add 123 456..");
470
+ console.log(
471
+ "TEST",
472
+ "result=" + JSON.stringify(result, null, 2),
473
+ );
474
+
475
+ // Assert
476
+ expect(result).toHaveProperty("root");
477
+
478
+ // Verify basic structure
479
+ const rootId = result.root;
480
+ const rootNode = result[rootId];
481
+ expect(rootNode.tag).toBe("PROG");
482
+
483
+ // Find the result node - expecting a single NUM node with the sum
484
+ let resultNode = null;
485
+
486
+ // Check all nodes for the result of evaluation (123 + 456 = 579)
487
+ for (const key in result) {
488
+ if (key !== "root") {
489
+ const node = result[key];
490
+ if (node.tag === "NUM" && node.elts[0] === "579") {
491
+ resultNode = node;
492
+ break;
493
+ }
494
+ }
495
+ }
496
+
497
+ // We should find a node with the computed value (579)
498
+ expect(resultNode).not.toBeNull();
499
+ expect(resultNode.tag).toBe("NUM");
500
+ expect(resultNode.elts[0]).toBe("579");
501
+
502
+ // The original numbers should not be in the final AST
503
+ // if parse-time evaluation is working correctly
504
+ let found123 = false;
505
+ let found456 = false;
506
+
507
+ for (const key in result) {
508
+ if (key !== "root") {
509
+ const node = result[key];
510
+ if (node.tag === "NUM") {
511
+ if (node.elts[0] === "123") found123 = true;
512
+ if (node.elts[0] === "456") found456 = true;
513
+ }
514
+ }
515
+ }
516
+
517
+ // The original operands should not be in the final AST
518
+ // if they were properly evaluated at parse time
519
+ expect(found123).toBe(false);
520
+ expect(found456).toBe(false);
521
+ });
522
+ });
package/src/parse.spec.js DELETED
@@ -1,118 +0,0 @@
1
- import { buildParser } from "./parser.js";
2
- import { mockPromiseValue } from "./testing/index.js";
3
-
4
- describe("parse operations", () => {
5
- const log = jest.fn();
6
-
7
- it("should parse 'apply (<a b: add a b>) [10 20]'", async () => {
8
- // Arrange
9
- const cache = new Map();
10
- const getLangAsset = mockPromiseValue("{}");
11
- const main = {
12
- parse: mockPromiseValue({
13
- 1: {
14
- elts: ["a"],
15
- tag: "IDENT"
16
- },
17
- 2: {
18
- elts: ["b"],
19
- tag: "IDENT"
20
- },
21
- 3: {
22
- elts: [1, 2],
23
- tag: "ADD"
24
- },
25
- 4: {
26
- elts: [3],
27
- tag: "EXPRS"
28
- },
29
- 5: {
30
- elts: [1, 2],
31
- tag: "LIST"
32
- },
33
- 6: {
34
- elts: [5, 4],
35
- tag: "LAMBDA"
36
- },
37
- 7: {
38
- elts: ["10"],
39
- tag: "NUM"
40
- },
41
- 8: {
42
- elts: ["20"],
43
- tag: "NUM"
44
- },
45
- 9: {
46
- elts: [7, 8],
47
- tag: "LIST"
48
- },
49
- 10: {
50
- elts: [6, 9],
51
- tag: "APPLY"
52
- },
53
- 11: {
54
- elts: [10],
55
- tag: "EXPRS"
56
- },
57
- 12: {
58
- elts: [11],
59
- tag: "PROG"
60
- },
61
- root: 12
62
- })
63
- };
64
- const parser = buildParser({ log, cache, getLangAsset, main });
65
- const lang = "0002";
66
- const src = "apply (<a b: add a b>) [10 20]..";
67
-
68
- // Act
69
- const result = await parser.parse(lang, src);
70
-
71
- // Assert
72
- expect(result).toHaveProperty("root", 12);
73
- expect(result[10].tag).toBe("APPLY");
74
- expect(result[10].elts).toEqual([6, 9]);
75
- expect(result[6].tag).toBe("LAMBDA");
76
- expect(result[9].tag).toBe("LIST");
77
- });
78
-
79
- it("should parse 'hello, world!'", async () => {
80
- // Arrange
81
- const cache = new Map();
82
- const getLangAsset = mockPromiseValue("{}");
83
- const main = {
84
- parse: mockPromiseValue({
85
- 1: {
86
- elts: [
87
- "hello, world"
88
- ],
89
- tag: "STR"
90
- },
91
- 2: {
92
- elts: [
93
- 1
94
- ],
95
- tag: "EXPRS"
96
- },
97
- 3: {
98
- elts: [
99
- 2
100
- ],
101
- tag: "PROG"
102
- },
103
- root: 3
104
- })
105
- };
106
- const parser = buildParser({ log, cache, getLangAsset, main });
107
- const lang = "0002";
108
- const src = "'hello, world'..";
109
-
110
- // Act
111
- const result = await parser.parse(lang, src);
112
-
113
- // Assert
114
- expect(result).toHaveProperty("root", 3);
115
- expect(result[1].tag).toBe("STR");
116
- expect(result[1].elts).toEqual(["hello, world"]);
117
- });
118
- });