@graffiticode/parser 0.1.2 → 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.2",
3
+ "version": "0.1.3",
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,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,11 +680,7 @@ export class Ast {
666
680
  });
667
681
  nids.push(word.nid || 0);
668
682
  }
669
- const pattern = env.pattern;
670
- console.log(
671
- "lambda()",
672
- "pattern=" + JSON.stringify(pattern, null, 2),
673
- );
683
+ // const pattern = env.pattern;
674
684
  Ast.push(ctx, {
675
685
  tag: "LAMBDA",
676
686
  elts: [{
package/src/folder.js CHANGED
@@ -225,8 +225,11 @@ 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
+ });
230
233
  }
231
234
  }
232
235
 
package/src/parse.js CHANGED
@@ -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);
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 { 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,253 @@ 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 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);
401
+ });
402
+ });
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
- });