@graffiticode/parser 1.3.1 → 1.4.1
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/.claude/settings.local.json +3 -1
- package/package.json +1 -1
- package/src/ast.js +21 -67
- package/src/folder.js +21 -3
- package/src/folder.spec.js +40 -4
- package/src/parser.spec.js +12 -26
- package/src/unparse.js +4 -4
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"Bash(npm test:*)",
|
|
7
7
|
"Bash(npm publish:*)",
|
|
8
8
|
"Bash(grep:*)",
|
|
9
|
-
"Bash(npm install:*)"
|
|
9
|
+
"Bash(npm install:*)",
|
|
10
|
+
"Bash(NODE_OPTIONS=--experimental-vm-modules node -e \"\nimport { parser } from './src/parser.js';\nimport { unparse } from './src/unparse.js';\nimport { lexicon as basisLexicon } from '@graffiticode/basis';\n\nconst src = 'case x of 1: \\\\\"one\\\\\" 2: \\\\\"two\\\\\" end..';\nconsole.log\\('Input:', src\\);\nconst ast = await parser.parse\\(0, src, basisLexicon\\);\nconsole.log\\('AST:', JSON.stringify\\(ast, null, 2\\)\\);\nconst result = unparse\\(ast, basisLexicon\\);\nconsole.log\\('Unparsed:', result\\);\n\")",
|
|
11
|
+
"Bash(NODE_OPTIONS=--experimental-vm-modules node -e \"\nimport { parser } from './src/parser.js';\nimport { unparse } from './src/unparse.js';\nimport { lexicon as basisLexicon } from '@graffiticode/basis';\n\n// Test nested case-of\nconst src1 = 'case x of 1: case y of 3: \\\\\"a\\\\\" 4: \\\\\"b\\\\\" end 2: \\\\\"two\\\\\" end..';\nconsole.log\\('Input:', src1\\);\ntry {\n const ast1 = await parser.parse\\(0, src1, basisLexicon\\);\n console.log\\('Unparsed:', unparse\\(ast1, basisLexicon\\)\\);\n} catch\\(e\\) { console.log\\('Error:', e.message\\); }\n\nconsole.log\\('---'\\);\n\n// Test with expression as case target\nconst src2 = 'case add 1 2 of 3: \\\\\"yes\\\\\" 4: \\\\\"no\\\\\" end..';\nconsole.log\\('Input:', src2\\);\ntry {\n const ast2 = await parser.parse\\(0, src2, basisLexicon\\);\n console.log\\('AST:', JSON.stringify\\(ast2, null, 2\\)\\);\n console.log\\('Unparsed:', unparse\\(ast2, basisLexicon\\)\\);\n} catch\\(e\\) { console.log\\('Error:', e.message\\); }\n\nconsole.log\\('---'\\);\n\n// Test with identifier patterns\nconst src3 = 'case x of y: y end..';\nconsole.log\\('Input:', src3\\);\ntry {\n const ast3 = await parser.parse\\(0, src3, basisLexicon\\);\n console.log\\('AST:', JSON.stringify\\(ast3, null, 2\\)\\);\n console.log\\('Unparsed:', unparse\\(ast3, basisLexicon\\)\\);\n} catch\\(e\\) { console.log\\('Error:', e.message\\); }\n\nconsole.log\\('---'\\);\n\n// Test case-of inside a let or other expression\nconst src4 = 'let x = 5 in case x of 5: \\\\\"five\\\\\" end..';\nconsole.log\\('Input:', src4\\);\ntry {\n const ast4 = await parser.parse\\(0, src4, basisLexicon\\);\n console.log\\('Unparsed:', unparse\\(ast4, basisLexicon\\)\\);\n} catch\\(e\\) { console.log\\('Error:', e.message\\); }\n\nconsole.log\\('---'\\);\n\n// Test case-of as argument to a function\nconst src5 = 'add \\(case x of 1: 10 2: 20 end\\) 5..';\nconsole.log\\('Input:', src5\\);\ntry {\n const ast5 = await parser.parse\\(0, src5, basisLexicon\\);\n console.log\\('Unparsed:', unparse\\(ast5, basisLexicon\\)\\);\n} catch\\(e\\) { console.log\\('Error:', e.message\\); }\n\")"
|
|
10
12
|
]
|
|
11
13
|
}
|
|
12
14
|
}
|
package/package.json
CHANGED
package/src/ast.js
CHANGED
|
@@ -496,86 +496,40 @@ export class Ast {
|
|
|
496
496
|
Ast.number(ctx, -1 * v1);
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
static add(ctx
|
|
500
|
-
const n2 = Ast.
|
|
501
|
-
const n1 = Ast.
|
|
502
|
-
|
|
503
|
-
const v1 = n1.elts[0];
|
|
504
|
-
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
505
|
-
Ast.push(ctx, {
|
|
506
|
-
tag: "ADD",
|
|
507
|
-
elts: [n1, n2],
|
|
508
|
-
coord
|
|
509
|
-
});
|
|
510
|
-
} else {
|
|
511
|
-
Ast.number(ctx, +v1 + +v2);
|
|
512
|
-
}
|
|
499
|
+
static add(ctx) {
|
|
500
|
+
const n2 = Ast.pop(ctx);
|
|
501
|
+
const n1 = Ast.pop(ctx);
|
|
502
|
+
Ast.push(ctx, { tag: "ADD", elts: [n1, n2] });
|
|
513
503
|
}
|
|
514
504
|
|
|
515
505
|
static sub(ctx) {
|
|
516
|
-
const n2 = Ast.
|
|
517
|
-
const n1 = Ast.
|
|
518
|
-
|
|
519
|
-
const v1 = n1.elts[0];
|
|
520
|
-
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
521
|
-
Ast.push(ctx, { tag: "SUB", elts: [n1, n2] });
|
|
522
|
-
} else {
|
|
523
|
-
Ast.number(ctx, +v1 - +v2);
|
|
524
|
-
}
|
|
506
|
+
const n2 = Ast.pop(ctx);
|
|
507
|
+
const n1 = Ast.pop(ctx);
|
|
508
|
+
Ast.push(ctx, { tag: "SUB", elts: [n1, n2] });
|
|
525
509
|
}
|
|
526
510
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
// if (n1.tag === undefined) {
|
|
533
|
-
// n1 = n1.elts[0];
|
|
534
|
-
// }
|
|
535
|
-
// if (n2.tag === undefined) {
|
|
536
|
-
// n2 = n2.elts[0];
|
|
537
|
-
// }
|
|
538
|
-
// if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
539
|
-
// Ast.push(ctx, { tag: "MUL", elts: [n2, n1] });
|
|
540
|
-
// } else {
|
|
541
|
-
// Ast.number(ctx, +v1 * +v2);
|
|
542
|
-
// }
|
|
543
|
-
// }
|
|
511
|
+
static mul(ctx) {
|
|
512
|
+
const n2 = Ast.pop(ctx);
|
|
513
|
+
const n1 = Ast.pop(ctx);
|
|
514
|
+
Ast.push(ctx, { tag: "MUL", elts: [n1, n2] });
|
|
515
|
+
}
|
|
544
516
|
|
|
545
517
|
static div(ctx) {
|
|
546
|
-
const n2 = Ast.
|
|
547
|
-
const n1 = Ast.
|
|
548
|
-
|
|
549
|
-
const v1 = n1.elts[0];
|
|
550
|
-
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
551
|
-
Ast.push(ctx, { tag: "DIV", elts: [n1, n2] });
|
|
552
|
-
} else {
|
|
553
|
-
Ast.number(ctx, +v1 / +v2);
|
|
554
|
-
}
|
|
518
|
+
const n2 = Ast.pop(ctx);
|
|
519
|
+
const n1 = Ast.pop(ctx);
|
|
520
|
+
Ast.push(ctx, { tag: "DIV", elts: [n1, n2] });
|
|
555
521
|
}
|
|
556
522
|
|
|
557
523
|
static mod(ctx) {
|
|
558
|
-
const n2 = Ast.
|
|
559
|
-
const n1 = Ast.
|
|
560
|
-
|
|
561
|
-
const v2 = n2.elts[0];
|
|
562
|
-
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
563
|
-
Ast.push(ctx, { tag: "MOD", elts: [n1, n2] });
|
|
564
|
-
} else {
|
|
565
|
-
Ast.number(ctx, +v1 % +v2);
|
|
566
|
-
}
|
|
524
|
+
const n2 = Ast.pop(ctx);
|
|
525
|
+
const n1 = Ast.pop(ctx);
|
|
526
|
+
Ast.push(ctx, { tag: "MOD", elts: [n1, n2] });
|
|
567
527
|
}
|
|
568
528
|
|
|
569
529
|
static pow(ctx) {
|
|
570
|
-
const n2 = Ast.
|
|
571
|
-
const n1 = Ast.
|
|
572
|
-
|
|
573
|
-
const v1 = n1.elts[0];
|
|
574
|
-
if (n1.tag !== "NUM" || n2.tag !== "NUM") {
|
|
575
|
-
Ast.push(ctx, { tag: "POW", elts: [n1, n2] });
|
|
576
|
-
} else {
|
|
577
|
-
Ast.number(ctx, Math.pow(+v1, +v2));
|
|
578
|
-
}
|
|
530
|
+
const n2 = Ast.pop(ctx);
|
|
531
|
+
const n1 = Ast.pop(ctx);
|
|
532
|
+
Ast.push(ctx, { tag: "POW", elts: [n1, n2] });
|
|
579
533
|
}
|
|
580
534
|
|
|
581
535
|
static concat(ctx) {
|
package/src/folder.js
CHANGED
|
@@ -25,9 +25,9 @@ export class Folder {
|
|
|
25
25
|
PARENS: Folder.unaryExpr,
|
|
26
26
|
APPLY: Folder.apply,
|
|
27
27
|
LAMBDA: Folder.lambda,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
MUL: Folder.mul,
|
|
29
|
+
DIV: Folder.div,
|
|
30
|
+
SUB: Folder.sub,
|
|
31
31
|
TAG: Folder.tag,
|
|
32
32
|
ADD: Folder.add,
|
|
33
33
|
POW: Folder.pow,
|
|
@@ -161,6 +161,24 @@ export class Folder {
|
|
|
161
161
|
Ast.add(Folder.#ctx);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
static sub(node) {
|
|
165
|
+
Folder.#visit(node.elts[0]);
|
|
166
|
+
Folder.#visit(node.elts[1]);
|
|
167
|
+
Ast.sub(Folder.#ctx);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static mul(node) {
|
|
171
|
+
Folder.#visit(node.elts[0]);
|
|
172
|
+
Folder.#visit(node.elts[1]);
|
|
173
|
+
Ast.mul(Folder.#ctx);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static div(node) {
|
|
177
|
+
Folder.#visit(node.elts[0]);
|
|
178
|
+
Folder.#visit(node.elts[1]);
|
|
179
|
+
Ast.div(Folder.#ctx);
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
static pow(node) {
|
|
165
183
|
Folder.#visit(node.elts[0]);
|
|
166
184
|
Folder.#visit(node.elts[1]);
|
package/src/folder.spec.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Folder } from "./folder.js";
|
|
|
2
2
|
import { Ast } from "./ast.js";
|
|
3
3
|
|
|
4
4
|
describe("folder", () => {
|
|
5
|
-
it("should
|
|
5
|
+
it("should pass 'add 2 3' through as ADD node", () => {
|
|
6
6
|
// Arrange
|
|
7
7
|
const ctx = {
|
|
8
8
|
state: {
|
|
@@ -35,8 +35,44 @@ describe("folder", () => {
|
|
|
35
35
|
const resultId = ctx.state.nodeStack.pop();
|
|
36
36
|
const resultNode = ctx.state.nodePool[resultId];
|
|
37
37
|
|
|
38
|
-
// Assert
|
|
39
|
-
expect(resultNode.tag).toBe("
|
|
40
|
-
expect(resultNode.elts
|
|
38
|
+
// Assert - arithmetic is deferred to compiler, not folded
|
|
39
|
+
expect(resultNode.tag).toBe("ADD");
|
|
40
|
+
expect(resultNode.elts.length).toBe(2);
|
|
41
|
+
// elts are nids (intern recursively interns object elts)
|
|
42
|
+
const n1 = Ast.node(ctx, resultNode.elts[0]);
|
|
43
|
+
const n2 = Ast.node(ctx, resultNode.elts[1]);
|
|
44
|
+
expect(n1.tag).toBe("NUM");
|
|
45
|
+
expect(n1.elts[0]).toBe("2");
|
|
46
|
+
expect(n2.tag).toBe("NUM");
|
|
47
|
+
expect(n2.elts[0]).toBe("3");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should pass 'pow 2 3' through as POW node", () => {
|
|
51
|
+
const ctx = {
|
|
52
|
+
state: {
|
|
53
|
+
nodePool: ["unused"],
|
|
54
|
+
nodeStack: [],
|
|
55
|
+
nodeStackStack: [],
|
|
56
|
+
nodeMap: {},
|
|
57
|
+
env: [{ name: "global", lexicon: {} }]
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const n1Id = Ast.intern(ctx, { tag: "NUM", elts: ["2"] });
|
|
62
|
+
const n2Id = Ast.intern(ctx, { tag: "NUM", elts: ["3"] });
|
|
63
|
+
const powNodeId = Ast.intern(ctx, { tag: "POW", elts: [n1Id, n2Id] });
|
|
64
|
+
|
|
65
|
+
Folder.fold(ctx, powNodeId);
|
|
66
|
+
const resultId = ctx.state.nodeStack.pop();
|
|
67
|
+
const resultNode = ctx.state.nodePool[resultId];
|
|
68
|
+
|
|
69
|
+
expect(resultNode.tag).toBe("POW");
|
|
70
|
+
expect(resultNode.elts.length).toBe(2);
|
|
71
|
+
const n1 = Ast.node(ctx, resultNode.elts[0]);
|
|
72
|
+
const n2 = Ast.node(ctx, resultNode.elts[1]);
|
|
73
|
+
expect(n1.tag).toBe("NUM");
|
|
74
|
+
expect(n1.elts[0]).toBe("2");
|
|
75
|
+
expect(n2.tag).toBe("NUM");
|
|
76
|
+
expect(n2.elts[0]).toBe("3");
|
|
41
77
|
});
|
|
42
78
|
});
|
package/src/parser.spec.js
CHANGED
|
@@ -370,47 +370,36 @@ describe("parser integration tests", () => {
|
|
|
370
370
|
expect(errorNode.tag).toBe("ERROR");
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
it("should
|
|
374
|
-
//
|
|
375
|
-
// Act - parse a simple addition expression
|
|
373
|
+
it("should parse 'add 123 456' as ADD node with operands", async () => {
|
|
374
|
+
// Arithmetic is deferred to the compiler, not folded at parse time
|
|
376
375
|
const result = await parser.parse(0, "add 123 456..", basisLexicon);
|
|
377
|
-
console.log(
|
|
378
|
-
"TEST",
|
|
379
|
-
"result=" + JSON.stringify(result, null, 2),
|
|
380
|
-
);
|
|
381
376
|
|
|
382
377
|
// Assert
|
|
383
378
|
expect(result).toHaveProperty("root");
|
|
384
379
|
|
|
385
|
-
// Verify basic structure
|
|
386
380
|
const rootId = result.root;
|
|
387
381
|
const rootNode = result[rootId];
|
|
388
382
|
expect(rootNode.tag).toBe("PROG");
|
|
389
383
|
|
|
390
|
-
// Find the
|
|
391
|
-
let
|
|
392
|
-
|
|
393
|
-
// Check all nodes for the result of evaluation (123 + 456 = 579)
|
|
384
|
+
// Find the ADD node
|
|
385
|
+
let addNode = null;
|
|
394
386
|
for (const key in result) {
|
|
395
387
|
if (key !== "root") {
|
|
396
388
|
const node = result[key];
|
|
397
|
-
if (node.tag === "
|
|
398
|
-
|
|
389
|
+
if (node.tag === "ADD") {
|
|
390
|
+
addNode = node;
|
|
399
391
|
break;
|
|
400
392
|
}
|
|
401
393
|
}
|
|
402
394
|
}
|
|
403
395
|
|
|
404
|
-
|
|
405
|
-
expect(
|
|
406
|
-
expect(
|
|
407
|
-
expect(resultNode.elts[0]).toBe("579");
|
|
396
|
+
expect(addNode).not.toBeNull();
|
|
397
|
+
expect(addNode.tag).toBe("ADD");
|
|
398
|
+
expect(addNode.elts.length).toBe(2);
|
|
408
399
|
|
|
409
|
-
//
|
|
410
|
-
// if parse-time evaluation is working correctly
|
|
400
|
+
// Original operands should be preserved
|
|
411
401
|
let found123 = false;
|
|
412
402
|
let found456 = false;
|
|
413
|
-
|
|
414
403
|
for (const key in result) {
|
|
415
404
|
if (key !== "root") {
|
|
416
405
|
const node = result[key];
|
|
@@ -420,11 +409,8 @@ describe("parser integration tests", () => {
|
|
|
420
409
|
}
|
|
421
410
|
}
|
|
422
411
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
// if they were properly evaluated at parse time
|
|
426
|
-
expect(found123).toBe(false);
|
|
427
|
-
expect(found456).toBe(false);
|
|
412
|
+
expect(found123).toBe(true);
|
|
413
|
+
expect(found456).toBe(true);
|
|
428
414
|
});
|
|
429
415
|
|
|
430
416
|
// Tests for escaped quotes
|
package/src/unparse.js
CHANGED
|
@@ -166,10 +166,10 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
|
|
|
166
166
|
return "";
|
|
167
167
|
|
|
168
168
|
case "LAMBDA":
|
|
169
|
-
// Lambda function
|
|
170
|
-
if (node.elts && node.elts.length >=
|
|
171
|
-
const params = node.elts[
|
|
172
|
-
const body = node.elts[
|
|
169
|
+
// Lambda function: elts = [param_names_list, body, pattern_list, init_list]
|
|
170
|
+
if (node.elts && node.elts.length >= 2) {
|
|
171
|
+
const params = node.elts[0];
|
|
172
|
+
const body = node.elts[1];
|
|
173
173
|
|
|
174
174
|
// Extract parameter names
|
|
175
175
|
let paramStr = "";
|