@graffiticode/parser 1.4.1 → 1.4.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/.claude/settings.local.json +2 -1
- package/package.json +2 -2
- package/src/ast.js +12 -6
- package/src/folder.js +2 -1
- package/src/parse.js +5 -5
- package/src/unparse.js +3 -3
- package/src/unparse.spec.js +15 -3
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"Bash(grep:*)",
|
|
9
9
|
"Bash(npm install:*)",
|
|
10
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\")"
|
|
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\")",
|
|
12
|
+
"Bash(git:*)"
|
|
12
13
|
]
|
|
13
14
|
}
|
|
14
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiticode/parser",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"description": "",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@graffiticode/basis": "^1.
|
|
25
|
+
"@graffiticode/basis": "^1.7.0"
|
|
26
26
|
}
|
|
27
27
|
}
|
package/src/ast.js
CHANGED
|
@@ -532,12 +532,18 @@ export class Ast {
|
|
|
532
532
|
Ast.push(ctx, { tag: "POW", elts: [n1, n2] });
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
-
static concat(ctx) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
535
|
+
static concat(ctx, count) {
|
|
536
|
+
// Chain binary CONCATs from the parts on the stack.
|
|
537
|
+
// E.g. 3 parts [a, b, c] becomes CONCAT(CONCAT(a, b), c)
|
|
538
|
+
const parts = [];
|
|
539
|
+
for (let i = 0; i < count; i++) {
|
|
540
|
+
parts.unshift(Ast.pop(ctx));
|
|
541
|
+
}
|
|
542
|
+
let result = parts[0];
|
|
543
|
+
for (let i = 1; i < parts.length; i++) {
|
|
544
|
+
result = { tag: "CONCAT", elts: [result, parts[i]] };
|
|
545
|
+
}
|
|
546
|
+
Ast.push(ctx, result);
|
|
541
547
|
}
|
|
542
548
|
|
|
543
549
|
static eq(ctx) {
|
package/src/folder.js
CHANGED
package/src/parse.js
CHANGED
|
@@ -108,8 +108,8 @@ export const parse = (function () {
|
|
|
108
108
|
null: { tk: 0x15, cls: "val", length: 0 },
|
|
109
109
|
val: { tk: 1, name: "VAL", cls: "function", length: 2, arity: 2 },
|
|
110
110
|
key: { tk: 1, name: "KEY", cls: "function", length: 2, arity: 2 },
|
|
111
|
-
|
|
112
|
-
concat: { tk: 1, name: "CONCAT", cls: "function", length:
|
|
111
|
+
length: { tk: 1, name: "LEN", cls: "function", length: 1, arity: 1 },
|
|
112
|
+
concat: { tk: 1, name: "CONCAT", cls: "function", length: 2, arity: 2 },
|
|
113
113
|
add: { tk: 1, name: "ADD", cls: "function", length: 2, arity: 2 },
|
|
114
114
|
mul: { tk: 1, name: "MUL", cls: "function", length: 2, arity: 2 },
|
|
115
115
|
pow: { tk: 1, name: "POW", cls: "function", length: 2, arity: 2 },
|
|
@@ -362,9 +362,9 @@ export const parse = (function () {
|
|
|
362
362
|
const processedSuffix = processEscapeSequences(lexeme);
|
|
363
363
|
Ast.string(ctx, processedSuffix, getCoord(ctx)); // strip quotes;
|
|
364
364
|
countCounter(ctx);
|
|
365
|
-
|
|
365
|
+
const count = ctx.state.exprc;
|
|
366
366
|
stopCounter(ctx);
|
|
367
|
-
Ast.concat(ctx);
|
|
367
|
+
Ast.concat(ctx, count);
|
|
368
368
|
cc.cls = "string";
|
|
369
369
|
return cc;
|
|
370
370
|
});
|
|
@@ -1240,7 +1240,7 @@ export const parse = (function () {
|
|
|
1240
1240
|
}
|
|
1241
1241
|
|
|
1242
1242
|
// `abc` --> "abc"
|
|
1243
|
-
// `a${x}c` --> concat
|
|
1243
|
+
// `a${x}c` --> concat (concat "a" x) "c"
|
|
1244
1244
|
function string(ctx, c) {
|
|
1245
1245
|
const quoteChar = c;
|
|
1246
1246
|
ctx.state.quoteCharStack.push(c);
|
package/src/unparse.js
CHANGED
|
@@ -100,7 +100,7 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
|
|
|
100
100
|
if (opts.compact) {
|
|
101
101
|
// Compact mode: inline list
|
|
102
102
|
const items = node.elts.map(elt => unparseNode(elt, lexicon, indent, opts));
|
|
103
|
-
return "[" + items.join("
|
|
103
|
+
return "[" + items.join(" ") + "]";
|
|
104
104
|
} else {
|
|
105
105
|
// Pretty print with each element on a new line
|
|
106
106
|
const innerIndent = indent + opts.indentSize;
|
|
@@ -181,9 +181,9 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
|
|
|
181
181
|
const bodyStr = unparseNode(body, lexicon, indent, opts);
|
|
182
182
|
|
|
183
183
|
if (paramStr) {
|
|
184
|
-
return
|
|
184
|
+
return `<${paramStr}: ${bodyStr}>`;
|
|
185
185
|
} else {
|
|
186
|
-
return
|
|
186
|
+
return `<: ${bodyStr}>`;
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
return "";
|
package/src/unparse.spec.js
CHANGED
|
@@ -79,13 +79,13 @@ describe("unparse", () => {
|
|
|
79
79
|
it("should unparse list with multiple elements", async () => {
|
|
80
80
|
const source = "[1, 2, 3]..";
|
|
81
81
|
const unparsed = await testRoundTrip(source);
|
|
82
|
-
expect(unparsed).toBe("[1
|
|
82
|
+
expect(unparsed).toBe("[1 2 3]..");
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it("should unparse nested lists", async () => {
|
|
86
86
|
const source = "[[1, 2], [3, 4]]..";
|
|
87
87
|
const unparsed = await testRoundTrip(source);
|
|
88
|
-
expect(unparsed).toBe("[[1
|
|
88
|
+
expect(unparsed).toBe("[[1 2] [3 4]]..");
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
it("should unparse empty record", async () => {
|
|
@@ -162,6 +162,12 @@ describe("unparse", () => {
|
|
|
162
162
|
expect(unparsed).toBe("concat 'hello' ' world'..");
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
+
it("should unparse template literal", async () => {
|
|
166
|
+
const source = 'let x = "world"..`hello ${x}`..';
|
|
167
|
+
const unparsed = await testRoundTrip(source);
|
|
168
|
+
expect(unparsed).toBe('concat concat "hello " "world" ""..');
|
|
169
|
+
});
|
|
170
|
+
|
|
165
171
|
it.skip("should unparse complex arithmetic expression", async () => {
|
|
166
172
|
const source = "mul (add 1 2) 3..";
|
|
167
173
|
const unparsed = await testRoundTrip(source);
|
|
@@ -207,6 +213,12 @@ describe("unparse", () => {
|
|
|
207
213
|
const unparsed = await testRoundTrip(source);
|
|
208
214
|
expect(unparsed).toBe("foo (bar 42)..");
|
|
209
215
|
});
|
|
216
|
+
|
|
217
|
+
it("should unparse map with lambda and list", async () => {
|
|
218
|
+
const source = 'map (<x: add x 10>) [\n 1\n 2\n 3\n]..';
|
|
219
|
+
const unparsed = await testRoundTrip(source);
|
|
220
|
+
expect(unparsed).toBe("map (<x: add x 10>) [1 2 3]..");
|
|
221
|
+
});
|
|
210
222
|
});
|
|
211
223
|
|
|
212
224
|
describe("control flow", () => {
|
|
@@ -337,7 +349,7 @@ describe("unparse", () => {
|
|
|
337
349
|
it("should support compact option", async () => {
|
|
338
350
|
const source = "[1, 2, 3]..";
|
|
339
351
|
const reformatted = await parser.reformat(0, source, basisLexicon, { compact: true });
|
|
340
|
-
expect(reformatted).toBe("[1
|
|
352
|
+
expect(reformatted).toBe("[1 2 3]..");
|
|
341
353
|
});
|
|
342
354
|
|
|
343
355
|
it("should support custom indent size", async () => {
|