@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.
@@ -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.1",
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.6.4"
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
- const n1 = Ast.node(ctx, Ast.pop(ctx));
537
- Ast.push(ctx, {
538
- tag: "CONCAT",
539
- elts: [n1]
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
@@ -187,7 +187,8 @@ export class Folder {
187
187
 
188
188
  static concat(node) {
189
189
  Folder.#visit(node.elts[0]);
190
- Ast.concat(Folder.#ctx);
190
+ Folder.#visit(node.elts[1]);
191
+ Ast.concat(Folder.#ctx, 2);
191
192
  }
192
193
 
193
194
  static mod(node) {
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
- len: { tk: 1, name: "LEN", cls: "function", length: 1, arity: 1 },
112
- concat: { tk: 1, name: "CONCAT", cls: "function", length: 1, arity: 1 },
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
- Ast.list(ctx, ctx.state.exprc);
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 ["a", x, "b"]
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 `\\${paramStr} . ${bodyStr}`;
184
+ return `<${paramStr}: ${bodyStr}>`;
185
185
  } else {
186
- return `\\. ${bodyStr}`;
186
+ return `<: ${bodyStr}>`;
187
187
  }
188
188
  }
189
189
  return "";
@@ -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, 2, 3]..");
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, 2], [3, 4]]..");
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, 2, 3]..");
352
+ expect(reformatted).toBe("[1 2 3]..");
341
353
  });
342
354
 
343
355
  it("should support custom indent size", async () => {