@graffiticode/parser 0.1.1 → 0.1.3

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.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -8,18 +8,17 @@
8
8
  "main": "./src/index.js",
9
9
  "devDependencies": {
10
10
  "eslint": "^8.38.0",
11
- "jest": "^29.5.0",
11
+ "jest": "^29.7.0",
12
12
  "supertest": "^6.3.3"
13
13
  },
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": "",
21
- "license": "ISC",
22
- "description": "",
23
- "dependencies": {
24
- }
22
+ "license": "MIT",
23
+ "description": ""
25
24
  }
package/src/ast.js CHANGED
@@ -20,23 +20,23 @@ function nodeToJSON(n) {
20
20
  let obj;
21
21
  if (typeof n === "object") {
22
22
  switch (n.tag) {
23
- case "num":
24
- obj = n.elts[0];
25
- break;
26
- case "str":
27
- obj = n.elts[0];
28
- break;
29
- default:
30
- obj = {};
31
- obj.tag = n.tag;
32
- obj.elts = [];
33
- if (n.coord) {
34
- obj.coord = n.coord;
35
- }
36
- for (let i = 0; i < n.elts.length; i++) {
37
- obj.elts[i] = nodeToJSON(n.elts[i]);
38
- }
39
- break;
23
+ case "num":
24
+ obj = n.elts[0];
25
+ break;
26
+ case "str":
27
+ obj = n.elts[0];
28
+ break;
29
+ default:
30
+ obj = {};
31
+ obj.tag = n.tag;
32
+ obj.elts = [];
33
+ if (n.coord) {
34
+ obj.coord = n.coord;
35
+ }
36
+ for (let i = 0; i < n.elts.length; i++) {
37
+ obj.elts[i] = nodeToJSON(n.elts[i]);
38
+ }
39
+ break;
40
40
  }
41
41
  } else if (typeof n === "string") {
42
42
  obj = n;
@@ -148,13 +148,27 @@ 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
+ };
158
172
  }
159
173
 
160
174
  static dump(n) {
@@ -666,7 +680,7 @@ export class Ast {
666
680
  });
667
681
  nids.push(word.nid || 0);
668
682
  }
