@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.
- package/package.json +1 -1
- package/src/index.js +0 -1
- package/src/parser.js +16 -46
- package/src/parser.spec.js +49 -143
- package/src/unparse-l0166.spec.js +364 -0
- package/src/unparse-l0166.spec.js~ +341 -0
- package/src/unparse.js +78 -31
- package/src/unparse.spec.js +113 -54
|
@@ -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
|
+
});
|