@graffiticode/parser 0.3.0 → 1.0.0

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,364 @@
1
+ import { parser } from "./parser.js";
2
+ import { unparse } from "./unparse.js";
3
+ import { lexicon as basisLexicon } from "@graffiticode/basis";
4
+
5
+ describe("unparse with L0166 lexicon", () => {
6
+ // L0166 lexicon for spreadsheet operations (from l0166/packages/api/src/lexicon.js)
7
+ const l0166Lexicon = {
8
+ "title": {
9
+ "tk": 1,
10
+ "name": "TITLE",
11
+ "cls": "function",
12
+ "length": 2,
13
+ "arity": 2,
14
+ },
15
+ "instructions": {
16
+ "tk": 1,
17
+ "name": "INSTRUCTIONS",
18
+ "cls": "function",
19
+ "length": 2,
20
+ "arity": 2,
21
+ },
22
+ "params": {
23
+ "tk": 1,
24
+ "name": "PARAMS",
25
+ "cls": "function",
26
+ "length": 2,
27
+ "arity": 2,
28
+ },
29
+ "cell": {
30
+ "tk": 1,
31
+ "name": "CELL",
32
+ "cls": "function",
33
+ "length": 2,
34
+ "arity": 2,
35
+ },
36
+ "text": {
37
+ "tk": 1,
38
+ "name": "TEXT",
39
+ "cls": "function",
40
+ "length": 2,
41
+ "arity": 2,
42
+ },
43
+ "assess": {
44
+ "tk": 1,
45
+ "name": "ASSESS",
46
+ "cls": "function",
47
+ "length": 2,
48
+ "arity": 2,
49
+ },
50
+ "method": {
51
+ "tk": 1,
52
+ "name": "METHOD",
53
+ "cls": "function",
54
+ "length": 1,
55
+ "arity": 1,
56
+ },
57
+ "expected": {
58
+ "tk": 1,
59
+ "name": "EXPECTED",
60
+ "cls": "function",
61
+ "length": 1,
62
+ "arity": 1,
63
+ },
64
+ "width": {
65
+ "tk": 1,
66
+ "name": "WIDTH",
67
+ "cls": "function",
68
+ "length": 2,
69
+ "arity": 2,
70
+ },
71
+ "align": {
72
+ "tk": 1,
73
+ "name": "ALIGN",
74
+ "cls": "function",
75
+ "length": 2,
76
+ "arity": 2,
77
+ },
78
+ "background-color": {
79
+ "tk": 1,
80
+ "name": "BACKGROUND_COLOR",
81
+ "cls": "function",
82
+ "length": 2,
83
+ "arity": 2,
84
+ },
85
+ "font-weight": {
86
+ "tk": 1,
87
+ "name": "FONT_WEIGHT",
88
+ "cls": "function",
89
+ "length": 2,
90
+ "arity": 2,
91
+ },
92
+ "format": {
93
+ "tk": 1,
94
+ "name": "FORMAT",
95
+ "cls": "function",
96
+ "length": 2,
97
+ "arity": 2,
98
+ },
99
+ "protected": {
100
+ "tk": 1,
101
+ "name": "PROTECTED",
102
+ "cls": "function",
103
+ "length": 2,
104
+ "arity": 2,
105
+ },
106
+ "cells": {
107
+ "tk": 1,
108
+ "name": "CELLS",
109
+ "cls": "function",
110
+ "length": 2,
111
+ "arity": 2,
112
+ },
113
+ "rows": {
114
+ "tk": 1,
115
+ "name": "ROWS",
116
+ "cls": "function",
117
+ "length": 2,
118
+ "arity": 2,
119
+ },
120
+ "column": {
121
+ "tk": 1,
122
+ "name": "COLUMN",
123
+ "cls": "function",
124
+ "length": 2,
125
+ "arity": 2,
126
+ },
127
+ "columns": {
128
+ "tk": 1,
129
+ "name": "COLUMNS",
130
+ "cls": "function",
131
+ "length": 2,
132
+ "arity": 2,
133
+ }
134
+ };
135
+
136
+ // Merge basis and L0166 lexicons
137
+ const mergedLexicon = { ...basisLexicon, ...l0166Lexicon };
138
+
139
+ it("should unparse L0166 spreadsheet code", async () => {
140
+ const source = `columns [
141
+ column A width 100 align "center" protected true {}
142
+ ]
143
+ rows [
144
+ row 1 background-color "#eee" protected true {}
145
+ ]
146
+ cells [
147
+ cell A1 text "A1" protected true {}
148
+ ]
149
+ {
150
+ v: "0.0.1"
151
+ }..`;
152
+
153
+ // Note: The parser may transform this code, so we test that unparse
154
+ // produces valid code that can be parsed again
155
+ // Pass the lexicon directly to avoid fetching
156
+
157
+ // Parse with merged lexicon
158
+ const ast = await parser.parse(0, source, mergedLexicon);
159
+
160
+ // Log the AST pool
161
+ console.log("AST Pool:", JSON.stringify(ast, null, 2));
162
+
163
+ const unparsed = unparse(ast, l0166Lexicon);
164
+
165
+ // The unparsed code should be valid and parseable
166
+ expect(unparsed).toBeDefined();
167
+ expect(unparsed.endsWith("..")).toBe(true);
168
+
169
+ // Check that key elements appear in the output
170
+ // (the exact format may differ due to how the parser handles the syntax)
171
+ console.log("Original source:", source);
172
+ console.log("Unparsed:", unparsed);
173
+ });
174
+
175
+ it("should handle individual L0166 constructs", async () => {
176
+ const tests = [
177
+ {
178
+ source: '{v: "0.0.1"}..',
179
+ description: "version record"
180
+ },
181
+ {
182
+ source: '[]..',
183
+ description: "empty list"
184
+ },
185
+ {
186
+ source: '{}..',
187
+ description: "empty record"
188
+ },
189
+ {
190
+ source: '"A1"..',
191
+ description: "string literal"
192
+ },
193
+ {
194
+ source: '100..',
195
+ description: "number literal"
196
+ },
197
+ {
198
+ source: 'true..',
199
+ description: "boolean literal"
200
+ }
201
+ ];
202
+
203
+ for (const { source, description } of tests) {
204
+ const ast = await parser.parse(166, source, mergedLexicon);
205
+ const unparsed = unparse(ast, l0166Lexicon);
206
+
207
+ // Check that unparse produces output
208
+ expect(unparsed).toBeDefined();
209
+ expect(unparsed).not.toBe("");
210
+
211
+ // The output should end with ..
212
+ if (!unparsed.endsWith("..")) {
213
+ console.log(`${description}: "${source}" -> "${unparsed}"`);
214
+ }
215
+ expect(unparsed.endsWith("..")).toBe(true);
216
+ }
217
+ });
218
+
219
+ it("should preserve simple L0166 expressions", async () => {
220
+ // Test simpler L0166 expressions that should parse correctly
221
+ const tests = [
222
+ 'column A {}..',
223
+ 'row 1 {}..',
224
+ 'cell A1 {}..',
225
+ ];
226
+
227
+ for (const source of tests) {
228
+ const ast = await parser.parse(0, source, mergedLexicon);
229
+ const unparsed = unparse(ast, l0166Lexicon);
230
+
231
+ // Should produce valid output
232
+ expect(unparsed).toBeDefined();
233
+ expect(unparsed.endsWith("..")).toBe(true);
234
+
235
+ console.log(`Simple L0166: "${source}" -> "${unparsed}"`);
236
+ }
237
+ });
238
+
239
+ it("should handle complex L0166 budget assessment code", async () => {
240
+ const source = `title "Home Budget Assessment"
241
+ instructions \`
242
+ - Calculate your monthly budget based on income percentages
243
+ - Fill in the empty cells with the correct formulas
244
+ - Ensure all expenses and savings are properly allocated
245
+ \`
246
+ columns [
247
+ column A width 150 align "left" {}
248
+ column B width 100 format "($#,##0)" {}
249
+ column C width 250 align "left" {}
250
+ ]
251
+ cells [
252
+ cell A1 text "CATEGORY" font-weight "bold" {}
253
+ cell B1 text "AMOUNT" font-weight "bold" {}
254
+ cell C1 text "DETAILS" font-weight "bold" {}
255
+
256
+ cell A2 text "Income" {}
257
+ cell B2 text "4000" {}
258
+ cell C2 text "Total monthly income" {}
259
+
260
+ cell A3 text "Rent" {}
261
+ cell B3
262
+ text "",
263
+ assess [
264
+ method "value"
265
+ expected "1400"
266
+ ] {}
267
+ cell C3 text "35% of your total income" {}
268
+
269
+ cell A4 text "Utilities" {}
270
+ cell B4 text "200" {}
271
+ cell C4 text "Fixed expense" {}
272
+
273
+ cell A5 text "Food" {}
274
+ cell B5
275
+ text "",
276
+ assess [
277
+ method "value"
278
+ expected "600"
279
+ ] {}
280
+ cell C5 text "15% of your total income" {}
281
+
282
+ cell A6 text "Transportation" {}
283
+ cell B6
284
+ text "",
285
+ assess [
286
+ method "value"
287
+ expected "400"
288
+ ] {}
289
+ cell C6 text "10% of your total income" {}
290
+
291
+ cell A7 text "Entertainment" {}
292
+ cell B7 text "150" {}
293
+ cell C7 text "Fixed expense" {}
294
+
295
+ cell A8 text "Savings" {}
296
+ cell B8
297
+ text "",
298
+ assess [
299
+ method "value"
300
+ expected "800"
301
+ ] {}
302
+ cell C8 text "20% of your total income" {}
303
+
304
+ cell A9 text "Miscellaneous" {}
305
+ cell B9
306
+ text "",
307
+ assess [
308
+ method "value"
309
+ expected "450"
310
+ ] {}
311
+ cell C9 text "Remaining income after all other expenses" {}
312
+ ]
313
+ {
314
+ v: "0.0.1"
315
+ }..`;
316
+
317
+ // Parse with merged lexicon
318
+ const ast = await parser.parse("0166", source, mergedLexicon);
319
+
320
+ console.log("Complex L0166 AST nodes:", Object.keys(ast).length);
321
+
322
+ const unparsed = unparse(ast, l0166Lexicon);
323
+
324
+ // The unparsed code should be valid and parseable
325
+ expect(unparsed).toBeDefined();
326
+ expect(unparsed.endsWith("..")).toBe(true);
327
+
328
+ // Check that key elements appear in the output
329
+ expect(unparsed).toContain("title");
330
+ expect(unparsed).toContain("columns");
331
+ expect(unparsed).toContain("cells");
332
+ expect(unparsed).toContain("column A");
333
+ expect(unparsed).toContain("column B");
334
+ expect(unparsed).toContain("column C");
335
+
336
+ // Log a portion of the output to see the pretty printing
337
+ const lines = unparsed.split("\n");
338
+ console.log("First 20 lines of unparsed output:");
339
+ console.log(lines.slice(0, 20).join("\n"));
340
+ console.log("...");
341
+ console.log("Last 10 lines of unparsed output:");
342
+ console.log(lines.slice(-10).join("\n"));
343
+ console.log(unparsed);
344
+ });
345
+
346
+ it("should reformat L0166 code using parser.reformat", async () => {
347
+ const source = `columns [column A width 100 {}] rows [row 1 {}] cells [cell A1 text "Hello" {}] {v: "0.0.1"}..`;
348
+
349
+ // Reformat with merged lexicon
350
+ const reformatted = await parser.reformat("0166", source, mergedLexicon);
351
+
352
+ // Check that it produces valid output
353
+ expect(reformatted).toBeDefined();
354
+ expect(reformatted.endsWith("..")).toBe(true);
355
+
356
+ // Check for pretty printing
357
+ expect(reformatted).toContain("columns [\n");
358
+ expect(reformatted).toContain("rows [\n");
359
+ expect(reformatted).toContain("cells [\n");
360
+
361
+ console.log("Reformatted L0166 code:");
362
+ console.log(reformatted);
363
+ });
364
+ });
@@ -0,0 +1,341 @@
1
+ import { parser } from "./parser.js";
2
+ import { unparse } from "./unparse.js";
3
+
4
+ describe("unparse with L0166 lexicon", () => {
5
+ // L0166 lexicon for spreadsheet operations (from l0166/packages/api/src/lexicon.js)
6
+ const l0166Lexicon = {
7
+ "title": {
8
+ "tk": 1,
9
+ "name": "TITLE",
10
+ "cls": "function",
11
+ "length": 2,
12
+ "arity": 2,
13
+ },
14
+ "instructions": {
15
+ "tk": 1,
16
+ "name": "INSTRUCTIONS",
17
+ "cls": "function",
18
+ "length": 2,
19
+ "arity": 2,
20
+ },
21
+ "params": {
22
+ "tk": 1,
23
+ "name": "PARAMS",
24
+ "cls": "function",
25
+ "length": 2,
26
+ "arity": 2,
27
+ },
28
+ "cell": {
29
+ "tk": 1,
30
+ "name": "CELL",
31
+ "cls": "function",
32
+ "length": 2,
33
+ "arity": 2,
34
+ },
35
+ "text": {
36
+ "tk": 1,
37
+ "name": "TEXT",
38
+ "cls": "function",
39
+ "length": 2,
40
+ "arity": 2,
41
+ },
42
+ "assess": {
43
+ "tk": 1,
44
+ "name": "ASSESS",
45
+ "cls": "function",
46
+ "length": 2,
47
+ "arity": 2,
48
+ },
49
+ "method": {
50
+ "tk": 1,
51
+ "name": "METHOD",
52
+ "cls": "function",
53
+ "length": 1,
54
+ "arity": 1,
55
+ },
56
+ "expected": {
57
+ "tk": 1,
58
+ "name": "EXPECTED",
59
+ "cls": "function",
60
+ "length": 1,
61
+ "arity": 1,
62
+ },
63
+ "width": {
64
+ "tk": 1,
65
+ "name": "WIDTH",
66
+ "cls": "function",
67
+ "length": 2,
68
+ "arity": 2,
69
+ },
70
+ "align": {
71
+ "tk": 1,
72
+ "name": "ALIGN",
73
+ "cls": "function",
74
+ "length": 2,
75
+ "arity": 2,
76
+ },
77
+ "background-color": {
78
+ "tk": 1,
79
+ "name": "BACKGROUND_COLOR",
80
+ "cls": "function",
81
+ "length": 2,
82
+ "arity": 2,
83
+ },
84
+ "font-weight": {
85
+ "tk": 1,
86
+ "name": "FONT_WEIGHT",
87
+ "cls": "function",
88
+ "length": 2,
89
+ "arity": 2,
90
+ },
91
+ "format": {
92
+ "tk": 1,
93
+ "name": "FORMAT",
94
+ "cls": "function",
95
+ "length": 2,
96
+ "arity": 2,
97
+ },
98
+ "protected": {
99
+ "tk": 1,
100
+ "name": "PROTECTED",
101
+ "cls": "function",
102
+ "length": 2,
103
+ "arity": 2,
104
+ },
105
+ "cells": {
106
+ "tk": 1,
107
+ "name": "CELLS",
108
+ "cls": "function",
109
+ "length": 2,
110
+ "arity": 2,
111
+ },
112
+ "rows": {
113
+ "tk": 1,
114
+ "name": "ROWS",
115
+ "cls": "function",
116
+ "length": 2,
117
+ "arity": 2,
118
+ },
119
+ "column": {
120
+ "tk": 1,
121
+ "name": "COLUMN",
122
+ "cls": "function",
123
+ "length": 2,
124
+ "arity": 2,
125
+ },
126
+ "columns": {
127
+ "tk": 1,
128
+ "name": "COLUMNS",
129
+ "cls": "function",
130
+ "length": 2,
131
+ "arity": 2,
132
+ }
133
+ };
134
+
135
+ it("should unparse L0166 spreadsheet code", async () => {
136
+ const source = `columns [
137
+ column A width 100 align "center" protected true {}
138
+ ]
139
+ rows [
140
+ row 1 background-color "#eee" protected true {}
141
+ ]
142
+ cells [
143
+ cell A1 text "A1" protected true {}
144
+ ]
145
+ {
146
+ v: "0.0.1"
147
+ }..`;
148
+
149
+ // Note: The parser may transform this code, so we test that unparse
150
+ // produces valid code that can be parsed again
151
+ // Pass the lexicon directly to avoid fetching
152
+
153
+ // For complex L0166 code, we'll just parse with language 0
154
+ // since the specific L0166 syntax may require special handling
155
+ const ast = await parser.parse(0, source);
156
+
157
+ // Log the AST pool
158
+ console.log("AST Pool:", JSON.stringify(ast, null, 2));
159
+
160
+ const unparsed = unparse(ast, l0166Lexicon);
161
+
162
+ // The unparsed code should be valid and parseable
163
+ expect(unparsed).toBeDefined();
164
+ expect(unparsed.endsWith("..")).toBe(true);
165
+
166
+ // Check that key elements appear in the output
167
+ // (the exact format may differ due to how the parser handles the syntax)
168
+ console.log("Original source:", source);
169
+ console.log("Unparsed:", unparsed);
170
+ });
171
+
172
+ it("should handle individual L0166 constructs", async () => {
173
+ const tests = [
174
+ {
175
+ source: '{v: "0.0.1"}..',
176
+ description: "version record"
177
+ },
178
+ {
179
+ source: '[]..',
180
+ description: "empty list"
181
+ },
182
+ {
183
+ source: '{}..',
184
+ description: "empty record"
185
+ },
186
+ {
187
+ source: '"A1"..',
188
+ description: "string literal"
189
+ },
190
+ {
191
+ source: '100..',
192
+ description: "number literal"
193
+ },
194
+ {
195
+ source: 'true..',
196
+ description: "boolean literal"
197
+ }
198
+ ];
199
+
200
+ for (const { source, description } of tests) {
201
+ const ast = await parser.parse(166, source, l0166Lexicon);
202
+ const unparsed = unparse(ast, l0166Lexicon);
203
+
204
+ // Check that unparse produces output
205
+ expect(unparsed).toBeDefined();
206
+ expect(unparsed).not.toBe("");
207
+
208
+ // The output should end with ..
209
+ if (!unparsed.endsWith("..")) {
210
+ console.log(`${description}: "${source}" -> "${unparsed}"`);
211
+ }
212
+ expect(unparsed.endsWith("..")).toBe(true);
213
+ }
214
+ });
215
+
216
+ it("should preserve simple L0166 expressions", async () => {
217
+ // Test simpler L0166 expressions that should parse correctly
218
+ const tests = [
219
+ 'column A {}..',
220
+ 'row 1 {}..',
221
+ 'cell A1 {}..',
222
+ ];
223
+
224
+ for (const source of tests) {
225
+ const ast = await parser.parse(0, source);
226
+ const unparsed = unparse(ast, l0166Lexicon);
227
+
228
+ // Should produce valid output
229
+ expect(unparsed).toBeDefined();
230
+ expect(unparsed.endsWith("..")).toBe(true);
231
+
232
+ console.log(`Simple L0166: "${source}" -> "${unparsed}"`);
233
+ }
234
+ });
235
+
236
+ it("should handle complex L0166 budget assessment code", async () => {
237
+ const source = `title "Home Budget Assessment"
238
+ instructions \`
239
+ - Calculate your monthly budget based on income percentages
240
+ - Fill in the empty cells with the correct formulas
241
+ - Ensure all expenses and savings are properly allocated
242
+ \`
243
+ columns [
244
+ column A width 150 align "left" {}
245
+ column B width 100 format "($#,##0)" {}
246
+ column C width 250 align "left" {}
247
+ ]
248
+ cells [
249
+ cell A1 text "CATEGORY" font-weight "bold" {}
250
+ cell B1 text "AMOUNT" font-weight "bold" {}
251
+ cell C1 text "DETAILS" font-weight "bold" {}
252
+
253
+ cell A2 text "Income" {}
254
+ cell B2 text "4000" {}
255
+ cell C2 text "Total monthly income" {}
256
+
257
+ cell A3 text "Rent" {}
258
+ cell B3
259
+ text "",
260
+ assess [
261
+ method "value"
262
+ expected "1400"
263
+ ] {}
264
+ cell C3 text "35% of your total income" {}
265
+
266
+ cell A4 text "Utilities" {}
267
+ cell B4 text "200" {}
268
+ cell C4 text "Fixed expense" {}
269
+
270
+ cell A5 text "Food" {}
271
+ cell B5
272
+ text "",
273
+ assess [
274
+ method "value"
275
+ expected "600"
276
+ ] {}
277
+ cell C5 text "15% of your total income" {}
278
+
279
+ cell A6 text "Transportation" {}
280
+ cell B6
281
+ text "",
282
+ assess [
283
+ method "value"
284
+ expected "400"
285
+ ] {}
286
+ cell C6 text "10% of your total income" {}
287
+
288
+ cell A7 text "Entertainment" {}
289
+ cell B7 text "150" {}
290
+ cell C7 text "Fixed expense" {}
291
+
292
+ cell A8 text "Savings" {}
293
+ cell B8
294
+ text "",
295
+ assess [
296
+ method "value"
297
+ expected "800"
298
+ ] {}
299
+ cell C8 text "20% of your total income" {}
300
+
301
+ cell A9 text "Miscellaneous" {}
302
+ cell B9
303
+ text "",
304
+ assess [
305
+ method "value"
306
+ expected "450"
307
+ ] {}
308
+ cell C9 text "Remaining income after all other expenses" {}
309
+ ]
310
+ {
311
+ v: "0.0.1"
312
+ }..`;
313
+
314
+ // Parse with L0166 lexicon
315
+ const ast = await parser.parse("0166", source, l0166Lexicon);
316
+
317
+ console.log("Complex L0166 AST nodes:", Object.keys(ast).length);
318
+
319
+ const unparsed = unparse(ast, l0166Lexicon);
320
+
321
+ // The unparsed code should be valid and parseable
322
+ expect(unparsed).toBeDefined();
323
+ expect(unparsed.endsWith("..")).toBe(true);
324
+
325
+ // Check that key elements appear in the output
326
+ expect(unparsed).toContain("title");
327
+ expect(unparsed).toContain("columns");
328
+ expect(unparsed).toContain("cells");
329
+ expect(unparsed).toContain("column A");
330
+ expect(unparsed).toContain("column B");
331
+ expect(unparsed).toContain("column C");
332
+
333
+ // Log a portion of the output to see the pretty printing
334
+ const lines = unparsed.split("\n");
335
+ console.log("First 20 lines of unparsed output:");
336
+ console.log(lines.slice(0, 20).join("\n"));
337
+ console.log("...");
338
+ console.log("Last 10 lines of unparsed output:");
339
+ console.log(lines.slice(-10).join("\n"));
340
+ });
341
+ });