@graffiticode/parser 1.2.0 → 1.2.2

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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(NODE_OPTIONS=--experimental-vm-modules npx jest:*)",
5
+ "Bash(npx eslint:*)",
6
+ "Bash(npm test:*)",
7
+ "Bash(npm publish:*)"
8
+ ]
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiticode/parser",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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.2"
25
+ "@graffiticode/basis": "^1.6.3"
26
26
  }
27
27
  }
package/src/ast.js CHANGED
@@ -120,6 +120,7 @@ export class Ast {
120
120
  case "STR":
121
121
  case "IDENT":
122
122
  case "BOOL":
123
+ case "TAG":
123
124
  elts[0] = n.elts[0];
124
125
  break;
125
126
  default:
package/src/folder.js CHANGED
@@ -28,6 +28,7 @@ export class Folder {
28
28
  // "MUL": mul,
29
29
  // "DIV": div,
30
30
  // "SUB": sub,
31
+ TAG: Folder.tag,
31
32
  ADD: Folder.add,
32
33
  POW: Folder.pow,
33
34
  MOD: Folder.mod,
@@ -227,8 +228,8 @@ export class Folder {
227
228
  } else {
228
229
  // Tag value.
229
230
  Ast.push(ctx, {
230
- tag: name,
231
- elts: [],
231
+ tag: "TAG",
232
+ elts: [name],
232
233
  coord: node.coord,
233
234
  });
234
235
  }
@@ -245,6 +246,10 @@ export class Folder {
245
246
  static bool(node) {
246
247
  Ast.bool(Folder.#ctx, node.elts[0]);
247
248
  }
249
+
250
+ static tag(node) {
251
+ Ast.push(Folder.#ctx, node);
252
+ }
248
253
  }
249
254
 
250
255
  // Keep backward compatibility export
package/src/unparse.js CHANGED
@@ -46,9 +46,9 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
46
46
  if (node.elts.length >= 3) {
47
47
  const first = node.elts[0];
48
48
  // Check if first element is an identifier that could be a function
49
- if (first && first.tag && first.elts && first.elts.length === 0) {
49
+ if (first && first.tag === "TAG") {
50
50
  // This might be a function name followed by arguments
51
- const funcName = first.tag;
51
+ const funcName = first.elts[0];
52
52
  // Check if this matches a lexicon function
53
53
  if (lexicon && lexicon[funcName]) {
54
54
  const arity = lexicon[funcName].arity || 0;
@@ -88,8 +88,10 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
88
88
  case "IDENT":
89
89
  return node.elts[0];
90
90
 
91
+ case "TAG":
92
+ return node.elts[0];
93
+
91
94
  case "LIST": {
92
- console.log("LIST");
93
95
  // Array literal [a, b, c]
94
96
  if (!node.elts || node.elts.length === 0) {
95
97
  return "[]";
@@ -213,13 +215,15 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
213
215
  // If-then-else
214
216
  if (node.elts && node.elts.length >= 2) {
215
217
  const cond = unparseNode(node.elts[0], lexicon, indent, opts);
216
- const thenExpr = unparseNode(node.elts[1], lexicon, indent, opts);
218
+ const innerIndent = indent + opts.indentSize;
219
+ const indentStr = " ".repeat(innerIndent);
220
+ const thenExpr = unparseNode(node.elts[1], lexicon, innerIndent, opts);
217
221
 
218
222
  if (node.elts.length >= 3) {
219
- const elseExpr = unparseNode(node.elts[2], lexicon, indent, opts);
220
- return `if ${cond} then ${thenExpr} else ${elseExpr}`;
223
+ const elseExpr = unparseNode(node.elts[2], lexicon, innerIndent, opts);
224
+ return `if ${cond} then\n${indentStr}${thenExpr}\nelse\n${indentStr}${elseExpr}`;
221
225
  } else {
222
- return `if ${cond} then ${thenExpr}`;
226
+ return `if ${cond} then\n${indentStr}${thenExpr}`;
223
227
  }
224
228
  }
225
229
  return "";
@@ -228,17 +232,39 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
228
232
  // Case expression
229
233
  if (node.elts && node.elts.length > 0) {
230
234
  const expr = unparseNode(node.elts[0], lexicon, indent, opts);
231
- const cases = node.elts.slice(1).map(c => unparseNode(c, lexicon, indent, opts));
232
- return `case ${expr} of ${cases.join(" | ")}`;
235
+ const innerIndent = indent + opts.indentSize;
236
+ const indentStr = " ".repeat(innerIndent);
237
+ const endIndentStr = " ".repeat(indent);
238
+
239
+ // Process each case branch with proper indentation
240
+ const cases = node.elts.slice(1).map(c => {
241
+ // Pass the inner indent to OF nodes but don't add extra indentation here
242
+ // The OF node will handle its own formatting
243
+ const caseStr = unparseNode(c, lexicon, indent, opts);
244
+ return caseStr;
245
+ });
246
+
247
+ return `case ${expr} of\n${cases.join("\n")}\n${endIndentStr}end`;
233
248
  }
234
249
  return "";
235
250
 
236
251
  case "OF":
237
252
  // Case branch
238
253
  if (node.elts && node.elts.length >= 2) {
254
+ const indentStr = " ".repeat(indent + opts.indentSize);
239
255
  const pattern = unparseNode(node.elts[0], lexicon, indent, opts);
240
- const expr = unparseNode(node.elts[1], lexicon, indent, opts);
241
- return `${pattern} => ${expr}`;
256
+
257
+ // Check if the expression is a CASE node for proper nested formatting
258
+ const exprNode = node.elts[1];
259
+ let expr;
260
+ if (exprNode && exprNode.tag === "CASE") {
261
+ // For nested case, don't add extra indent to the case keyword itself
262
+ expr = unparseNode(exprNode, lexicon, indent + opts.indentSize, opts);
263
+ } else {
264
+ expr = unparseNode(exprNode, lexicon, indent, opts);
265
+ }
266
+
267
+ return `${indentStr}${pattern}: ${expr}`;
242
268
  }
243
269
  return "";
244
270
 
@@ -348,6 +374,7 @@ function reconstructNode(pool, nodeId) {
348
374
  case "STR":
349
375
  case "IDENT":
350
376
  case "BOOL":
377
+ case "TAG":
351
378
  // These nodes have primitive values in elts[0]
352
379
  result.elts = [node.elts[0]];
353
380
  break;
@@ -16,7 +16,7 @@ describe("unparse", () => {
16
16
  it("should unparse string literals", async () => {
17
17
  const source = "'hello, world'..";
18
18
  const unparsed = await testRoundTrip(source);
19
- expect(unparsed).toBe("'hello, world'..");
19
+ expect(unparsed).toBe('"hello, world"..');
20
20
  });
21
21
 
22
22
  it.skip("should unparse string literals with escaped quotes", async () => {
@@ -218,14 +218,14 @@ describe("unparse", () => {
218
218
 
219
219
  it("should unparse if-then-else expression", async () => {
220
220
  const source = "if true then 1 else 2..";
221
- const unparsed = await testRoundTrip(source);
222
- expect(unparsed).toBe("if true then 1 else 2..");
221
+ const unparsed = await testRoundTrip(source, {}, { compact: false });
222
+ expect(unparsed).toBe("if true then\n 1\nelse\n 2..");
223
223
  });
224
224
 
225
225
  it("should unparse nested if expressions", async () => {
226
226
  const source = "if true then (if false then 1 else 2) else 3..";
227
- const unparsed = await testRoundTrip(source);
228
- expect(unparsed).toBe("if true then (if false then 1 else 2) else 3..");
227
+ const unparsed = await testRoundTrip(source, {}, { compact: false });
228
+ expect(unparsed).toBe("if true then\n (if false then\n 1\nelse\n 2)\nelse\n 3..");
229
229
  });
230
230
  });
231
231
 
@@ -328,7 +328,7 @@ describe("unparse", () => {
328
328
  it("should reformat multiple expressions", async () => {
329
329
  const source = "'hello'.[1, 2].{x: 10}..";
330
330
  const reformatted = await parser.reformat(0, source, basisLexicon);
331
- expect(reformatted).toContain("'hello'");
331
+ expect(reformatted).toContain('"hello"');
332
332
  expect(reformatted).toContain("[\n 1");
333
333
  expect(reformatted).toContain("{\n x: 10");
334
334
  expect(reformatted).toContain("..");
@@ -346,5 +346,11 @@ describe("unparse", () => {
346
346
  expect(reformatted).toContain(" 1"); // 4 spaces
347
347
  expect(reformatted).toContain(" 2"); // 4 spaces
348
348
  });
349
+
350
+ it("should preserve escaped quotes in strings", async () => {
351
+ const source = '"\\\"hello\\\""..'
352
+ const reformatted = await parser.reformat(0, source, basisLexicon, { compact: true });
353
+ expect(reformatted).toBe('"\\\"hello\\\""..'); // Should produce identical program
354
+ });
349
355
  });
350
356
  });