669
- const pattern = env.pattern;
683
+ // const pattern = env.pattern;
670
684
  Ast.push(ctx, {
671
685
  tag: "LAMBDA",
672
686
  elts: [{
@@ -674,7 +688,9 @@ export class Ast {
674
688
  elts: names
675
689
  }, nid, {
676
690
  tag: "LIST",
677
- elts: pattern
691
+ elts: [
692
+ // pattern // FIXME
693
+ ],
678
694
  }, {
679
695
  tag: "LIST",
680
696
  elts: nids
package/src/env.js CHANGED
@@ -1,5 +1,9 @@
1
1
  // env
2
2
 
3
+ function topEnv(ctx) {
4
+ return ctx.state.env[ctx.state.env.length - 1];
5
+ }
6
+
3
7
  export class Env {
4
8
  static findWord(ctx, lexeme) {
5
9
  const env = ctx.state.env;
@@ -13,12 +17,12 @@ export class Env {
13
17
  }
14
18
 
15
19
  static addWord(ctx, lexeme, entry) {
16
- window.gcexports.topEnv(ctx).lexicon[lexeme] = entry;
20
+ topEnv(ctx).lexicon[lexeme] = entry;
17
21
  return null;
18
22
  }
19
23
 
20
24
  static addPattern(ctx, pattern) {
21
- window.gcexports.topEnv(ctx).pattern.push(pattern);
25
+ topEnv(ctx).pattern.push(pattern);
22
26
  }
23
27
 
24
28
  static enterEnv(ctx, name) {
@@ -31,7 +35,7 @@ export class Env {
31
35
  // return; // just stop recursing
32
36
  throw new Error("runaway recursion");
33
37
  }
34
- window.gcexports.topEnv(ctx).paramc = ctx.state.paramc;
38
+ topEnv(ctx).paramc = ctx.state.paramc;
35
39
  ctx.state.env.push({
36
40
  name,
37
41
  lexicon: {},
@@ -41,6 +45,6 @@ export class Env {
41
45
 
42
46
  static exitEnv(ctx) {
43
47
  ctx.state.env.pop();
44
- ctx.state.paramc = window.gcexports.topEnv(ctx).paramc;
48
+ ctx.state.paramc = topEnv(ctx).paramc;
45
49
  }
46
50
  }
package/src/folder.js CHANGED
@@ -203,11 +203,16 @@ export class Folder {
203
203
  const elts = [];
204
204
  for (let i = 0; i < word.length; i++) {
205
205
  const elt = Ast.pop(ctx);
206
- assertErr(ctx, elt, `Too few arguments for ${word.name}. Expected ${word.length}.`, node.coord);
206
+ assertErr(
207
+ ctx,
208
+ elt,
209
+ `Too few arguments for ${word.name}. Expected ${word.length}.`,
210
+ node.coord
211
+ );
207
212
  elts.push(elt);
208
213
  }
209
214
  if (word.nid) {
210
- Ast.fold(ctx, word, elts);
215
+ Ast.foldApply(ctx, word, elts);
211
216
  } else {
212
217
  Ast.push(ctx, {
213
218
  tag: word.name,
@@ -220,8 +225,11 @@ export class Folder {
220
225
  assert(false);
221
226
  }
222
227
  } else {
223
- // assert(false, "unresolved ident "+name);
224
- Ast.push(ctx, node);
228
+ // Tag value.
229
+ Ast.push(ctx, {
230
+ tag: name,
231
+ elts: [],
232
+ });
225
233
  }
226
234
  }
227
235
 
@@ -0,0 +1,42 @@
1
+ import { Folder } from "./folder.js";
2
+ import { Ast } from "./ast.js";
3
+
4
+ describe("folder", () => {
5
+ it("should fold 'add 2 3' to 5", () => {
6
+ // Arrange
7
+ const ctx = {
8
+ state: {
9
+ nodePool: ["unused"],
10
+ nodeStack: [],
11
+ nodeStackStack: [],
12
+ nodeMap: {},
13
+ env: [{ name: "global", lexicon: {} }]
14
+ }
15
+ };
16
+
17
+ // Create nodes for "add 2 3"
18
+ const n1Id = Ast.intern(ctx, {
19
+ tag: "NUM",
20
+ elts: ["2"]
21
+ });
22
+
23
+ const n2Id = Ast.intern(ctx, {
24
+ tag: "NUM",
25
+ elts: ["3"]
26
+ });
27
+
28
+ const addNodeId = Ast.intern(ctx, {
29
+ tag: "ADD",
30
+ elts: [n1Id, n2Id]
31
+ });
32
+
33
+ // Act
34
+ Folder.fold(ctx, addNodeId);
35
+ const resultId = ctx.state.nodeStack.pop();
36
+ const resultNode = ctx.state.nodePool[resultId];
37
+
38
+ // Assert
39
+ expect(resultNode.tag).toBe("NUM");
40
+ expect(resultNode.elts[0]).toBe("5");
41
+ });
42
+ });
package/src/parse.js CHANGED
@@ -239,7 +239,7 @@ export const parse = (function () {
239
239
  scanTime += (t1 - t0);
240
240
  ctx.state.nextToken = tk;
241
241
  const to = getPos(ctx);
242
- ctx.state.nextTokenCoord = {from, to};
242
+ ctx.state.nextTokenCoord = { from, to };
243
243
  } else {
244
244
  tk = nextToken;
245
245
  }
@@ -266,7 +266,7 @@ export const parse = (function () {
266
266
  function number(ctx, cc) {
267
267
  eat(ctx, TK_NUM);
268
268
  cc.cls = "number";
269
- Ast.number(ctx, lexeme, getCoord(ctx));
269
+ Ast.number(ctx, lexeme);
270
270
  return cc;
271
271
  }
272
272
 
@@ -281,28 +281,24 @@ export const parse = (function () {
281
281
  */
282
282
 
283
283
  function str(ctx, cc) {
284
- let coord;
285
284
  if (match(ctx, TK_STR)) {
286
285
  eat(ctx, TK_STR);
287
- coord = getCoord(ctx);
288
- Ast.string(ctx, lexeme, coord); // strip quotes;
286
+ Ast.string(ctx, lexeme); // strip quotes;
289
287
  cc.cls = "string";
290
288
  return cc;
291
289
  } else if (match(ctx, TK_STRPREFIX)) {
292
290
  ctx.state.inStr++;
293
291
  eat(ctx, TK_STRPREFIX);
294
292
  startCounter(ctx);
295
- coord = getCoord(ctx);
296
- Ast.string(ctx, lexeme, coord); // strip quotes;
293
+ Ast.string(ctx, lexeme); // strip quotes;
297
294
  countCounter(ctx);
298
295
  const ret = function (ctx) {
299
296
  return strSuffix(ctx, function (ctx) {
300
297
  ctx.state.inStr--;
301
298
  eat(ctx, TK_STRSUFFIX);
302
- const coord = getCoord(ctx);
303
- Ast.string(ctx, lexeme, coord); // strip quotes;
299
+ Ast.string(ctx, lexeme); // strip quotes;
304
300
  countCounter(ctx);
305
- Ast.list(ctx, ctx.state.exprc, getCoord(ctx));
301
+ Ast.list(ctx, ctx.state.exprc);
306
302
  stopCounter(ctx);
307
303
  Ast.concat(ctx);
308
304
  cc.cls = "string";
@@ -324,8 +320,7 @@ export const parse = (function () {
324
320
  if (match(ctx, TK_STRMIDDLE)) {
325
321
  // Not done yet.
326
322
  eat(ctx, TK_STRMIDDLE);
327
- const coord = getCoord(ctx);
328
- Ast.string(ctx, lexeme, coord); // strip quotes;
323
+ Ast.string(ctx, lexeme); // strip quotes;
329
324
  countCounter(ctx);
330
325
  ret = function (ctx) {
331
326
  return strSuffix(ctx, resume);
@@ -355,7 +350,7 @@ export const parse = (function () {
355
350
  function bindingName(ctx, cc) {
356
351
  if (match(ctx, TK_IDENT)) {
357
352
  eat(ctx, TK_IDENT);
358
- Ast.string(ctx, lexeme, getCoord(ctx));
353
+ Ast.string(ctx, lexeme);
359
354
  cc.cls = "variable";
360
355
  return cc;
361
356
  }
@@ -389,7 +384,7 @@ export const parse = (function () {
389
384
  offset: ctx.state.paramc,
390
385
  nid: 0
391
386
  });
392
- Ast.name(ctx, lexeme, getCoord(ctx));
387
+ Ast.name(ctx, lexeme);
393
388
  cc.cls = "val";
394
389
  return cc;
395
390
  }
@@ -414,8 +409,10 @@ export const parse = (function () {
414
409
  }
415
410
  }
416
411
  } else {
417
- cc.cls = "error";
418
- Ast.error(ctx, "Name '" + lexeme + "' not found.", coord);
412
+ // cc.cls = "error";
413
+ // Ast.error(ctx, "Name '" + lexeme + "' not found.", coord);
414
+ // Create a tag value.
415
+ Ast.name(ctx, lexeme, coord);
419
416
  }
420
417
  // assert(cc, "name");
421
418
  return cc;
@@ -505,14 +502,12 @@ export const parse = (function () {
505
502
  return ret;
506
503
  }
507
504
  function list(ctx, cc) {
508
- const coord = getCoord(ctx);
509
505
  eat(ctx, TK_LEFTBRACKET);
510
506
  startCounter(ctx);
511
507
  const ret = function (ctx) {
512
508
  return elements(ctx, function (ctx) {
513
509
  eat(ctx, TK_RIGHTBRACKET);
514
- coord.to = getCoord(ctx).to;
515
- Ast.list(ctx, ctx.state.exprc, coord);
510
+ Ast.list(ctx, ctx.state.exprc);
516
511
  stopCounter(ctx);
517
512
  cc.cls = "punc";
518
513
  return cc;
@@ -984,7 +979,7 @@ export const parse = (function () {
984
979
  stream.next();
985
980
  }
986
981
  if (cc && !stream.peek()) {
987
- assertErr(ctx, false, "End of progam reached.", getCoord(ctx));
982
+ assertErr(ctx, false, "Missing program terminator.", getCoord(ctx));
988
983
  }
989
984
  } catch (x) {
990
985
  // console.log("catch() x=" + x);
@@ -1166,7 +1161,7 @@ export const parse = (function () {
1166
1161
  c = nextCC();
1167
1162
  }
1168
1163
  }
1169
- const coord = {from: getPos(ctx) - lexeme.length, to: getPos(ctx)};
1164
+ const coord = { from: getPos(ctx) - lexeme.length, to: getPos(ctx) };
1170
1165
  assertErr(ctx, c !== 0, `Unterminated string: ${lexeme}`, coord);
1171
1166
  if (quoteChar === CC_BACKTICK && c === CC_DOLLAR &&
1172
1167
  peekCC() === CC_LEFTBRACE) {
@@ -0,0 +1,119 @@
1
+ import { jest } from "@jest/globals";
2
+ import { parser, buildParser } from "./parser.js";
3
+ import { mockPromiseValue, mockPromiseError } from "./testing/index.js";
4
+
5
+ describe("parse operations", () => {
6
+ const log = jest.fn();
7
+
8
+ it("should parse 'apply (<a b: add a b>) [10 20]'", async () => {
9
+ // Arrange
10
+ const cache = new Map();
11
+ const getLangAsset = mockPromiseValue("{}");
12
+ const main = {
13
+ parse: mockPromiseValue({
14
+ 1: {
15
+ elts: ["a"],
16
+ tag: "IDENT"
17
+ },
18
+ 2: {
19
+ elts: ["b"],
20
+ tag: "IDENT"
21
+ },
22
+ 3: {
23
+ elts: [1, 2],
24
+ tag: "ADD"
25
+ },
26
+ 4: {
27
+ elts: [3],
28
+ tag: "EXPRS"
29
+ },
30
+ 5: {
31
+ elts: [1, 2],
32
+ tag: "LIST"
33
+ },
34
+ 6: {
35
+ elts: [5, 4],
36
+ tag: "LAMBDA"
37
+ },
38
+ 7: {
39
+ elts: ["10"],
40
+ tag: "NUM"
41
+ },
42
+ 8: {
43
+ elts: ["20"],
44
+ tag: "NUM"
45
+ },
46
+ 9: {
47
+ elts: [7, 8],
48
+ tag: "LIST"
49
+ },
50
+ 10: {
51
+ elts: [6, 9],
52
+ tag: "APPLY"
53
+ },
54
+ 11: {
55
+ elts: [10],
56
+ tag: "EXPRS"
57
+ },
58
+ 12: {
59
+ elts: [11],
60
+ tag: "PROG"
61
+ },
62
+ root: 12
63
+ })
64
+ };
65
+ const parser = buildParser({ log, cache, getLangAsset, main });
66
+ const lang = "0";
67
+ const src = "apply (<a b: add a b>) [10 20]..";
68
+
69
+ // Act
70
+ const result = await parser.parse(lang, src);
71
+
72
+ // Assert
73
+ expect(result).toHaveProperty("root", 12);
74
+ expect(result[10].tag).toBe("APPLY");
75
+ expect(result[10].elts).toEqual([6, 9]);
76
+ expect(result[6].tag).toBe("LAMBDA");
77
+ expect(result[9].tag).toBe("LIST");
78
+ });
79
+
80
+ it("should parse 'hello, world!'", async () => {
81
+ // Arrange
82
+ const cache = new Map();
83
+ const getLangAsset = mockPromiseValue("{}");
84
+ const main = {
85
+ parse: mockPromiseValue({
86
+ 1: {
87
+ elts: [
88
+ "hello, world"
89
+ ],
90
+ tag: "STR"
91
+ },
92
+ 2: {
93
+ elts: [
94
+ 1
95
+ ],
96
+ tag: "EXPRS"
97
+ },
98
+ 3: {
99
+ elts: [
100
+ 2
101
+ ],
102
+ tag: "PROG"
103
+ },
104
+ root: 3
105
+ })
106
+ };
107
+ const parser = buildParser({ log, cache, getLangAsset, main });
108
+ const lang = "0";
109
+ const src = "'hello, world'..";
110
+
111
+ // Act
112
+ const result = await parser.parse(lang, src);
113
+
114
+ // Assert
115
+ expect(result).toHaveProperty("root", 3);
116
+ expect(result[1].tag).toBe("STR");
117
+ expect(result[1].elts).toEqual(["hello, world"]);
118
+ });
119
+ });
package/src/parser.js CHANGED
@@ -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 { parser, 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();
@@ -135,41 +136,267 @@ describe("lang/parser", () => {
135
136
  expect(vm.createContext).toHaveBeenCalled();
136
137
  expect(vm.runInContext).toHaveBeenCalledWith(rawLexicon, expect.anything());
137
138
  });
138
- it.skip("should parse 'hello, world!'", async () => {
139
+ it("should parse error", async () => {
140
+ // Arrange
141
+ const cache = new Map();
142
+ const getLangAsset = mockPromiseValue("{}");
143
+ const err = new Error("End of program reached.");
144
+ const main = { parse: mockPromiseError(err) };
145
+ const parser = buildParser({ log, cache, getLangAsset, main });
139
146
  const lang = "0";
140
- const src = "'hello, world'..";
141
- const ast = {
142
- 1: {
143
- elts: [
144
- "hello, world"
145
- ],
146
- tag: "STR"
147
- },
148
- 2: {
149
- elts: [
150
- 1
151
- ],
152
- tag: "EXPRS"
147
+ const src = "'hello, world'";
148
+
149
+ // Act & Assert
150
+ await expect(parser.parse(lang, src)).rejects.toBe(err);
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
153
247
  },
154
- 3: {
155
- elts: [
156
- 2
157
- ],
158
- tag: "PROG"
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
+ }
159
265
  },
160
- root: 3
161
- };
162
- await expect(parser.parse(lang, src)).resolves.toStrictEqual(ast);
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();
163
321
  });
164
- it.skip("should parse error", async () => {
165
- const lang = "0";
166
- const src = "'hello, world'";
167
- await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
322
+
323
+ it("should perform parse-time evaluation for adding two numbers", async () => {
324
+ // Create parser with custom lexicon that defines 'add' function
325
+ const customLexiconCache = new Map();
326
+ customLexiconCache.set(0, {
327
+ add: {
328
+ tk: 2,
329
+ name: "add",
330
+ cls: "function",
331
+ length: 2
332
+ }
333
+ });
334
+
335
+ // Use the parser with our custom cache
336
+ const customParser = buildParser({
337
+ log: console.log,
338
+ cache: customLexiconCache,
339
+ getLangAsset: async () => ({}),
340
+ main: {
341
+ parse: (src, lexicon) => {
342
+ return Promise.resolve(parser.parse(0, src));
343
+ }
344
+ },
345
+ vm
346
+ });
347
+
348
+ // Act - parse a simple addition expression
349
+ const result = await customParser.parse(0, "add 123 456..");
350
+ console.log(
351
+ "TEST",
352
+ "result=" + JSON.stringify(result, null, 2),
353
+ );
354
+
355
+ // Assert
356
+ expect(result).toHaveProperty("root");
357
+
358
+ // Verify basic structure
359
+ const rootId = result.root;
360
+ const rootNode = result[rootId];
361
+ expect(rootNode.tag).toBe("PROG");
362
+
363
+ // Find the result node - expecting a single NUM node with the sum
364
+ let resultNode = null;
365
+
366
+ // Check all nodes for the result of evaluation (123 + 456 = 579)
367
+ for (const key in result) {
368
+ if (key !== "root") {
369
+ const node = result[key];
370
+ if (node.tag === "NUM" && node.elts[0] === "579") {
371
+ resultNode = node;
372
+ break;
373
+ }
374
+ }
375
+ }
376
+
377
+ // We should find a node with the computed value (579)
378
+ expect(resultNode).not.toBeNull();
379
+ expect(resultNode.tag).toBe("NUM");
380
+ expect(resultNode.elts[0]).toBe("579");
381
+
382
+ // The original numbers should not be in the final AST
383
+ // if parse-time evaluation is working correctly
384
+ let found123 = false;
385
+ let found456 = false;
386
+
387
+ for (const key in result) {
388
+ if (key !== "root") {
389
+ const node = result[key];
390
+ if (node.tag === "NUM") {
391
+ if (node.elts[0] === "123") found123 = true;
392
+ if (node.elts[0] === "456") found456 = true;
393
+ }
394
+ }
395
+ }
396
+
397
+ // The original operands should not be in the final AST
398
+ // if they were properly evaluated at parse time
399
+ expect(found123).toBe(false);
400
+ expect(found456).toBe(false);
168
401
  });
169
- // it("should parse error", async () => {
170
- // const lang = "0";
171
- // const src = "'hello, world..";
172
- // const ast = {};
173
- // await expect(parser.parse(lang, src)).rejects.toThrow("End of program reached");
174
- // });
175
402
  });