@graffiticode/parser 0.2.0 → 0.4.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/CLAUDE.md +72 -0
- package/package.json +5 -2
- package/src/parser.js +24 -8
- package/src/parser.spec.js +21 -0
- package/src/unparse-l0166.spec.js +361 -0
- package/src/unparse-l0166.spec.js~ +341 -0
- package/src/unparse.js +377 -0
- package/src/unparse.spec.js +347 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
```bash
|
|
9
|
+
# Run all tests with experimental VM modules
|
|
10
|
+
npm test
|
|
11
|
+
|
|
12
|
+
# Run specific test files
|
|
13
|
+
NODE_OPTIONS=--experimental-vm-modules jest src/parser.spec.js
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Linting
|
|
17
|
+
```bash
|
|
18
|
+
# Lint code
|
|
19
|
+
npm run lint
|
|
20
|
+
|
|
21
|
+
# Lint and automatically fix issues
|
|
22
|
+
npm run lint:fix
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Architecture Overview
|
|
26
|
+
|
|
27
|
+
This is the Graffiticode parser package - a core component that parses Graffiticode language syntax into ASTs (Abstract Syntax Trees).
|
|
28
|
+
|
|
29
|
+
### Package Structure
|
|
30
|
+
|
|
31
|
+
The parser is a workspace package within the Graffiticode monorepo. It's an ES module package (`"type": "module"`) that exports parsing functionality used by the API and language compilers.
|
|
32
|
+
|
|
33
|
+
### Core Components
|
|
34
|
+
|
|
35
|
+
1. **Parser Entry Point** (`src/parser.js`):
|
|
36
|
+
- `buildParser()` - Factory function that creates a parser instance with dependencies
|
|
37
|
+
- Integrates with language lexicons loaded from the API
|
|
38
|
+
- Uses Node.js VM module for sandboxed execution
|
|
39
|
+
|
|
40
|
+
2. **Core Parser** (`src/parse.js`):
|
|
41
|
+
- Implements the main parsing logic with a state machine approach
|
|
42
|
+
- Handles tokenization and AST construction
|
|
43
|
+
- Includes error tracking and position coordinates
|
|
44
|
+
- Supports keywords, operators, and language-specific lexicons
|
|
45
|
+
|
|
46
|
+
3. **AST Module** (`src/ast.js`):
|
|
47
|
+
- Manages AST node creation and manipulation
|
|
48
|
+
- Node pooling for memory efficiency
|
|
49
|
+
- Error node generation
|
|
50
|
+
|
|
51
|
+
4. **Environment** (`src/env.js`):
|
|
52
|
+
- Manages parsing environment and scopes
|
|
53
|
+
- Handles lexicon lookups
|
|
54
|
+
|
|
55
|
+
5. **Folder** (`src/folder.js`):
|
|
56
|
+
- AST transformation and folding operations
|
|
57
|
+
|
|
58
|
+
## Testing Strategy
|
|
59
|
+
|
|
60
|
+
- Uses Jest with experimental VM modules support
|
|
61
|
+
- Test files follow `*.spec.js` pattern
|
|
62
|
+
- Main test file: `src/parser.spec.js` contains comprehensive parsing tests
|
|
63
|
+
|
|
64
|
+
## Monorepo Context
|
|
65
|
+
|
|
66
|
+
This parser package is part of the Graffiticode monorepo:
|
|
67
|
+
- Parent monorepo runs Firebase emulators for integration testing
|
|
68
|
+
- API package (`../api`) depends on this parser
|
|
69
|
+
- Auth packages (`../auth`, `../auth-client`) handle authentication
|
|
70
|
+
- Common package (`../common`) contains shared utilities
|
|
71
|
+
|
|
72
|
+
When working with the parser, be aware that it integrates tightly with the API's language loading mechanism (`../../api/src/lang/index.js`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graffiticode/parser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -20,5 +20,8 @@
|
|
|
20
20
|
"keywords": [],
|
|
21
21
|
"author": "",
|
|
22
22
|
"license": "MIT",
|
|
23
|
-
"description": ""
|
|
23
|
+
"description": "",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@graffiticode/basis": "^1.6.2"
|
|
26
|
+
}
|
|
24
27
|
}
|
package/src/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import vm from "vm";
|
|
2
2
|
import { getLangAsset } from "../../api/src/lang/index.js";
|
|
3
3
|
import { parse } from "./parse.js";
|
|
4
|
+
import { unparse } from "./unparse.js";
|
|
4
5
|
|
|
5
6
|
// commonjs export
|
|
6
7
|
const main = {
|
|
@@ -47,7 +48,13 @@ export const buildParser = ({
|
|
|
47
48
|
vm
|
|
48
49
|
}) => {
|
|
49
50
|
return {
|
|
50
|
-
async parse(lang, src) {
|
|
51
|
+
async parse(lang, src, lexicon = null) {
|
|
52
|
+
// If lexicon is provided, use it directly
|
|
53
|
+
if (lexicon) {
|
|
54
|
+
return await main.parse(src, lexicon);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Otherwise, load from cache or remote
|
|
51
58
|
if (!cache.has(lang)) {
|
|
52
59
|
let data = await getLangAsset(lang, "/lexicon.js");
|
|
53
60
|
// TODO Make lexicon JSON.
|
|
@@ -59,9 +66,9 @@ export const buildParser = ({
|
|
|
59
66
|
throw new Error("unable to use lexicon");
|
|
60
67
|
}
|
|
61
68
|
const lstr = data.substring(data.indexOf("{"));
|
|
62
|
-
let
|
|
69
|
+
let loadedLexicon;
|
|
63
70
|
try {
|
|
64
|
-
|
|
71
|
+
loadedLexicon = JSON.parse(lstr);
|
|
65
72
|
} catch (err) {
|
|
66
73
|
if (err instanceof SyntaxError) {
|
|
67
74
|
log(`failed to parse ${lang} lexicon: ${err.message}`);
|
|
@@ -69,17 +76,17 @@ export const buildParser = ({
|
|
|
69
76
|
vm.createContext(context);
|
|
70
77
|
vm.runInContext(data, context);
|
|
71
78
|
if (typeof (context.window.gcexports.globalLexicon) === "object") {
|
|
72
|
-
|
|
79
|
+
loadedLexicon = context.window.gcexports.globalLexicon;
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
|
-
if (!
|
|
82
|
+
if (!loadedLexicon) {
|
|
76
83
|
throw new Error("Malformed lexicon");
|
|
77
84
|
}
|
|
78
85
|
}
|
|
79
|
-
cache.set(lang,
|
|
86
|
+
cache.set(lang, loadedLexicon);
|
|
80
87
|
};
|
|
81
|
-
const
|
|
82
|
-
return await main.parse(src,
|
|
88
|
+
const cachedLexicon = cache.get(lang);
|
|
89
|
+
return await main.parse(src, cachedLexicon);
|
|
83
90
|
}
|
|
84
91
|
};
|
|
85
92
|
};
|
|
@@ -91,3 +98,12 @@ export const parser = buildParser({
|
|
|
91
98
|
main,
|
|
92
99
|
vm
|
|
93
100
|
});
|
|
101
|
+
|
|
102
|
+
// Add unparse as a property of parser
|
|
103
|
+
parser.unparse = unparse;
|
|
104
|
+
|
|
105
|
+
// Add reformat function that parses and unparses code
|
|
106
|
+
parser.reformat = async function(lang, src, lexicon, options = {}) {
|
|
107
|
+
const ast = await this.parse(lang, src, lexicon);
|
|
108
|
+
return unparse(ast, lexicon, options);
|
|
109
|
+
};
|
package/src/parser.spec.js
CHANGED
|
@@ -5,6 +5,27 @@ import vm from "vm";
|
|
|
5
5
|
|
|
6
6
|
describe("lang/parser", () => {
|
|
7
7
|
const log = jest.fn();
|
|
8
|
+
it("should use provided lexicon directly", async () => {
|
|
9
|
+
// Arrange
|
|
10
|
+
const cache = new Map();
|
|
11
|
+
const getLangAsset = jest.fn(); // Should not be called
|
|
12
|
+
const main = {
|
|
13
|
+
parse: mockPromiseValue({ root: "0" })
|
|
14
|
+
};
|
|
15
|
+
const parser = buildParser({ log, cache, getLangAsset, main });
|
|
16
|
+
const lang = "0";
|
|
17
|
+
const src = "'foo'..";
|
|
18
|
+
const providedLexicon = { test: "lexicon" };
|
|
19
|
+
|
|
20
|
+
// Act
|
|
21
|
+
await expect(parser.parse(lang, src, providedLexicon)).resolves.toStrictEqual({ root: "0" });
|
|
22
|
+
|
|
23
|
+
// Assert
|
|
24
|
+
expect(getLangAsset).not.toHaveBeenCalled(); // Should not fetch when lexicon is provided
|
|
25
|
+
expect(main.parse).toHaveBeenCalledWith(src, providedLexicon);
|
|
26
|
+
expect(cache.has(lang)).toBe(false); // Should not cache when lexicon is provided
|
|
27
|
+
});
|
|
28
|
+
|
|
8
29
|
it("should call main parser language lexicon", async () => {
|
|
9
30
|
// Arrange
|
|
10
31
|
const cache = new Map();
|
|
@@ -0,0 +1,361 @@
|
|
|
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
|
+
console.log(unparsed);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should reformat L0166 code using parser.reformat", async () => {
|
|
344
|
+
const source = `columns [column A width 100 {}] rows [row 1 {}] cells [cell A1 text "Hello" {}] {v: "0.0.1"}..`;
|
|
345
|
+
|
|
346
|
+
// Reformat with L0166 lexicon
|
|
347
|
+
const reformatted = await parser.reformat("0166", source, l0166Lexicon);
|
|
348
|
+
|
|
349
|
+
// Check that it produces valid output
|
|
350
|
+
expect(reformatted).toBeDefined();
|
|
351
|
+
expect(reformatted.endsWith("..")).toBe(true);
|
|
352
|
+
|
|
353
|
+
// Check for pretty printing
|
|
354
|
+
expect(reformatted).toContain("columns [\n");
|
|
355
|
+
expect(reformatted).toContain("rows [\n");
|
|
356
|
+
expect(reformatted).toContain("cells [\n");
|
|
357
|
+
|
|
358
|
+
console.log("Reformatted L0166 code:");
|
|
359
|
+
console.log(reformatted);
|
|
360
|
+
});
|
|
361
|
+
});
|