@graffiticode/parser 1.1.0 → 1.2.1
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/unparse.js +274 -250
- package/src/unparse.spec.js +12 -6
package/package.json
CHANGED
package/src/unparse.js
CHANGED
|
@@ -28,272 +28,296 @@ function unparseNode(node, lexicon, indent = 0, options = {}) {
|
|
|
28
28
|
|
|
29
29
|
// Handle AST nodes
|
|
30
30
|
switch (node.tag) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
case "PROG":
|
|
32
|
+
// Program is a list of expressions ending with ".."
|
|
33
|
+
if (node.elts && node.elts.length > 0) {
|
|
34
|
+
const exprs = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
35
|
+
return exprs + "..";
|
|
36
|
+
}
|
|
37
|
+
return "..";
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
39
|
+
case "EXPRS":
|
|
40
|
+
// Multiple expressions
|
|
41
|
+
if (!node.elts || node.elts.length === 0) {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
// Check if this looks like a function application that wasn't folded
|
|
45
|
+
// e.g., sub followed by arguments as separate expressions
|
|
46
|
+
if (node.elts.length >= 3) {
|
|
47
|
+
const first = node.elts[0];
|
|
48
|
+
// Check if first element is an identifier that could be a function
|
|
49
|
+
if (first && first.tag && first.elts && first.elts.length === 0) {
|
|
50
|
+
// This might be a function name followed by arguments
|
|
51
|
+
const funcName = first.tag;
|
|
52
|
+
// Check if this matches a lexicon function
|
|
53
|
+
if (lexicon && lexicon[funcName]) {
|
|
54
|
+
const arity = lexicon[funcName].arity || 0;
|
|
55
|
+
if (arity > 0 && node.elts.length === arity + 1) {
|
|
56
|
+
// Treat this as a function application
|
|
57
|
+
const args = node.elts.slice(1).map(elt => unparseNode(elt, lexicon, indent, opts)).join(" ");
|
|
58
|
+
return `${funcName} ${args}`;
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
}
|
|
62
|
+
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
// For single expression, return as is
|
|
65
|
+
if (node.elts.length === 1) {
|
|
66
|
+
return unparseNode(node.elts[0], lexicon, indent, opts);
|
|
67
|
+
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// For multiple expressions, put each on its own line
|
|
70
|
+
return node.elts.map(elt => unparseNode(elt, lexicon, indent, opts)).join("\n");
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
case "NUM":
|
|
73
|
+
return node.elts[0];
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
case "STR": {
|
|
76
|
+
// Escape quotes and backslashes in the string
|
|
77
|
+
const str = node.elts[0];
|
|
78
|
+
const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
79
|
+
return `"${escaped}"`;
|
|
80
|
+
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
case "BOOL":
|
|
83
|
+
return node.elts[0] ? "true" : "false";
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
case "NULL":
|
|
86
|
+
return "null";
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
case "IDENT":
|
|
89
|
+
return node.elts[0];
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
case "LIST": {
|
|
92
|
+
// Array literal [a, b, c]
|
|
93
|
+
if (!node.elts || node.elts.length === 0) {
|
|
94
|
+
return "[]";
|
|
95
|
+
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
97
|
+
if (opts.compact) {
|
|
98
|
+
// Compact mode: inline list
|
|
99
|
+
const items = node.elts.map(elt => unparseNode(elt, lexicon, indent, opts));
|
|
100
|
+
return "[" + items.join(", ") + "]";
|
|
101
|
+
} else {
|
|
102
|
+
// Pretty print with each element on a new line
|
|
103
|
+
const innerIndent = indent + opts.indentSize;
|
|
104
|
+
const indentStr = " ".repeat(innerIndent);
|
|
105
|
+
const items = node.elts.map(elt =>
|
|
106
|
+
indentStr + unparseNode(elt, lexicon, innerIndent, opts)
|
|
107
|
+
);
|
|
108
|
+
return "[\n" + items.join("\n") + "\n" + " ".repeat(indent) + "]";
|
|
110
109
|
}
|
|
110
|
+
}
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
case "RECORD": {
|
|
113
|
+
// Object literal {a: 1, b: 2}
|
|
114
|
+
if (!node.elts || node.elts.length === 0) {
|
|
115
|
+
return "{}";
|
|
116
|
+
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
118
|
+
if (opts.compact) {
|
|
119
|
+
// Compact mode: inline record
|
|
120
|
+
const bindings = node.elts.map(elt => unparseNode(elt, lexicon, indent, opts));
|
|
121
|
+
return "{" + bindings.join(", ") + "}";
|
|
122
|
+
} else {
|
|
123
|
+
// Pretty print with each binding on a new line
|
|
124
|
+
const innerIndent = indent + opts.indentSize;
|
|
125
|
+
const indentStr = " ".repeat(innerIndent);
|
|
126
|
+
const bindings = node.elts.map(elt =>
|
|
127
|
+
indentStr + unparseNode(elt, lexicon, innerIndent, opts)
|
|
128
|
+
);
|
|
129
|
+
return "{\n" + bindings.join("\n") + "\n" + " ".repeat(indent) + "}";
|
|
131
130
|
}
|
|
131
|
+
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
const value = unparseNode(node.elts[1], lexicon, indent, opts);
|
|
144
|
-
return `${key}: ${value}`;
|
|
133
|
+
case "BINDING": {
|
|
134
|
+
// Key-value pair in a record
|
|
135
|
+
if (node.elts && node.elts.length >= 2) {
|
|
136
|
+
// If the key is a string node, unparse it without quotes for object keys
|
|
137
|
+
let key;
|
|
138
|
+
if (node.elts[0] && node.elts[0].tag === "STR") {
|
|
139
|
+
key = node.elts[0].elts[0]; // Get the raw string without quotes
|
|
140
|
+
} else {
|
|
141
|
+
key = unparseNode(node.elts[0], lexicon, indent);
|
|
145
142
|
}
|
|
146
|
-
|
|
143
|
+
const value = unparseNode(node.elts[1], lexicon, indent, opts);
|
|
144
|
+
return `${key}: ${value}`;
|
|
147
145
|
}
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
149
|
+
case "PAREN":
|
|
150
|
+
// Parenthesized expression
|
|
151
|
+
if (node.elts && node.elts.length > 0) {
|
|
152
|
+
return "(" + unparseNode(node.elts[0], lexicon, indent, opts) + ")";
|
|
153
|
+
}
|
|
154
|
+
return "()";
|
|
155
|
+
|
|
156
|
+
case "APPLY":
|
|
157
|
+
// Function application
|
|
158
|
+
if (node.elts && node.elts.length >= 2) {
|
|
159
|
+
const func = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
160
|
+
const args = unparseNode(node.elts[1], lexicon, indent, opts);
|
|
161
|
+
return func + " " + args;
|
|
162
|
+
}
|
|
163
|
+
return "";
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
case "LAMBDA":
|
|
166
|
+
// Lambda function
|
|
167
|
+
if (node.elts && node.elts.length >= 3) {
|
|
168
|
+
const params = node.elts[1];
|
|
169
|
+
const body = node.elts[2];
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
171
|
+
// Extract parameter names
|
|
172
|
+
let paramStr = "";
|
|
173
|
+
if (params && params.elts) {
|
|
174
|
+
paramStr = params.elts.map(p => unparseNode(p, lexicon, indent, opts)).join(" ");
|
|
175
|
+
}
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// Unparse body
|
|
178
|
+
const bodyStr = unparseNode(body, lexicon, indent, opts);
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
180
|
+
if (paramStr) {
|
|
181
|
+
return `\\${paramStr} . ${bodyStr}`;
|
|
182
|
+
} else {
|
|
183
|
+
return `\\. ${bodyStr}`;
|
|
185
184
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
case "LET":
|
|
189
|
-
// Let binding
|
|
190
|
-
if (node.elts && node.elts.length >= 2) {
|
|
191
|
-
const bindings = node.elts[0];
|
|
192
|
-
const body = node.elts[1];
|
|
193
|
-
|
|
194
|
-
let bindingStr = "";
|
|
195
|
-
if (bindings && bindings.elts) {
|
|
196
|
-
bindingStr = bindings.elts.map(b => {
|
|
197
|
-
if (b.elts && b.elts.length >= 2) {
|
|
198
|
-
const name = unparseNode(b.elts[0], lexicon, indent, opts);
|
|
199
|
-
const value = unparseNode(b.elts[1], lexicon, indent, opts);
|
|
200
|
-
return `${name} = ${value}`;
|
|
201
|
-
}
|
|
202
|
-
return "";
|
|
203
|
-
}).filter(s => s).join(", ");
|
|
204
|
-
}
|
|
185
|
+
}
|
|
186
|
+
return "";
|
|
205
187
|
|
|
206
|
-
|
|
207
|
-
|
|
188
|
+
case "LET":
|
|
189
|
+
// Let binding
|
|
190
|
+
if (node.elts && node.elts.length >= 2) {
|
|
191
|
+
const bindings = node.elts[0];
|
|
192
|
+
const body = node.elts[1];
|
|
193
|
+
|
|
194
|
+
let bindingStr = "";
|
|
195
|
+
if (bindings && bindings.elts) {
|
|
196
|
+
bindingStr = bindings.elts.map(b => {
|
|
197
|
+
if (b.elts && b.elts.length >= 2) {
|
|
198
|
+
const name = unparseNode(b.elts[0], lexicon, indent, opts);
|
|
199
|
+
const value = unparseNode(b.elts[1], lexicon, indent, opts);
|
|
200
|
+
return `${name} = ${value}`;
|
|
201
|
+
}
|
|
202
|
+
return "";
|
|
203
|
+
}).filter(s => s).join(", ");
|
|
208
204
|
}
|
|
209
|
-
return "";
|
|
210
205
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const thenExpr = unparseNode(node.elts[1], lexicon, indent, opts);
|
|
206
|
+
const bodyStr = unparseNode(body, lexicon, indent, opts);
|
|
207
|
+
return `let ${bindingStr} in ${bodyStr}`;
|
|
208
|
+
}
|
|
209
|
+
return "";
|
|
216
210
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return "";
|
|
211
|
+
case "IF":
|
|
212
|
+
// If-then-else
|
|
213
|
+
if (node.elts && node.elts.length >= 2) {
|
|
214
|
+
const cond = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
215
|
+
const innerIndent = indent + opts.indentSize;
|
|
216
|
+
const indentStr = " ".repeat(innerIndent);
|
|
217
|
+
const thenExpr = unparseNode(node.elts[1], lexicon, innerIndent, opts);
|
|
225
218
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return `case ${expr} of ${cases.join(" | ")}`;
|
|
219
|
+
if (node.elts.length >= 3) {
|
|
220
|
+
const elseExpr = unparseNode(node.elts[2], lexicon, innerIndent, opts);
|
|
221
|
+
return `if ${cond} then\n${indentStr}${thenExpr}\nelse\n${indentStr}${elseExpr}`;
|
|
222
|
+
} else {
|
|
223
|
+
return `if ${cond} then\n${indentStr}${thenExpr}`;
|
|
232
224
|
}
|
|
233
|
-
|
|
225
|
+
}
|
|
226
|
+
return "";
|
|
234
227
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
228
|
+
case "CASE":
|
|
229
|
+
// Case expression
|
|
230
|
+
if (node.elts && node.elts.length > 0) {
|
|
231
|
+
const expr = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
232
|
+
const innerIndent = indent + opts.indentSize;
|
|
233
|
+
const indentStr = " ".repeat(innerIndent);
|
|
234
|
+
const endIndentStr = " ".repeat(indent);
|
|
235
|
+
|
|
236
|
+
// Process each case branch with proper indentation
|
|
237
|
+
const cases = node.elts.slice(1).map(c => {
|
|
238
|
+
// Pass the inner indent to OF nodes but don't add extra indentation here
|
|
239
|
+
// The OF node will handle its own formatting
|
|
240
|
+
const caseStr = unparseNode(c, lexicon, indent, opts);
|
|
241
|
+
return caseStr;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return `case ${expr} of\n${cases.join("\n")}\n${endIndentStr}end`;
|
|
245
|
+
}
|
|
246
|
+
return "";
|
|
247
|
+
|
|
248
|
+
case "OF":
|
|
249
|
+
// Case branch
|
|
250
|
+
if (node.elts && node.elts.length >= 2) {
|
|
251
|
+
const indentStr = " ".repeat(indent + opts.indentSize);
|
|
252
|
+
const pattern = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
253
|
+
|
|
254
|
+
// Check if the expression is a CASE node for proper nested formatting
|
|
255
|
+
const exprNode = node.elts[1];
|
|
256
|
+
let expr;
|
|
257
|
+
if (exprNode && exprNode.tag === "CASE") {
|
|
258
|
+
// For nested case, don't add extra indent to the case keyword itself
|
|
259
|
+
expr = unparseNode(exprNode, lexicon, indent + opts.indentSize, opts);
|
|
260
|
+
} else {
|
|
261
|
+
expr = unparseNode(exprNode, lexicon, indent, opts);
|
|
241
262
|
}
|
|
242
|
-
|
|
263
|
+
|
|
264
|
+
return `${indentStr}${pattern}: ${expr}`;
|
|
265
|
+
}
|
|
266
|
+
return "";
|
|
243
267
|
|
|
244
268
|
// Unary operator - negative
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
269
|
+
case "NEG":
|
|
270
|
+
if (node.elts && node.elts.length >= 1) {
|
|
271
|
+
const expr = unparseNode(node.elts[0], lexicon, indent, opts);
|
|
272
|
+
return `-${expr}`;
|
|
273
|
+
}
|
|
274
|
+
return "";
|
|
251
275
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
return `/* ERROR: ${firstElt} */`;
|
|
276
|
+
case "ERROR":
|
|
277
|
+
// Error nodes - include as comments
|
|
278
|
+
if (node.elts && node.elts.length > 0) {
|
|
279
|
+
// The first element might be a node reference or a string
|
|
280
|
+
const firstElt = node.elts[0];
|
|
281
|
+
if (typeof firstElt === "object" && firstElt.elts) {
|
|
282
|
+
// It's a node, unparse it
|
|
283
|
+
return `/* ERROR: ${unparseNode(firstElt, lexicon, indent, opts)} */`;
|
|
262
284
|
}
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (sourceName) {
|
|
279
|
-
// This is a known lexicon function - unparse in prefix notation
|
|
280
|
-
if (node.elts && node.elts.length > 0) {
|
|
281
|
-
const args = node.elts.map(elt => unparseNode(elt, lexicon, indent, opts)).join(" ");
|
|
282
|
-
return `${sourceName} ${args}`;
|
|
285
|
+
return `/* ERROR: ${firstElt} */`;
|
|
286
|
+
}
|
|
287
|
+
return "/* ERROR */";
|
|
288
|
+
|
|
289
|
+
default: {
|
|
290
|
+
// Check if this is a lexicon-defined function
|
|
291
|
+
// First, find the source name for this tag in the lexicon
|
|
292
|
+
let sourceName = null;
|
|
293
|
+
if (lexicon) {
|
|
294
|
+
for (const [key, value] of Object.entries(lexicon)) {
|
|
295
|
+
if (value && value.name === node.tag) {
|
|
296
|
+
sourceName = key;
|
|
297
|
+
break;
|
|
283
298
|
}
|
|
284
|
-
return sourceName;
|
|
285
299
|
}
|
|
300
|
+
}
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
302
|
+
if (sourceName) {
|
|
303
|
+
// This is a known lexicon function - unparse in prefix notation
|
|
304
|
+
if (node.elts && node.elts.length > 0) {
|
|
305
|
+
const args = node.elts.map(elt => unparseNode(elt, lexicon, indent, opts)).join(" ");
|
|
306
|
+
return `${sourceName} ${args}`;
|
|
291
307
|
}
|
|
308
|
+
return sourceName;
|
|
309
|
+
}
|
|
292
310
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
311
|
+
// Handle identifiers that aren't in the lexicon (like lowercase "sub")
|
|
312
|
+
if (node.elts && node.elts.length === 0) {
|
|
313
|
+
// This is likely an identifier
|
|
314
|
+
return node.tag;
|
|
296
315
|
}
|
|
316
|
+
|
|
317
|
+
// Fallback for unknown nodes
|
|
318
|
+
console.warn(`Unknown node tag: ${node.tag}`);
|
|
319
|
+
return `/* ${node.tag} */`;
|
|
320
|
+
}
|
|
297
321
|
}
|
|
298
322
|
}
|
|
299
323
|
|
|
@@ -343,34 +367,34 @@ function reconstructNode(pool, nodeId) {
|
|
|
343
367
|
|
|
344
368
|
// Handle different node types
|
|
345
369
|
switch (node.tag) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
case "NUM":
|
|
371
|
+
case "STR":
|
|
372
|
+
case "IDENT":
|
|
373
|
+
case "BOOL":
|
|
374
|
+
// These nodes have primitive values in elts[0]
|
|
375
|
+
result.elts = [node.elts[0]];
|
|
376
|
+
break;
|
|
377
|
+
|
|
378
|
+
case "NULL":
|
|
379
|
+
// NULL nodes have no elements
|
|
380
|
+
result.elts = [];
|
|
381
|
+
break;
|
|
382
|
+
|
|
383
|
+
default:
|
|
384
|
+
// For all other nodes, recursively reconstruct child nodes
|
|
385
|
+
if (node.elts && Array.isArray(node.elts)) {
|
|
386
|
+
result.elts = node.elts.map(eltId => {
|
|
387
|
+
// Check if this is a node ID (number or string number)
|
|
388
|
+
if (typeof eltId === "number" || (typeof eltId === "string" && /^\d+$/.test(eltId))) {
|
|
389
|
+
// This is a reference to another node in the pool
|
|
390
|
+
return reconstructNode(pool, eltId);
|
|
391
|
+
} else {
|
|
392
|
+
// This is a primitive value
|
|
393
|
+
return eltId;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
374
398
|
}
|
|
375
399
|
|
|
376
400
|
return result;
|
package/src/unparse.spec.js
CHANGED
|
@@ -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("
|
|
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
|
|
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
|
|
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("
|
|
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
|
});
|