@bablr/agast-helpers 0.2.0 → 0.3.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/lib/builders.js +53 -26
- package/lib/print.js +56 -9
- package/lib/shorthand.js +4 -2
- package/lib/stream.js +148 -69
- package/lib/symbols.js +9 -0
- package/lib/template.js +2 -2
- package/lib/tree.js +164 -92
- package/package.json +2 -1
package/lib/builders.js
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
|
+
import * as sym from './symbols.js';
|
|
2
|
+
|
|
1
3
|
const { freeze } = Object;
|
|
2
4
|
|
|
5
|
+
const isObject = (val) => val !== null && typeof value !== 'object';
|
|
6
|
+
|
|
7
|
+
export const buildEmbeddedExpression = (expr) => {
|
|
8
|
+
if (!isObject(expr)) return expr;
|
|
9
|
+
return freeze({ type: 'EmbeddedExpression', value: expr });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const buildEmbeddedTag = (tag) => {
|
|
13
|
+
return freeze({ type: 'EmbeddedTag', value: tag });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const buildEffect = (value) => {
|
|
17
|
+
return freeze({ type: 'Effect', value });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const buildWriteEffect = (text, options = {}) => {
|
|
21
|
+
return buildEffect(
|
|
22
|
+
freeze({ verb: 'write', value: freeze({ text, options: buildEmbeddedExpression(options) }) }),
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const buildAnsiPushEffect = (spans = '') => {
|
|
27
|
+
return buildEffect(
|
|
28
|
+
freeze({ verb: 'ansi-push', value: { spans: spans === '' ? [] : spans.split(' ') } }),
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const buildAnsiPopEffect = () => {
|
|
33
|
+
return buildEffect(freeze({ verb: 'ansi-pop', value: undefined }));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const buildTokenGroup = (tokens) => {
|
|
37
|
+
return freeze({ type: 'TokenGroup', value: tokens });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const buildCall = (verb, ...args) => {
|
|
41
|
+
return { verb, arguments: args };
|
|
42
|
+
};
|
|
43
|
+
|
|
3
44
|
export const buildBeginningOfStreamToken = () => {
|
|
4
45
|
return freeze({ type: Symbol.for('@bablr/beginning-of-stream'), value: undefined });
|
|
5
46
|
};
|
|
@@ -20,8 +61,8 @@ export const buildShift = () => {
|
|
|
20
61
|
return freeze({ type: 'Shift', value: undefined });
|
|
21
62
|
};
|
|
22
63
|
|
|
23
|
-
export const
|
|
24
|
-
return freeze({ type: '
|
|
64
|
+
export const buildEmbeddedNode = (node) => {
|
|
65
|
+
return freeze({ type: 'EmbeddedNode', value: node });
|
|
25
66
|
};
|
|
26
67
|
|
|
27
68
|
export const buildDoctypeTag = (attributes) => {
|
|
@@ -31,13 +72,7 @@ export const buildDoctypeTag = (attributes) => {
|
|
|
31
72
|
});
|
|
32
73
|
};
|
|
33
74
|
|
|
34
|
-
export const buildNodeOpenTag = (
|
|
35
|
-
flags = {},
|
|
36
|
-
language = null,
|
|
37
|
-
type = null,
|
|
38
|
-
intrinsicValue = null,
|
|
39
|
-
attributes = {},
|
|
40
|
-
) => {
|
|
75
|
+
export const buildNodeOpenTag = (flags = {}, language = null, type = null, attributes = {}) => {
|
|
41
76
|
let { token, trivia, escape, expression, intrinsic } = flags;
|
|
42
77
|
|
|
43
78
|
token = !!token;
|
|
@@ -52,7 +87,6 @@ export const buildNodeOpenTag = (
|
|
|
52
87
|
flags: freeze({ token, trivia, escape, intrinsic, expression }),
|
|
53
88
|
language,
|
|
54
89
|
type,
|
|
55
|
-
intrinsicValue,
|
|
56
90
|
attributes,
|
|
57
91
|
}),
|
|
58
92
|
});
|
|
@@ -223,20 +257,13 @@ export const buildTriviaNode = (language, type, children = [], properties = {},
|
|
|
223
257
|
attributes: freeze(attributes),
|
|
224
258
|
});
|
|
225
259
|
|
|
226
|
-
export const
|
|
227
|
-
return freeze({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return buildEffect(
|
|
236
|
-
freeze({ verb: 'ansi-push', value: { spans: spans === '' ? [] : spans.split(' ') } }),
|
|
237
|
-
);
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
export const buildAnsiPopEffect = () => {
|
|
241
|
-
return buildEffect(freeze({ verb: 'ansi-pop', value: undefined }));
|
|
260
|
+
export const buildNullNode = () => {
|
|
261
|
+
return freeze({
|
|
262
|
+
flags: nodeFlags,
|
|
263
|
+
language: null,
|
|
264
|
+
type: sym.null,
|
|
265
|
+
children: freeze([buildNull()]),
|
|
266
|
+
properties: freeze({}),
|
|
267
|
+
attributes: freeze({}),
|
|
268
|
+
});
|
|
242
269
|
};
|
package/lib/print.js
CHANGED
|
@@ -8,6 +8,22 @@ const isFunction = (val) => typeof val === 'function';
|
|
|
8
8
|
const when = (condition, value) =>
|
|
9
9
|
condition ? (isFunction(value) ? value() : value) : { *[Symbol.iterator]() {} };
|
|
10
10
|
|
|
11
|
+
export const printCall = (call) => {
|
|
12
|
+
const { verb, arguments: args } = call;
|
|
13
|
+
return `${verb}${printTuple(args)}`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const printArray = (arr) => `[${arr.map((v) => printExpression(v)).join(', ')}]`;
|
|
17
|
+
|
|
18
|
+
export const printTuple = (tup) => `(${tup.map((v) => printExpression(v)).join(', ')})`;
|
|
19
|
+
|
|
20
|
+
export const printObject = (obj) => {
|
|
21
|
+
const entries = Object.entries(obj);
|
|
22
|
+
return entries.length
|
|
23
|
+
? `{ ${entries.map(([k, v]) => `${k}: ${printExpression(v)}`).join(', ')} }`
|
|
24
|
+
: '{}';
|
|
25
|
+
};
|
|
26
|
+
|
|
11
27
|
export const printExpression = (expr) => {
|
|
12
28
|
if (isString(expr)) {
|
|
13
29
|
return printString(expr);
|
|
@@ -23,14 +39,33 @@ export const printExpression = (expr) => {
|
|
|
23
39
|
throw new Error();
|
|
24
40
|
}
|
|
25
41
|
} else if (isArray(expr)) {
|
|
26
|
-
return
|
|
42
|
+
return printArray(expr);
|
|
27
43
|
} else if (typeof expr === 'object') {
|
|
28
|
-
return
|
|
44
|
+
return printEmbedded(expr);
|
|
29
45
|
} else {
|
|
30
46
|
throw new Error();
|
|
31
47
|
}
|
|
32
48
|
};
|
|
33
49
|
|
|
50
|
+
export const printEmbedded = (value) => {
|
|
51
|
+
switch (value.type) {
|
|
52
|
+
case 'EmbeddedTag':
|
|
53
|
+
return printTerminal(value.value);
|
|
54
|
+
|
|
55
|
+
case 'EmbeddedExpression': {
|
|
56
|
+
return printObject(value.value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case 'EmbeddedNode': {
|
|
60
|
+
throw new Error('not implemented');
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
default:
|
|
65
|
+
throw new Error();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
34
69
|
export const printAttributes = (attributes) => {
|
|
35
70
|
return Object.entries(attributes)
|
|
36
71
|
.map(([k, v]) => (v === true ? k : v === false ? `!${k}` : `${k}=${printExpression(v)}`))
|
|
@@ -46,9 +81,10 @@ export const printLanguage = (language) => {
|
|
|
46
81
|
};
|
|
47
82
|
|
|
48
83
|
export const printTagPath = (language, type) => {
|
|
49
|
-
return [
|
|
50
|
-
|
|
51
|
-
|
|
84
|
+
return [
|
|
85
|
+
...when(type && language?.length, () => [printLanguage(language)]),
|
|
86
|
+
...when(type, [type]),
|
|
87
|
+
].join(':');
|
|
52
88
|
};
|
|
53
89
|
|
|
54
90
|
const escapeReplacer = (esc) => {
|
|
@@ -121,7 +157,8 @@ export const printDoctypeTag = (terminal) => {
|
|
|
121
157
|
|
|
122
158
|
let { doctype, version, attributes } = terminal.value;
|
|
123
159
|
|
|
124
|
-
attributes =
|
|
160
|
+
attributes =
|
|
161
|
+
attributes && Object.values(attributes).length ? ` ${printAttributes(attributes)}` : '';
|
|
125
162
|
|
|
126
163
|
return `<!${version}:${doctype}${attributes}>`;
|
|
127
164
|
};
|
|
@@ -147,17 +184,27 @@ export const printFlags = (flags) => {
|
|
|
147
184
|
export const printOpenNodeTag = (terminal) => {
|
|
148
185
|
if (terminal?.type !== 'OpenNodeTag') throw new Error();
|
|
149
186
|
|
|
150
|
-
const { flags, language: tagLanguage, type,
|
|
187
|
+
const { flags, language: tagLanguage, type, attributes } = terminal.value;
|
|
188
|
+
|
|
189
|
+
const printedAttributes = attributes && printAttributes(attributes);
|
|
190
|
+
const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
|
|
191
|
+
|
|
192
|
+
return `<${printFlags(flags)}${printTagPath(tagLanguage, type)}${attributesFrag}>`;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const printSelfClosingNodeTag = (terminal, intrinsicValue) => {
|
|
196
|
+
if (terminal?.type !== 'OpenNodeTag') throw new Error();
|
|
197
|
+
|
|
198
|
+
const { flags, language: tagLanguage, type, attributes } = terminal.value;
|
|
151
199
|
|
|
152
200
|
const printedAttributes = attributes && printAttributes(attributes);
|
|
153
201
|
const attributesFrag = printedAttributes ? ` ${printedAttributes}` : '';
|
|
154
202
|
const intrinsicFrag = intrinsicValue ? ` ${printString(intrinsicValue)}` : '';
|
|
155
|
-
const selfCloser = intrinsicValue ? ' /' : '';
|
|
156
203
|
|
|
157
204
|
return `<${printFlags(flags)}${printTagPath(
|
|
158
205
|
tagLanguage,
|
|
159
206
|
type,
|
|
160
|
-
)}${intrinsicFrag}${attributesFrag}
|
|
207
|
+
)}${intrinsicFrag}${attributesFrag} />`;
|
|
161
208
|
};
|
|
162
209
|
|
|
163
210
|
export const printCloseNodeTag = (terminal) => {
|
package/lib/shorthand.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildReference,
|
|
3
3
|
buildGap,
|
|
4
|
-
|
|
4
|
+
buildEmbeddedNode,
|
|
5
5
|
buildNodeOpenTag,
|
|
6
6
|
buildNodeCloseTag,
|
|
7
7
|
buildLiteral,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
buildSyntacticEscapeNode,
|
|
13
13
|
buildSyntacticTriviaNode,
|
|
14
14
|
buildTriviaNode,
|
|
15
|
+
buildNullNode,
|
|
15
16
|
} from './builders.js';
|
|
16
17
|
|
|
17
18
|
export * from './builders.js';
|
|
@@ -43,7 +44,7 @@ export const ref = (path) => {
|
|
|
43
44
|
export const lit = (str) => buildLiteral(stripArray(str));
|
|
44
45
|
|
|
45
46
|
export const gap = buildGap;
|
|
46
|
-
export const embedded =
|
|
47
|
+
export const embedded = buildEmbeddedNode;
|
|
47
48
|
export const nodeOpen = buildNodeOpenTag;
|
|
48
49
|
export const nodeClose = buildNodeCloseTag;
|
|
49
50
|
export const node = buildNode;
|
|
@@ -53,3 +54,4 @@ export const e_node = buildEscapeNode;
|
|
|
53
54
|
export const s_e_node = buildSyntacticEscapeNode;
|
|
54
55
|
export const s_t_node = buildSyntacticTriviaNode;
|
|
55
56
|
export const t_node = buildTriviaNode;
|
|
57
|
+
export const null_node = buildNullNode;
|
package/lib/stream.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Coroutine } from '@bablr/coroutine';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import emptyStack from '@iter-tools/imm-stack';
|
|
3
|
+
import { printSelfClosingNodeTag, printTerminal } from './print.js';
|
|
4
|
+
import { buildWriteEffect, buildTokenGroup } from './builders.js';
|
|
4
5
|
export * from './print.js';
|
|
5
6
|
|
|
6
7
|
export const getStreamIterator = (obj) => {
|
|
@@ -83,6 +84,12 @@ export class AsyncGenerator {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
export const isIntrinsicToken = (terminal) => {
|
|
88
|
+
return (
|
|
89
|
+
terminal.type === 'OpenNodeTag' && terminal.value.flags.intrinsic && terminal.value.flags.token
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
86
93
|
export class StreamGenerator {
|
|
87
94
|
constructor(embeddedGenerator) {
|
|
88
95
|
this.generator = embeddedGenerator;
|
|
@@ -137,45 +144,6 @@ export const maybeWait = (maybePromise, callback) => {
|
|
|
137
144
|
}
|
|
138
145
|
};
|
|
139
146
|
|
|
140
|
-
function* __generateCSTMLStrategy(terminals) {
|
|
141
|
-
if (!terminals) {
|
|
142
|
-
yield buildWriteEffect('<//>');
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let prevTerminal = null;
|
|
147
|
-
|
|
148
|
-
const co = new Coroutine(getStreamIterator(terminals));
|
|
149
|
-
|
|
150
|
-
for (;;) {
|
|
151
|
-
co.advance();
|
|
152
|
-
|
|
153
|
-
if (co.current instanceof Promise) {
|
|
154
|
-
co.current = yield co.current;
|
|
155
|
-
}
|
|
156
|
-
if (co.done) break;
|
|
157
|
-
|
|
158
|
-
const terminal = co.value;
|
|
159
|
-
|
|
160
|
-
if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
|
|
161
|
-
yield buildWriteEffect(' ');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (terminal.type !== 'Effect') {
|
|
165
|
-
buildWriteEffect(printTerminal(terminal));
|
|
166
|
-
|
|
167
|
-
prevTerminal = terminal;
|
|
168
|
-
} else {
|
|
169
|
-
yield terminal;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
yield buildWriteEffect('\n');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export const generateCSTMLStrategy = (terminals) =>
|
|
177
|
-
new StreamIterable(__generateCSTMLStrategy(terminals));
|
|
178
|
-
|
|
179
147
|
function* __generateStandardOutput(terminals) {
|
|
180
148
|
const co = new Coroutine(getStreamIterator(terminals));
|
|
181
149
|
|
|
@@ -278,17 +246,138 @@ export const stringFromStream = (stream) => {
|
|
|
278
246
|
return str;
|
|
279
247
|
};
|
|
280
248
|
|
|
281
|
-
function*
|
|
282
|
-
|
|
249
|
+
function* __generateCSTMLStrategy(terminals, options) {
|
|
250
|
+
let { emitEffects = false, inline: inlineOption = true } = options;
|
|
251
|
+
|
|
252
|
+
if (emitEffects) {
|
|
253
|
+
throw new Error('You must use generatePrettyCSTML with emitEffects');
|
|
254
|
+
}
|
|
283
255
|
|
|
284
256
|
if (!terminals) {
|
|
285
257
|
yield buildWriteEffect('<//>');
|
|
286
258
|
return;
|
|
287
259
|
}
|
|
288
260
|
|
|
261
|
+
let prevTerminal = null;
|
|
262
|
+
|
|
263
|
+
const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
|
|
264
|
+
|
|
265
|
+
for (;;) {
|
|
266
|
+
co.advance();
|
|
267
|
+
|
|
268
|
+
if (co.current instanceof Promise) {
|
|
269
|
+
co.current = yield co.current;
|
|
270
|
+
}
|
|
271
|
+
if (co.done) break;
|
|
272
|
+
|
|
273
|
+
const terminal = co.value;
|
|
274
|
+
|
|
275
|
+
if (terminal.type === 'Reference' && prevTerminal.type === 'Null') {
|
|
276
|
+
yield buildWriteEffect(' ');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (terminal.type === 'Effect') {
|
|
280
|
+
const effect = terminal.value;
|
|
281
|
+
if (emitEffects && effect.verb === 'write') {
|
|
282
|
+
yield buildWriteEffect(effect.value.text, effect.value.options);
|
|
283
|
+
}
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (terminal.type === 'TokenGroup') {
|
|
288
|
+
const intrinsicValue = getCooked(terminal.value.slice(1, -1));
|
|
289
|
+
yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
|
|
290
|
+
} else {
|
|
291
|
+
yield buildWriteEffect(printTerminal(terminal));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
prevTerminal = terminal;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
yield buildWriteEffect('\n');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const prettyGroupTokens = (terminals) => new StreamIterable(__prettyGroupTokens(terminals));
|
|
301
|
+
|
|
302
|
+
function* __prettyGroupTokens(terminals) {
|
|
303
|
+
let states = emptyStack.push({ holding: [], broken: false, open: null });
|
|
304
|
+
let state = states.value;
|
|
305
|
+
|
|
289
306
|
const co = new Coroutine(getStreamIterator(terminals));
|
|
307
|
+
|
|
308
|
+
for (;;) {
|
|
309
|
+
co.advance();
|
|
310
|
+
|
|
311
|
+
if (co.done) break;
|
|
312
|
+
|
|
313
|
+
if (co.current instanceof Promise) {
|
|
314
|
+
co.current = yield co.current;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const terminal = co.value;
|
|
318
|
+
const isOpenClose =
|
|
319
|
+
terminal.type === 'CloseNodeTag' || (terminal.type === 'OpenNodeTag' && terminal.value.type);
|
|
320
|
+
|
|
321
|
+
if (
|
|
322
|
+
(terminal.type === 'Effect' && terminal.value.verb === 'write') ||
|
|
323
|
+
['Reference', 'DoctypeTag', 'Gap', 'Null'].includes(terminal.type) ||
|
|
324
|
+
(terminal.type === 'OpenNodeTag' && !terminal.value.type) ||
|
|
325
|
+
(state.open &&
|
|
326
|
+
!isIntrinsicToken(state.open) &&
|
|
327
|
+
(terminal.type === 'Literal' ||
|
|
328
|
+
(terminal.type === 'OpenNodeTag' && terminal.value.flags.escape)))
|
|
329
|
+
) {
|
|
330
|
+
state.broken = true;
|
|
331
|
+
|
|
332
|
+
if (state.holding.length) {
|
|
333
|
+
yield* state.holding;
|
|
334
|
+
state.holding = [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!isOpenClose) {
|
|
338
|
+
yield terminal;
|
|
339
|
+
}
|
|
340
|
+
} else if (!isOpenClose) {
|
|
341
|
+
state.holding.push(terminal);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (terminal.type === 'CloseNodeTag') {
|
|
345
|
+
if (!state.broken && (isIntrinsicToken(state.open) || state.holding.length === 1)) {
|
|
346
|
+
state.holding.push(terminal);
|
|
347
|
+
yield buildTokenGroup(state.holding);
|
|
348
|
+
} else {
|
|
349
|
+
if (state.holding.length) {
|
|
350
|
+
yield* state.holding;
|
|
351
|
+
}
|
|
352
|
+
yield terminal;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
states = states.pop();
|
|
356
|
+
state = states.value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (terminal.type === 'OpenNodeTag' && terminal.value.type) {
|
|
360
|
+
states = states.push({ holding: [terminal], broken: false, open: terminal });
|
|
361
|
+
state = states.value;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export const generateCSTMLStrategy = (terminals, options = {}) =>
|
|
367
|
+
new StreamIterable(__generateCSTMLStrategy(terminals, options));
|
|
368
|
+
|
|
369
|
+
function* __generatePrettyCSTMLStrategy(terminals, options) {
|
|
370
|
+
let { indent = ' ', emitEffects = false, inline: inlineOption = true } = options;
|
|
371
|
+
|
|
372
|
+
if (!terminals) {
|
|
373
|
+
yield buildWriteEffect('<//>');
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const co = new Coroutine(getStreamIterator(prettyGroupTokens(terminals)));
|
|
290
378
|
let indentLevel = 0;
|
|
291
379
|
let first = true;
|
|
380
|
+
let inline = false;
|
|
292
381
|
|
|
293
382
|
for (;;) {
|
|
294
383
|
co.advance();
|
|
@@ -306,18 +395,16 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
|
|
|
306
395
|
if (emitEffects && effect.verb === 'write') {
|
|
307
396
|
yield buildWriteEffect((first ? '' : '\n') + effect.value.text, effect.value.options);
|
|
308
397
|
|
|
398
|
+
inline = false;
|
|
309
399
|
first = false;
|
|
310
400
|
}
|
|
311
401
|
continue;
|
|
312
402
|
}
|
|
313
403
|
|
|
314
|
-
|
|
404
|
+
inline =
|
|
315
405
|
inlineOption &&
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
(terminal.type === 'OpenNodeTag' &&
|
|
319
|
-
terminal.value.intrinsicValue &&
|
|
320
|
-
terminal.value.flags.intrinsic));
|
|
406
|
+
inline &&
|
|
407
|
+
(terminal.type === 'Null' || terminal.type === 'Gap' || terminal.type === 'TokenGroup');
|
|
321
408
|
|
|
322
409
|
if (!first && !inline) {
|
|
323
410
|
yield buildWriteEffect('\n');
|
|
@@ -336,12 +423,19 @@ function* __generatePrettyCSTMLStrategy(terminals, options) {
|
|
|
336
423
|
} else {
|
|
337
424
|
yield buildWriteEffect(' ');
|
|
338
425
|
}
|
|
339
|
-
yield buildWriteEffect(printTerminal(terminal));
|
|
340
426
|
|
|
341
|
-
if (
|
|
342
|
-
terminal.
|
|
343
|
-
(
|
|
344
|
-
|
|
427
|
+
if (terminal.type === 'TokenGroup') {
|
|
428
|
+
const intrinsicValue = getCooked(terminal.value.slice(1, -1));
|
|
429
|
+
yield buildWriteEffect(printSelfClosingNodeTag(terminal.value[0], intrinsicValue));
|
|
430
|
+
} else {
|
|
431
|
+
yield buildWriteEffect(printTerminal(terminal));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (terminal.type === 'Reference') {
|
|
435
|
+
inline = true;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (terminal.type === 'OpenNodeTag') {
|
|
345
439
|
indentLevel++;
|
|
346
440
|
}
|
|
347
441
|
|
|
@@ -408,8 +502,6 @@ export const printSource = (terminals) => {
|
|
|
408
502
|
for (const terminal of terminals) {
|
|
409
503
|
if (terminal.type === 'Literal') {
|
|
410
504
|
printed += terminal.value;
|
|
411
|
-
} else if (terminal.type === 'OpenNodeTag' && terminal.value.intrinsicValue) {
|
|
412
|
-
printed += terminal.value.intrinsicValue;
|
|
413
505
|
}
|
|
414
506
|
}
|
|
415
507
|
|
|
@@ -420,21 +512,8 @@ export function* generateSourceTextFor(terminals) {
|
|
|
420
512
|
for (const terminal of terminals) {
|
|
421
513
|
if (terminal.type === 'Literal') {
|
|
422
514
|
yield* terminal.value;
|
|
423
|
-
} else if (terminal.type === 'OpenNodeTag' && terminal.value.intrinsicValue) {
|
|
424
|
-
yield* terminal.value.intrinsicValue;
|
|
425
515
|
}
|
|
426
516
|
}
|
|
427
517
|
}
|
|
428
518
|
|
|
429
519
|
export const sourceTextFor = printSource;
|
|
430
|
-
|
|
431
|
-
export const startsDocument = (terminal) => {
|
|
432
|
-
const { type, value } = terminal;
|
|
433
|
-
if ((type === 'OpenNodeTag' && !value.type) || type === 'DoctypeTag') {
|
|
434
|
-
return true;
|
|
435
|
-
} else if (type === 'OpenNodeTag') {
|
|
436
|
-
const { flags } = value;
|
|
437
|
-
|
|
438
|
-
return flags.trivia || flags.escape;
|
|
439
|
-
}
|
|
440
|
-
};
|
package/lib/symbols.js
ADDED
package/lib/template.js
CHANGED
|
@@ -28,10 +28,10 @@ export const interpolateArrayChildren = (value, ref, sep) => {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const validateTerminal = (term) => {
|
|
31
|
-
if (!term || (term.type !== 'Literal' && term.type !== '
|
|
31
|
+
if (!term || (term.type !== 'Literal' && term.type !== 'EmbeddedNode')) {
|
|
32
32
|
throw new Error('Invalid terminal');
|
|
33
33
|
}
|
|
34
|
-
if (term.type === '
|
|
34
|
+
if (term.type === 'EmbeddedNode' && !term.value.flags.escape) {
|
|
35
35
|
throw new Error();
|
|
36
36
|
}
|
|
37
37
|
};
|
package/lib/tree.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import emptyStack from '@iter-tools/imm-stack';
|
|
2
2
|
import { Coroutine } from '@bablr/coroutine';
|
|
3
|
-
import {
|
|
4
|
-
buildNodeCloseTag,
|
|
5
|
-
buildNodeOpenTag,
|
|
6
|
-
buildNull,
|
|
7
|
-
buildEmbedded,
|
|
8
|
-
nodeFlags,
|
|
9
|
-
buildDoctypeTag,
|
|
10
|
-
} from './builders.js';
|
|
3
|
+
import { buildNull, nodeFlags, buildDoctypeTag, buildEmbeddedNode } from './builders.js';
|
|
11
4
|
import {
|
|
12
5
|
printPrettyCSTML as printPrettyCSTMLFromStream,
|
|
13
6
|
printCSTML as printCSTMLFromStream,
|
|
14
7
|
getStreamIterator,
|
|
15
8
|
StreamIterable,
|
|
16
9
|
} from './stream.js';
|
|
10
|
+
import * as sym from './symbols.js';
|
|
17
11
|
export * from './builders.js';
|
|
18
12
|
export * from './print.js';
|
|
19
13
|
|
|
@@ -21,6 +15,8 @@ const arrayLast = (arr) => arr[arr.length - 1];
|
|
|
21
15
|
|
|
22
16
|
const isString = (str) => typeof str === 'string';
|
|
23
17
|
|
|
18
|
+
const { isArray } = Array;
|
|
19
|
+
|
|
24
20
|
const buildFrame = (node) => {
|
|
25
21
|
if (!node) throw new Error();
|
|
26
22
|
return { node, childrenIdx: -1, resolver: new Resolver(node) };
|
|
@@ -28,13 +24,33 @@ const buildFrame = (node) => {
|
|
|
28
24
|
|
|
29
25
|
const { hasOwn, freeze } = Object;
|
|
30
26
|
|
|
31
|
-
const get = (node, path) => {
|
|
27
|
+
export const get = (node, path) => {
|
|
28
|
+
const { type, properties } = node;
|
|
32
29
|
const { 1: name, 2: index } = /^([^\.]+)(?:\.(\d+))?/.exec(path) || [];
|
|
33
30
|
|
|
31
|
+
if (!hasOwn(properties, name)) {
|
|
32
|
+
throw new Error(`Cannot find {name: ${name}} on node of {type: ${type}}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
34
35
|
if (index != null) {
|
|
35
|
-
return
|
|
36
|
+
return properties[name]?.[parseInt(index, 10)];
|
|
36
37
|
} else {
|
|
37
|
-
return
|
|
38
|
+
return properties[name];
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const add = (node, ref, value) => {
|
|
43
|
+
const { name, isArray } = ref.value;
|
|
44
|
+
|
|
45
|
+
if (isArray) {
|
|
46
|
+
if (!hasOwn(node.properties, name)) {
|
|
47
|
+
node.properties[name] = [];
|
|
48
|
+
}
|
|
49
|
+
const array = node.properties[name];
|
|
50
|
+
|
|
51
|
+
array.push(value);
|
|
52
|
+
} else {
|
|
53
|
+
node.properties[name] = value;
|
|
38
54
|
}
|
|
39
55
|
};
|
|
40
56
|
|
|
@@ -64,6 +80,7 @@ function* __treeFromStream(tokens) {
|
|
|
64
80
|
|
|
65
81
|
rootNode = freeze({
|
|
66
82
|
flags: nodeFlags,
|
|
83
|
+
language: attributes['bablr-language'],
|
|
67
84
|
type: null,
|
|
68
85
|
children: [],
|
|
69
86
|
properties: {},
|
|
@@ -79,6 +96,13 @@ function* __treeFromStream(tokens) {
|
|
|
79
96
|
|
|
80
97
|
switch (token.type) {
|
|
81
98
|
case 'Null': {
|
|
99
|
+
const node = nodes.value;
|
|
100
|
+
const ref = arrayLast(node.children);
|
|
101
|
+
|
|
102
|
+
if (ref.type !== 'Reference') throw new Error();
|
|
103
|
+
|
|
104
|
+
add(node, ref, null);
|
|
105
|
+
|
|
82
106
|
break;
|
|
83
107
|
}
|
|
84
108
|
|
|
@@ -90,19 +114,12 @@ function* __treeFromStream(tokens) {
|
|
|
90
114
|
|
|
91
115
|
case 'Gap': {
|
|
92
116
|
if (held) {
|
|
93
|
-
const
|
|
94
|
-
const ref = arrayLast(children);
|
|
117
|
+
const node = nodes.value;
|
|
118
|
+
const ref = arrayLast(node.children);
|
|
95
119
|
|
|
96
120
|
if (ref.type !== 'Reference') throw new Error();
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
if (!properties[ref.value.name]) {
|
|
100
|
-
properties[ref.value.name] = [];
|
|
101
|
-
}
|
|
102
|
-
properties[ref.value.name].push(held);
|
|
103
|
-
} else {
|
|
104
|
-
properties[ref.value.name] = held;
|
|
105
|
-
}
|
|
122
|
+
add(node, ref, held);
|
|
106
123
|
|
|
107
124
|
held = null;
|
|
108
125
|
}
|
|
@@ -127,12 +144,7 @@ function* __treeFromStream(tokens) {
|
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
case 'OpenNodeTag': {
|
|
130
|
-
const
|
|
131
|
-
// do better
|
|
132
|
-
return [{ type: 'Literal', value: str }];
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const { flags, language, type, intrinsicValue, attributes } = token.value;
|
|
147
|
+
const { flags, language, type, attributes } = token.value;
|
|
136
148
|
const node = nodes.value;
|
|
137
149
|
|
|
138
150
|
if (!type) {
|
|
@@ -143,7 +155,7 @@ function* __treeFromStream(tokens) {
|
|
|
143
155
|
flags,
|
|
144
156
|
language,
|
|
145
157
|
type,
|
|
146
|
-
children:
|
|
158
|
+
children: [],
|
|
147
159
|
properties: {},
|
|
148
160
|
attributes: freeze(attributes),
|
|
149
161
|
});
|
|
@@ -153,24 +165,9 @@ function* __treeFromStream(tokens) {
|
|
|
153
165
|
throw new Error('Nodes must follow references');
|
|
154
166
|
}
|
|
155
167
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
if (isArray) {
|
|
159
|
-
if (!hasOwn(node.properties, name)) {
|
|
160
|
-
node.properties[name] = [];
|
|
161
|
-
}
|
|
162
|
-
const array = node.properties[name];
|
|
168
|
+
const ref = arrayLast(node.children);
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
} else {
|
|
166
|
-
node.properties[name] = newNode;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (intrinsicValue && flags.intrinsic) {
|
|
171
|
-
freeze(newNode.children);
|
|
172
|
-
freeze(newNode.properties);
|
|
173
|
-
break;
|
|
170
|
+
add(node, ref, newNode);
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
nodes = nodes.push(newNode);
|
|
@@ -184,7 +181,7 @@ function* __treeFromStream(tokens) {
|
|
|
184
181
|
if (flags.escape || flags.trivia) {
|
|
185
182
|
const parentChildren = nodes.prev.value.children;
|
|
186
183
|
|
|
187
|
-
parentChildren.push(
|
|
184
|
+
parentChildren.push(buildEmbeddedNode(completedNode));
|
|
188
185
|
}
|
|
189
186
|
|
|
190
187
|
freeze(completedNode.properties);
|
|
@@ -235,75 +232,66 @@ export const evaluateReturnAsync = async (generator) => {
|
|
|
235
232
|
return co.value;
|
|
236
233
|
};
|
|
237
234
|
|
|
238
|
-
export
|
|
235
|
+
export const streamFromTree = (rootNode) => __streamFromTree(rootNode);
|
|
236
|
+
|
|
237
|
+
function* __streamFromTree(rootNode) {
|
|
239
238
|
if (!rootNode || rootNode.type === 'Gap') {
|
|
240
239
|
return rootNode;
|
|
241
240
|
}
|
|
242
241
|
|
|
243
242
|
yield buildDoctypeTag(rootNode.attributes);
|
|
244
|
-
yield buildNodeOpenTag();
|
|
245
243
|
|
|
246
244
|
let stack = emptyStack.push(buildFrame(rootNode));
|
|
247
245
|
|
|
248
246
|
stack: while (stack.size) {
|
|
249
247
|
const frame = stack.value;
|
|
250
248
|
const { node, resolver } = frame;
|
|
251
|
-
const {
|
|
249
|
+
const { children } = node;
|
|
252
250
|
|
|
253
|
-
|
|
251
|
+
while (++frame.childrenIdx < children.length) {
|
|
252
|
+
const terminal = children[frame.childrenIdx];
|
|
254
253
|
|
|
255
|
-
|
|
254
|
+
switch (terminal.type) {
|
|
255
|
+
case 'EmbeddedNode': {
|
|
256
|
+
stack = stack.push(buildFrame(terminal.value));
|
|
256
257
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
260
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const terminal = children_[frame.childrenIdx];
|
|
261
|
+
case 'Reference': {
|
|
262
|
+
const resolved = resolver.consume(terminal).get(terminal);
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
case 'Literal':
|
|
267
|
-
case 'Gap':
|
|
268
|
-
case 'Null': {
|
|
269
|
-
yield terminal;
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
264
|
+
yield terminal;
|
|
272
265
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
266
|
+
if (terminal.value.isArray && !resolved) {
|
|
267
|
+
// TODO evaluate if this is still smart
|
|
268
|
+
yield buildNull();
|
|
269
|
+
} else {
|
|
270
|
+
if (!resolved) throw new Error();
|
|
277
271
|
|
|
278
|
-
|
|
279
|
-
if (stack.size > 1) {
|
|
280
|
-
yield terminal;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const resolved = resolver.consume(terminal).get(terminal);
|
|
284
|
-
if (resolved) {
|
|
285
|
-
stack = stack.push(buildFrame(resolved));
|
|
286
|
-
continue stack;
|
|
287
|
-
} else {
|
|
288
|
-
yield buildNull();
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
272
|
+
stack = stack.push(buildFrame(resolved));
|
|
291
273
|
}
|
|
292
274
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case 'Null': {
|
|
279
|
+
yield terminal;
|
|
280
|
+
|
|
281
|
+
break;
|
|
296
282
|
}
|
|
283
|
+
|
|
284
|
+
default:
|
|
285
|
+
yield terminal;
|
|
297
286
|
}
|
|
298
287
|
|
|
299
|
-
if (
|
|
300
|
-
|
|
288
|
+
if (terminal.type === 'EmbeddedNode' || terminal.type === 'Reference') {
|
|
289
|
+
continue stack;
|
|
301
290
|
}
|
|
302
291
|
}
|
|
303
292
|
|
|
304
293
|
stack = stack.pop();
|
|
305
294
|
}
|
|
306
|
-
yield buildNodeCloseTag();
|
|
307
295
|
}
|
|
308
296
|
|
|
309
297
|
export const getCooked = (cookable) => {
|
|
@@ -321,7 +309,7 @@ export const getCooked = (cookable) => {
|
|
|
321
309
|
throw new Error('cookable nodes must not contain other nodes');
|
|
322
310
|
}
|
|
323
311
|
|
|
324
|
-
case '
|
|
312
|
+
case 'EmbeddedNode': {
|
|
325
313
|
const { flags, attributes } = terminal.value;
|
|
326
314
|
|
|
327
315
|
if (!(flags.trivia || (flags.escape && attributes.cooked))) {
|
|
@@ -358,26 +346,28 @@ export const printCSTML = (rootNode) => {
|
|
|
358
346
|
return printCSTMLFromStream(streamFromTree(rootNode));
|
|
359
347
|
};
|
|
360
348
|
|
|
361
|
-
export const printPrettyCSTML = (rootNode,
|
|
362
|
-
return printPrettyCSTMLFromStream(streamFromTree(rootNode),
|
|
349
|
+
export const printPrettyCSTML = (rootNode, options = {}) => {
|
|
350
|
+
return printPrettyCSTMLFromStream(streamFromTree(rootNode), options);
|
|
363
351
|
};
|
|
364
352
|
|
|
365
353
|
export const printSource = (node) => {
|
|
366
354
|
const resolver = new Resolver(node);
|
|
367
355
|
let printed = '';
|
|
368
356
|
|
|
357
|
+
if (!node) return '';
|
|
358
|
+
|
|
369
359
|
if (node instanceof Promise) {
|
|
370
360
|
printed += '$Promise';
|
|
371
361
|
} else {
|
|
372
362
|
for (const child of node.children) {
|
|
373
363
|
if (child.type === 'Literal') {
|
|
374
364
|
printed += child.value;
|
|
375
|
-
} else if (child.type === '
|
|
365
|
+
} else if (child.type === 'EmbeddedNode') {
|
|
376
366
|
printed += printSource(child.value);
|
|
377
367
|
} else if (child.type === 'Reference') {
|
|
378
368
|
const node_ = resolver.consume(child).get(child);
|
|
379
369
|
|
|
380
|
-
if (node_
|
|
370
|
+
if (node_) {
|
|
381
371
|
printed += printSource(node_);
|
|
382
372
|
}
|
|
383
373
|
}
|
|
@@ -389,6 +379,88 @@ export const printSource = (node) => {
|
|
|
389
379
|
|
|
390
380
|
export const sourceTextFor = printSource;
|
|
391
381
|
|
|
382
|
+
export const getOpenTag = (node) => {
|
|
383
|
+
const tag = node.children[0];
|
|
384
|
+
if (tag && tag.type !== 'OpenNodeTag') throw new Error();
|
|
385
|
+
return tag;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
export const getCloseTag = (node) => {
|
|
389
|
+
const { children } = node;
|
|
390
|
+
const tag = children[children.length - 1];
|
|
391
|
+
if (tag.type !== 'CloseNodeTag') return null;
|
|
392
|
+
return tag;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export const getRange = (node) => {
|
|
396
|
+
const { children } = node;
|
|
397
|
+
return children.length ? [children[0], children[children.length - 1]] : null;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
export const createNode = (openTag) => {
|
|
401
|
+
const { flags, language, type, attributes } = openTag.value;
|
|
402
|
+
return { flags, language, type, children: [], properties: {}, attributes };
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export const finalizeNode = (node) => {
|
|
406
|
+
freeze(node);
|
|
407
|
+
freeze(node.children);
|
|
408
|
+
freeze(node.properties);
|
|
409
|
+
freeze(node.attributes);
|
|
410
|
+
return node;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const notNull = (node) => {
|
|
414
|
+
return node && node.type !== sym.null;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export const isNull = (node) => {
|
|
418
|
+
return !node || node.type === sym.null;
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
export const branchProperties = (properties) => {
|
|
422
|
+
const copy = { ...properties };
|
|
423
|
+
|
|
424
|
+
for (const { 0: key, 1: value } of Object.entries(copy)) {
|
|
425
|
+
if (isArray(value)) {
|
|
426
|
+
copy[key] = [...value];
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return copy;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export const branchNode = (node) => {
|
|
434
|
+
const { flags, language, type, children, properties, attributes } = node;
|
|
435
|
+
return {
|
|
436
|
+
flags,
|
|
437
|
+
language,
|
|
438
|
+
type,
|
|
439
|
+
children: [...children],
|
|
440
|
+
properties: branchProperties(properties),
|
|
441
|
+
attributes: { ...attributes },
|
|
442
|
+
};
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
export const acceptNode = (node, accepted) => {
|
|
446
|
+
const { children, properties, attributes } = accepted;
|
|
447
|
+
node.children = children;
|
|
448
|
+
node.properties = properties;
|
|
449
|
+
node.attributes = attributes;
|
|
450
|
+
return node;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export const getRoot = (fragmentNode) => {
|
|
454
|
+
if (!fragmentNode) return null;
|
|
455
|
+
|
|
456
|
+
for (const terminal of fragmentNode.children) {
|
|
457
|
+
if (terminal.type === 'Reference') {
|
|
458
|
+
if (terminal.value.isArray) throw new Error();
|
|
459
|
+
return fragmentNode.properties[terminal.value.name];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
392
464
|
export class Resolver {
|
|
393
465
|
constructor(node, counters = new Map()) {
|
|
394
466
|
this.node = node;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/agast-helpers",
|
|
3
3
|
"description": "Helper functions for working with agAST trees",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"./print": "./lib/print.js",
|
|
14
14
|
"./shorthand": "./lib/shorthand.js",
|
|
15
15
|
"./stream": "./lib/stream.js",
|
|
16
|
+
"./symbols": "./lib/symbols.js",
|
|
16
17
|
"./template": "./lib/template.js",
|
|
17
18
|
"./tree": "./lib/tree.js"
|
|
18
19
|
},
|