@bablr/agast-helpers 0.10.10 → 0.11.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 CHANGED
@@ -1,5 +1,8 @@
1
- import emptyStack from '@iter-tools/imm-stack';
2
- import { isObject, isSymbol } from './object.js';
1
+ import * as BList from './b-list.js';
2
+ import { hasOwn, isObject, isArray, isSymbol, isString, isFrozen, freezeRecord } from './object.js';
3
+ import { buildParser, match } from './parse.js';
4
+ import { printExpression, printTag } from './print.js';
5
+
3
6
  import {
4
7
  DoctypeTag,
5
8
  OpenNodeTag,
@@ -16,83 +19,78 @@ import {
16
19
  NullNode,
17
20
  GapNode,
18
21
  TreeNode,
22
+ StreamTag,
19
23
  } from './symbols.js';
20
24
 
21
- const { freeze, values } = Object;
22
- const { isArray } = Array;
23
-
24
- export const deepFreeze = (object) => {
25
- let list = emptyStack.push(object);
26
- while (list.size) {
27
- let item = list.value;
28
- list = list.pop();
25
+ export const symbolName = (name) => {
26
+ return isString(name) ? Symbol.for(name) : name;
27
+ };
29
28
 
30
- for (const value of values(item)) {
31
- if (isObject(value)) {
32
- list = list.push(value);
33
- }
34
- }
29
+ export const buildSpan = (name, guard = null, props = '{}') => {
30
+ if (!name) throw new Error();
31
+ if (!isString(props)) throw new Error();
35
32
 
36
- freeze(item);
37
- }
33
+ return freezeRecord({ name, guard, props });
38
34
  };
39
35
 
40
- export const buildSpan = (name, guard = null, props = {}) => {
41
- if (!name) throw new Error();
42
- deepFreeze(props);
43
- return freeze({ name, guard, props });
36
+ export const buildSpanEntry = (name, guard = null, props = '{}') => {
37
+ return freezeRecord([name, buildSpan(name, guard, props)]);
44
38
  };
45
39
 
46
40
  export const buildProperty = (tags, shift) => {
47
41
  if (shift && !shift.index) throw new Error();
48
- freeze(tags);
49
-
50
- if (tags[0] && ![ReferenceTag, ShiftTag].includes(tags[0].type)) throw new Error();
51
- if (tags.length > 1 && !isArray(tags[1])) throw new Error();
42
+ let { 0: open, 1: bindings, 2: node } = tags[1];
52
43
 
53
- freeze(tags[1]);
44
+ if (!BList.getType(tags) === Symbol.for('List')) throw new Error();
45
+ if (
46
+ !(
47
+ (isArray(open) && open[0] === 0) ||
48
+ (isString(open) && [ReferenceTag, ShiftTag].includes(parseTagType(open)))
49
+ )
50
+ )
51
+ throw new Error();
52
+ if (tags[1].length > 1 && !isArray(bindings)) throw new Error();
54
53
 
55
54
  // if (property.node && !(tags.length === 3)) throw new Error();
56
55
  // if (tags[0].type === ShiftTag && !property.shift) throw new Error();
57
56
 
58
- if ((tags[0]?.type == ShiftTag) !== !!shift) throw new Error();
59
- if (tags[2] && ![NullNode, GapNode, TreeNode].includes(tags[2].type)) throw new Error();
57
+ if (!isArray(open) && (parseTagType(open) === ShiftTag) !== !!shift) throw new Error();
58
+ if (node && ![NullNode, GapNode, TreeNode].includes(node.type)) throw new Error();
60
59
 
61
- return freeze({
60
+ return freezeRecord({
62
61
  tags,
63
- reference: tags[0]?.type === ShiftTag ? null : tags[0]?.value || buildReference(),
64
- bindings: freeze(tags[1]?.map((tag) => tag.value) || []),
65
- node: tags[2] || null,
66
- shift: tags[0]?.type == ShiftTag ? shift : null,
62
+ reference: shift ? null : (!isArray(open) && parseTag(open).value) || buildReference(),
63
+ bindings: freezeRecord(
64
+ bindings ? [...BList.traverse(bindings)].map((tag) => parseTag(tag).value) : [],
65
+ ),
66
+ node: node || null,
67
+ shift: shift ? shift : null,
67
68
  });
68
69
  };
69
70
 
70
71
  export const buildPathFrame = (property, parentIndex = null, isGap = false) => {
71
- return freeze({ property, parentIndex, isGap });
72
+ return freezeRecord({ property, parentIndex, isGap });
72
73
  };
73
74
 
74
75
  export const buildPropertyTag = (tags, shift) => {
75
- return freeze({ type: Property, value: buildProperty(tags, shift) });
76
+ if (BList.getType(tags) !== Symbol.for('List')) throw new Error();
77
+ return buildTag(Property, buildProperty(tags, shift));
76
78
  };
77
79
 
78
80
  export const buildDocument = (doctypeTag, tree) => {
79
- return freeze({ type: Document, value: freeze({ doctypeTag, tree }) });
81
+ return buildTag(Document, { doctypeTag, tree });
80
82
  };
81
83
 
82
84
  export const buildBeginningOfStreamToken = () => {
83
- return freeze({ type: Symbol.for('@bablr/beginning-of-stream'), value: undefined });
85
+ return buildTag(Symbol.for('@bablr/beginning-of-stream'));
84
86
  };
85
87
 
86
88
  export const buildReferenceTag = (type = null, name = null, flags = referenceFlags) => {
87
- if (!Object.isFrozen(flags)) throw new Error();
88
-
89
- return freeze({
90
- type: ReferenceTag,
91
- value: buildReference(type, name, flags),
92
- });
89
+ return buildTag(ReferenceTag, buildReference(type, name, flags));
93
90
  };
94
91
 
95
92
  export const buildReference = (type = null, name = null, flags = referenceFlags) => {
93
+ if (!Object.isFrozen(flags)) throw new Error();
96
94
  if (type != null && !['_', '.', '#', '@'].includes(type)) throw new Error();
97
95
  if (type && ['_', '#', '@'].includes(type) && (flags.intrinsic || flags.hasGap))
98
96
  throw new Error();
@@ -106,74 +104,78 @@ export const buildReference = (type = null, name = null, flags = referenceFlags)
106
104
 
107
105
  let type_ = type == null && name == null ? '.' : type;
108
106
 
109
- return freeze({ type: type_, name, flags: freeze({ array, expression, intrinsic, hasGap }) });
107
+ return freezeRecord({
108
+ type: type_,
109
+ name,
110
+ flags: freezeRecord({ array, expression, intrinsic, hasGap }),
111
+ });
110
112
  };
111
113
 
112
114
  export const referenceFromMatcher = (matcher) => {
115
+ if (!matcher) return buildReference();
113
116
  let { type, name, flags } = matcher;
117
+
114
118
  return buildReference(type, name, flags);
115
119
  };
116
120
 
121
+ export const buildBounds = (leading, trailing) => {
122
+ return freezeRecord({ leading, trailing });
123
+ };
124
+
117
125
  export const buildShift = (index, height) => {
118
126
  if (index == null || height == null) throw new Error();
119
- return freeze({ index, height });
127
+
128
+ return freezeRecord({ index, height });
120
129
  };
121
130
 
122
131
  export const buildNullTag = () => {
123
- return freeze({ type: NullTag, value: undefined });
132
+ return buildTag(NullTag);
124
133
  };
125
134
 
126
- export const buildBinding = (segments = []) => {
127
- // TODO relax this restriction
128
- if (!isArray(segments) || !segments.length) throw new Error();
129
- if (segments.includes(undefined)) throw new Error();
130
- return freeze({
131
- segments: segments.map((segment) =>
132
- freeze(isString(segment) ? { type: null, name: segment } : segment),
133
- ),
134
- });
135
+ export const buildBinding = (type, name) => {
136
+ if (type && (!isSymbol(type) || !['..', '_'].includes(type.desription))) throw new Error();
137
+ if (name && !isSymbol(name)) throw new Error();
138
+ if (type && name) throw new Error();
139
+
140
+ return freezeRecord({ type, name });
135
141
  };
136
142
 
137
- export const buildBindingTag = (segments = []) => {
138
- return freeze({
139
- type: BindingTag,
140
- value: buildBinding(segments),
141
- });
143
+ export const buildBindingTag = (type, name) => {
144
+ return buildTag(BindingTag, buildBinding(type, name));
145
+ };
146
+
147
+ export const buildStreamTag = (processPath, stream) => {
148
+ if (stream && !(stream >= 1 && stream <= 4)) throw new Error();
149
+
150
+ return buildTag(StreamTag, { processPath, stream });
142
151
  };
143
152
 
144
153
  export const buildChild = (type, value) => {
145
154
  if (!isSymbol(type)) throw new Error();
146
- return freeze({ type, value });
155
+ let child = buildTag(type, value);
156
+ if (!parseTag(child)) throw new Error();
157
+ return child;
147
158
  };
148
159
 
149
160
  export const buildGapTag = () => {
150
- return freeze({ type: GapTag, value: undefined });
161
+ return buildTag(GapTag);
151
162
  };
152
163
 
153
164
  export const buildShiftTag = () => {
154
- let value = Object.defineProperties(
155
- {},
156
- {
157
- index: {
158
- get() {
159
- throw new Error('moved');
160
- },
161
- },
162
- height: {
163
- get() {
164
- throw new Error('moved');
165
- },
166
- },
167
- },
168
- );
169
- return freeze({ type: ShiftTag, value });
165
+ return buildTag(ShiftTag);
170
166
  };
171
167
 
172
- export const buildDoctypeTag = (version = 0, attributes = freeze({})) => {
173
- return freeze({
174
- type: DoctypeTag,
175
- value: freeze({ doctype: 'cstml', version, attributes }),
176
- });
168
+ export const buildDoctype = (version = 0, attributes = '{}') => {
169
+ if (!isString(attributes)) throw new Error();
170
+ return freezeRecord({ doctype: 'cstml', version, attributes });
171
+ };
172
+
173
+ export const buildTag = (type, value) => {
174
+ return freezeRecord({ type, value });
175
+ };
176
+
177
+ export const buildDoctypeTag = (version = 0, attributes = '{}') => {
178
+ return buildTag(DoctypeTag, buildDoctype(version, attributes));
177
179
  };
178
180
 
179
181
  export const buildOpenFragmentTag = (flags = nodeFlags, selfClosing = false) => {
@@ -188,55 +190,67 @@ export const buildOpenNodeTag = (
188
190
  flags,
189
191
  name,
190
192
  literalValue = null,
191
- attributes = {},
193
+ attributes = '{}',
192
194
  selfClosing = !!literalValue,
193
195
  ) => {
194
- if (!name && !flags.token) throw new Error();
195
196
  return buildFullOpenNodeTag(flags, null, name, literalValue, attributes, selfClosing);
196
197
  };
197
198
 
198
- export const buildFullOpenNodeTag = (
199
+ export const buildOpenNode = (
199
200
  flags = nodeFlags,
200
201
  type = Symbol.for('__'),
201
202
  name = null,
202
203
  literalValue = null,
203
- attributes = {},
204
+ attributes = '{}',
204
205
  selfClosing = !!literalValue,
205
206
  ) => {
206
- if (!isObject(attributes)) throw new Error();
207
+ if (!isString(attributes)) throw new Error();
207
208
  if (literalValue && !isString(literalValue)) throw new Error();
208
- if (!type && !name && !flags.token && !literalValue) throw new Error();
209
-
210
- deepFreeze(attributes);
211
-
212
- return freeze({
213
- type: OpenNodeTag,
214
- value: freeze({
215
- flags: freeze(flags),
216
- name: isString(name) ? Symbol.for(name) : name,
217
- type: isString(type) ? Symbol.for(type) : type,
218
- literalValue,
219
- attributes,
220
- selfClosing,
221
- }),
209
+ if (!type && !name && !flags.token && literalValue != null) throw new Error();
210
+ if (literalValue != null && !selfClosing) throw new Error();
211
+
212
+ return freezeRecord({
213
+ flags,
214
+ name: symbolName(name),
215
+ type: symbolName(type),
216
+ literalValue,
217
+ attributes,
218
+ selfClosing,
222
219
  });
223
220
  };
224
221
 
225
- export const buildCloseNodeTag = () => {
226
- return freeze({ type: CloseNodeTag, value: undefined });
222
+ export const buildFullOpenNodeTag = (
223
+ flags = nodeFlags,
224
+ type = Symbol.for('__'),
225
+ name = null,
226
+ literalValue = null,
227
+ attributes = '{}',
228
+ selfClosing = !!literalValue,
229
+ ) => {
230
+ if (!hasOwn(flags, 'token')) throw new Error();
231
+ return buildTag(
232
+ OpenNodeTag,
233
+ buildOpenNode(flags, type, name, literalValue, attributes, selfClosing),
234
+ );
227
235
  };
228
236
 
229
- const isString = (val) => typeof val === 'string';
237
+ export const buildCloseNodeTag = () => {
238
+ return buildTag(CloseNodeTag);
239
+ };
230
240
 
231
241
  export const buildLiteralTag = (value) => {
232
242
  if (!isString(value)) throw new Error('invalid literal');
233
- return freeze({ type: LiteralTag, value });
243
+ return buildTag(LiteralTag, value);
234
244
  };
235
245
 
236
246
  export const buildAttributeDefinition = (key, value) => {
237
247
  if (!key?.length) throw new Error();
238
- freeze(key);
239
- return freeze({ type: AttributeDefinition, value: freeze({ path: key, value }) });
248
+
249
+ return freezeRecord({ path: key, value });
250
+ };
251
+
252
+ export const buildAttributeDefinitionTag = (key, value) => {
253
+ return buildTag(AttributeDefinition, buildAttributeDefinition(key, value));
240
254
  };
241
255
 
242
256
  const flagsWithGap = new WeakMap();
@@ -246,7 +260,7 @@ export const getFlagsWithGap = (flags, hasGap = true) => {
246
260
 
247
261
  let gapFlags = flagsWithGap.get(flags);
248
262
  if (!gapFlags) {
249
- gapFlags = freeze({
263
+ gapFlags = freezeRecord({
250
264
  token: flags.token,
251
265
  hasGap,
252
266
  });
@@ -255,40 +269,828 @@ export const getFlagsWithGap = (flags, hasGap = true) => {
255
269
  return gapFlags;
256
270
  };
257
271
 
258
- export const nodeFlags = freeze({
272
+ export const nodeFlags = freezeRecord({
259
273
  token: false,
260
274
  hasGap: false,
261
275
  });
262
276
 
263
- export const tokenFlags = freeze({
277
+ export const tokenFlags = freezeRecord({
264
278
  token: true,
265
279
  hasGap: false,
266
280
  });
267
281
 
268
- export const referenceFlags = freeze({
282
+ export const referenceFlags = freezeRecord({
269
283
  array: false,
270
284
  expression: false,
271
285
  intrinsic: false,
272
286
  hasGap: false,
273
287
  });
274
288
 
275
- export const intrinsicReferenceFlags = freeze({
289
+ export const intrinsicReferenceFlags = freezeRecord({
276
290
  array: false,
277
291
  expression: false,
278
292
  intrinsic: true,
279
293
  hasGap: false,
280
294
  });
281
295
 
282
- export const gapReferenceFlags = freeze({
296
+ export const gapReferenceFlags = freezeRecord({
283
297
  array: false,
284
298
  expression: false,
285
299
  intrinsic: false,
286
300
  hasGap: true,
287
301
  });
288
302
 
289
- export const expressionReferenceFlags = freeze({
303
+ export const expressionReferenceFlags = freezeRecord({
290
304
  array: false,
291
305
  expression: true,
292
306
  intrinsic: false,
293
307
  hasGap: false,
294
308
  });
309
+
310
+ export const canStartIdentifier = (chr) => {
311
+ let code = chr.charCodeAt(0);
312
+ return (
313
+ (code >= 97 && code <= 122) ||
314
+ (code >= 65 && code <= 90) ||
315
+ code === 96 ||
316
+ (code >= 0x80 && code <= 0x10ffff)
317
+ );
318
+ };
319
+
320
+ export const canContinueIdentifier = (chr) => {
321
+ if (canStartIdentifier(chr)) return true;
322
+ let code = chr.charCodeAt(0);
323
+
324
+ return code === 45 || code === 95 || (code >= 48 && code <= 57);
325
+ };
326
+
327
+ export const parseTagType = (input) => {
328
+ let p = buildTagParser(input);
329
+ let { str, idx } = p;
330
+ if (str == null) return null;
331
+ if (isObject(str)) return str.type;
332
+ if (!isString(str)) throw new Error();
333
+ if (!str.length) throw new Error();
334
+
335
+ switch (str[idx]) {
336
+ case 'n':
337
+ return str[idx + 1] === 'u' && str[idx + 2] === 'l' && str[idx + 3] === 'l'
338
+ ? NullTag
339
+ : ReferenceTag;
340
+ case '<':
341
+ if (str[idx + 1] === '-' && str[idx + 2] === '-' && str[idx + 3] === '-') {
342
+ throw new Error('reserved syntax');
343
+ }
344
+ return str[idx + 1] === '!'
345
+ ? DoctypeTag
346
+ : str[idx + 1] === '/'
347
+ ? str[idx + 2] === '/'
348
+ ? GapTag
349
+ : CloseNodeTag
350
+ : str[idx + 1] === '-'
351
+ ? StreamTag
352
+ : OpenNodeTag;
353
+ case '^':
354
+ return ShiftTag;
355
+ case ':':
356
+ return BindingTag;
357
+ case '.':
358
+ case '#':
359
+ case '@':
360
+ case '_':
361
+ return ReferenceTag;
362
+ case '"':
363
+ case "'":
364
+ return LiteralTag;
365
+ case '{':
366
+ return AttributeDefinition;
367
+ default:
368
+ if (canStartIdentifier(str[idx])) {
369
+ return ReferenceTag;
370
+ } else {
371
+ throw new Error();
372
+ }
373
+ }
374
+ };
375
+
376
+ export const parseObject = (input) => {
377
+ let p = buildParser(input);
378
+ let { str } = p;
379
+ if (!str.length) throw new Error();
380
+ let obj = {};
381
+ let chr = str[p.idx];
382
+
383
+ if (chr !== '{') throw new Error();
384
+ chr = str[++p.idx];
385
+
386
+ while (chr === ' ') {
387
+ chr = str[++p.idx];
388
+ }
389
+
390
+ let sep = true;
391
+ while (sep && chr !== '}') {
392
+ let key = `'"`.includes(chr) ? parseString(p) : parseIdentifier(p);
393
+ chr = str[p.idx];
394
+
395
+ if (chr !== ':') throw new Error();
396
+ chr = str[++p.idx];
397
+
398
+ while (chr === ' ') chr = str[++p.idx];
399
+
400
+ let value = parseExpression(p);
401
+ chr = str[p.idx];
402
+
403
+ obj[key] = value;
404
+
405
+ sep = chr === ',' ? chr : null;
406
+ if (sep) {
407
+ chr = str[++p.idx];
408
+ }
409
+
410
+ while (chr === ' ') chr = str[++p.idx];
411
+ }
412
+
413
+ if (chr !== '}') throw new Error();
414
+ chr = str[++p.idx];
415
+
416
+ return freezeRecord(obj);
417
+ };
418
+
419
+ export const parseArray = (input) => {
420
+ let p = buildParser(input);
421
+ let { str } = p;
422
+ let arr = [];
423
+ let chr = str[p.idx];
424
+
425
+ if (chr !== '[') throw new Error();
426
+ chr = str[++p.idx];
427
+
428
+ while (chr === ' ') {
429
+ chr = str[++p.idx];
430
+ }
431
+
432
+ let value = parseExpression(p);
433
+ chr = str[p.idx];
434
+
435
+ arr.push(value);
436
+
437
+ while (chr === ' ') {
438
+ chr = str[++p.idx];
439
+ }
440
+
441
+ if (chr !== ']') throw new Error();
442
+ chr = str[++p.idx];
443
+
444
+ return freezeRecord(arr);
445
+ };
446
+
447
+ export const parseString = (input) => {
448
+ let p = buildParser(input);
449
+ let { str } = p;
450
+ let chr = str[p.idx];
451
+ let q = chr;
452
+ let result = [];
453
+
454
+ if (!`'"`.includes(q)) throw new Error();
455
+ chr = str[++p.idx];
456
+
457
+ while (chr && chr !== q) {
458
+ if (chr === '\\') {
459
+ result.push(parseEscape(p));
460
+ chr = str[p.idx];
461
+ } else {
462
+ result.push(chr);
463
+ chr = str[++p.idx];
464
+ }
465
+ }
466
+
467
+ if (chr === q) {
468
+ chr = str[++p.idx];
469
+ } else {
470
+ throw new Error();
471
+ }
472
+
473
+ return result.join('');
474
+ };
475
+
476
+ export const parseUnsignedInteger = (input) => {
477
+ let p = buildParser(input);
478
+ let { str } = p;
479
+ let chr = str[p.idx];
480
+ let digits = [];
481
+
482
+ while (chr >= '0' && chr <= '9') {
483
+ digits.push(chr);
484
+ chr = str[++p.idx];
485
+ }
486
+
487
+ return parseInt(digits.join(''), 10);
488
+ };
489
+
490
+ export const parseEscape = (input) => {
491
+ let p = buildParser(input);
492
+ let { str } = p;
493
+ let chr = str[p.idx];
494
+
495
+ if (chr !== '\\') throw new Error();
496
+ chr = str[++p.idx];
497
+
498
+ switch (chr) {
499
+ case 'u': {
500
+ chr = str[++p.idx];
501
+
502
+ let digits = [];
503
+ let q = chr === '{' ? chr : null;
504
+ if (q) {
505
+ chr = str[++p.idx];
506
+ }
507
+
508
+ let i = 0;
509
+ while (q ? chr !== '}' : i < 4) {
510
+ if (
511
+ !((chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z'))
512
+ )
513
+ throw new Error();
514
+
515
+ digits.push(chr);
516
+ chr = str[++p.idx];
517
+ i++;
518
+ }
519
+
520
+ if (q) {
521
+ if (chr !== '}') throw new Error();
522
+ chr = str[++p.idx];
523
+ }
524
+ return String.fromCodePoint(parseInt(digits.join(''), 16));
525
+ }
526
+ case 'r':
527
+ chr = str[++p.idx];
528
+ return '\r';
529
+ case 'n':
530
+ chr = str[++p.idx];
531
+ return '\n';
532
+ case 't':
533
+ chr = str[++p.idx];
534
+ return '\t';
535
+ case '\\':
536
+ case '"':
537
+ case "'":
538
+ case '`':
539
+ ++p.idx;
540
+ return chr;
541
+ }
542
+ };
543
+
544
+ export const parseExpression = (input) => {
545
+ let p = buildParser(input);
546
+ let { str } = p;
547
+ let chr = str[p.idx];
548
+
549
+ switch (chr) {
550
+ case '{':
551
+ return parseObject(p);
552
+ case '[':
553
+ return parseArray(p);
554
+ case '"':
555
+ case "'":
556
+ return parseString(p);
557
+ case 'n':
558
+ if (match(p, 'null')) {
559
+ p.idx += 4;
560
+ return null;
561
+ } else {
562
+ throw new Error();
563
+ }
564
+ case 't':
565
+ if (match(p, 'true')) {
566
+ p.idx += 4;
567
+ return true;
568
+ } else {
569
+ throw new Error();
570
+ }
571
+ case 'f':
572
+ if (match(p, 'false')) {
573
+ p.idx += 5;
574
+ return false;
575
+ } else {
576
+ throw new Error();
577
+ }
578
+ case 'u':
579
+ if (match(p, 'undefined')) {
580
+ p.idx += 9;
581
+ return undefined;
582
+ } else {
583
+ throw new Error();
584
+ }
585
+ case 'N':
586
+ if (match(p, 'NaN')) {
587
+ p.idx += 3;
588
+ return NaN;
589
+ } else {
590
+ throw new Error();
591
+ }
592
+ case 'I':
593
+ if (match(p, 'Infinity')) {
594
+ p.idx += 8;
595
+ return Infinity;
596
+ } else {
597
+ throw new Error();
598
+ }
599
+ case '-':
600
+ if (str[p.idx + 1] === 'I' && match(p, '-Infinity')) {
601
+ p.idx += 9;
602
+ return -Infinity;
603
+ } else {
604
+ throw new Error();
605
+ }
606
+ case '+':
607
+ if (str[p.idx + 1] === 'I' && match(p, '+Infinity')) {
608
+ p.idx += 9;
609
+ return Infinity;
610
+ } else {
611
+ throw new Error();
612
+ }
613
+ default:
614
+ if (chr >= '0' && chr <= '9') {
615
+ return parseUnsignedInteger(p);
616
+ } else {
617
+ throw new Error();
618
+ }
619
+ }
620
+ };
621
+
622
+ export const parseIdentifier = (input) => {
623
+ let p = buildParser(input);
624
+ let { str } = p;
625
+ let chr = str[p.idx];
626
+ let value = [];
627
+
628
+ let q = chr === '`' ? chr : null;
629
+ if (q) {
630
+ chr = str[++p.idx];
631
+ }
632
+
633
+ let lit, esc;
634
+ do {
635
+ lit = null;
636
+ esc = null;
637
+ if (chr === '\\') {
638
+ esc = parseEscape(p);
639
+ chr = str[p.idx];
640
+ } else {
641
+ if (!q) {
642
+ if (!value.length) {
643
+ if (canStartIdentifier(chr)) {
644
+ lit = chr;
645
+ value.push(chr);
646
+ chr = str[++p.idx];
647
+ } else {
648
+ throw new Error();
649
+ }
650
+ }
651
+
652
+ while (chr && canContinueIdentifier(chr)) {
653
+ lit = chr;
654
+ value.push(chr);
655
+ chr = str[++p.idx];
656
+ }
657
+ } else {
658
+ while (!'`\\\r\n'.includes(chr)) {
659
+ lit = chr;
660
+ value.push(chr);
661
+ chr = str[++p.idx];
662
+ }
663
+ }
664
+ }
665
+ } while (lit || esc);
666
+
667
+ if (q) {
668
+ if (chr !== '`') throw new Error();
669
+ chr = str[++p.idx];
670
+ }
671
+ return value.join('');
672
+ };
673
+
674
+ let buildTagParser = (tag) => {
675
+ switch (typeof tag) {
676
+ case 'string':
677
+ return { idx: 0, str: tag };
678
+ case 'object':
679
+ if (tag.type) {
680
+ // TODO str is sometimes not a string!
681
+ return { idx: 0, str: typeof tag === 'string' ? tag : printTag(tag) };
682
+ } else {
683
+ return tag;
684
+ }
685
+ }
686
+ };
687
+
688
+ export const parseOpenNodeTag = (tag) => {
689
+ let p = buildTagParser(tag);
690
+ let { str } = p;
691
+ let chr = str[p.idx];
692
+
693
+ if (chr !== '<') throw new Error();
694
+ chr = str[++p.idx];
695
+ let token = false;
696
+ let hasGap = false;
697
+ if (chr === '*') {
698
+ chr = str[++p.idx];
699
+ token = true;
700
+ }
701
+ if (chr === '$') {
702
+ chr = str[++p.idx];
703
+ hasGap = true;
704
+ }
705
+
706
+ let flags = token ? tokenFlags : nodeFlags;
707
+
708
+ let type = null;
709
+ let name = null;
710
+
711
+ if (chr === '_') {
712
+ chr = str[++p.idx];
713
+ type = Symbol.for('_');
714
+
715
+ if (chr === '_') {
716
+ chr = str[++p.idx];
717
+ type = Symbol.for('__');
718
+ }
719
+ }
720
+ if (!` {'"/>`.includes(chr)) {
721
+ name = parseIdentifier(p);
722
+ chr = str[p.idx];
723
+ }
724
+
725
+ while (chr === ' ') {
726
+ chr = str[++p.idx];
727
+ }
728
+
729
+ let literalValue = null;
730
+
731
+ if (`'"`.includes(chr)) {
732
+ literalValue = parseString(p);
733
+ chr = str[p.idx];
734
+
735
+ while (chr === ' ') chr = str[++p.idx];
736
+ }
737
+
738
+ let attributes = freezeRecord({});
739
+
740
+ if (chr === '{') {
741
+ attributes = parseObject(p);
742
+ chr = str[p.idx];
743
+
744
+ while (chr === ' ') chr = str[++p.idx];
745
+ }
746
+
747
+ let selfClosing = false;
748
+
749
+ if (chr === '/') {
750
+ chr = str[++p.idx];
751
+ selfClosing = true;
752
+ }
753
+
754
+ if (chr === '>') {
755
+ chr = str[++p.idx];
756
+ }
757
+
758
+ if (isString(tag) && p.idx !== str.length) throw new Error();
759
+
760
+ if (hasGap) flags = getFlagsWithGap(flags);
761
+
762
+ return buildTag(OpenNodeTag, {
763
+ flags,
764
+ name: symbolName(name),
765
+ type: symbolName(type),
766
+ literalValue,
767
+ attributes: printExpression(attributes),
768
+ selfClosing,
769
+ });
770
+ };
771
+
772
+ export const parseReferenceFlags = (input) => {
773
+ let p = buildTagParser(input);
774
+ let { str } = p;
775
+ let chr = str[p.idx];
776
+ let array = false;
777
+ let expression = false;
778
+ let intrinsic = false;
779
+ let hasGap = false;
780
+
781
+ if (chr === '[') {
782
+ chr = str[++p.idx];
783
+ array = true;
784
+ if (chr !== ']') throw new Error();
785
+ chr = str[++p.idx];
786
+ }
787
+
788
+ if (chr === '+') {
789
+ chr = str[++p.idx];
790
+ expression = true;
791
+ }
792
+
793
+ if (chr === '*') {
794
+ chr = str[++p.idx];
795
+ intrinsic = true;
796
+ }
797
+
798
+ if (chr === '$') {
799
+ chr = str[++p.idx];
800
+ hasGap = true;
801
+ }
802
+
803
+ return freezeRecord({ array, expression, intrinsic, hasGap });
804
+ };
805
+
806
+ export const parseReferenceTag = (tag) => {
807
+ let p = buildTagParser(tag);
808
+ let { str } = p;
809
+ let chr = str[p.idx];
810
+ let type = null;
811
+ let name = null;
812
+
813
+ if ('.#@_'.includes(chr)) {
814
+ type = chr;
815
+ chr = str[++p.idx];
816
+ }
817
+
818
+ if (!type || (type === '#' && canStartIdentifier(chr))) {
819
+ name = parseIdentifier(p);
820
+ chr = str[p.idx];
821
+ }
822
+ while (chr === ' ') chr = str[++p.idx];
823
+
824
+ let flags = parseReferenceFlags(p);
825
+ chr = str[p.idx];
826
+
827
+ while (chr === ' ') chr = str[++p.idx];
828
+
829
+ if (chr !== ':') throw new Error();
830
+ chr = str[++p.idx];
831
+
832
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
833
+
834
+ return buildTag(ReferenceTag, { type, name, flags });
835
+ };
836
+
837
+ export const parseLiteralTag = (tag) => {
838
+ let p = buildTagParser(tag);
839
+
840
+ let str = parseString(p);
841
+
842
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
843
+
844
+ return buildLiteralTag(str);
845
+ };
846
+
847
+ export const parseCloseNodeTag = (tag) => {
848
+ let p = buildTagParser(tag);
849
+ if (!match(p, '</>')) throw new Error();
850
+ p.idx += 3;
851
+
852
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
853
+
854
+ return buildCloseNodeTag();
855
+ };
856
+
857
+ export const parseGapTag = (tag) => {
858
+ let p = buildTagParser(tag);
859
+ if (!match(p, '<//>')) throw new Error();
860
+ p.idx += 4;
861
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
862
+ return buildGapTag();
863
+ };
864
+
865
+ export const parseNullTag = (tag) => {
866
+ let p = buildTagParser(tag);
867
+ if (!match(p, 'null')) throw new Error();
868
+ p.idx += 4;
869
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
870
+ return buildNullTag();
871
+ };
872
+
873
+ export const parseShiftTag = (tag) => {
874
+ let p = buildTagParser(tag);
875
+ if (!match(p, '^^^')) throw new Error();
876
+ p.idx += 3;
877
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
878
+ return buildShiftTag();
879
+ };
880
+
881
+ export const parseIdentifierPath = (p) => {
882
+ let { str } = buildParser(p);
883
+ let chr = str[p.idx];
884
+ let segments = [];
885
+
886
+ let sep = true;
887
+ while (sep) {
888
+ segments.push(parseIdentifier(p));
889
+ chr = str[p.idx];
890
+
891
+ sep = chr === '.' ? chr : null;
892
+ }
893
+
894
+ return freezeRecord(segments);
895
+ };
896
+
897
+ export const parseAttributeDefinitionTag = (tag) => {
898
+ let p = buildTagParser(tag);
899
+ let { str } = p;
900
+ let chr = str[p.idx];
901
+
902
+ if (chr !== '{') throw new Error();
903
+ chr = str[++p.idx];
904
+
905
+ while (chr === ' ') {
906
+ chr = str[++p.idx];
907
+ }
908
+
909
+ let key = parseIdentifierPath(p);
910
+ chr = str[p.idx];
911
+
912
+ if (chr !== ':') throw new Error();
913
+ chr = str[++p.idx];
914
+
915
+ while (chr === ' ') {
916
+ chr = str[++p.idx];
917
+ }
918
+
919
+ let value = parseExpression(p);
920
+ chr = str[p.idx];
921
+
922
+ while (chr === ' ') {
923
+ chr = str[++p.idx];
924
+ }
925
+
926
+ if (chr !== '}') throw new Error();
927
+ chr = str[++p.idx];
928
+
929
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
930
+
931
+ return buildTag(AttributeDefinition, { path: key, value });
932
+ };
933
+
934
+ export const parseBindingTag = (tag) => {
935
+ let p = buildTagParser(tag);
936
+ let { str } = p;
937
+ let chr = str[p.idx];
938
+ let type = null;
939
+ let name = null;
940
+
941
+ if (chr !== ':') throw new Error();
942
+ chr = str[++p.idx];
943
+
944
+ if (chr === '.' && str[p.idx + 1] === '.') {
945
+ p.idx += 2;
946
+ type = Symbol.for('..');
947
+ } else {
948
+ name = Symbol.for(parseIdentifier(p));
949
+ chr = str[p.idx];
950
+ }
951
+
952
+ if (chr !== ':') throw new Error();
953
+ chr = str[++p.idx];
954
+
955
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
956
+
957
+ return buildBindingTag(type, name);
958
+ };
959
+
960
+ export const parseDoctypeTag = (tag) => {
961
+ let p = buildTagParser(tag);
962
+ let { str } = p;
963
+ let chr = str[p.idx];
964
+
965
+ if (chr !== '<' || str[p.idx + 1] !== '!') throw new Error();
966
+ p.idx += 2;
967
+
968
+ let version = parseUnsignedInteger(p);
969
+ chr = str[p.idx];
970
+
971
+ if (!match(p, ':cstml')) throw new Error();
972
+ p.idx += 6;
973
+ chr = str[p.idx];
974
+
975
+ while (chr === ' ') {
976
+ chr = str[++p.idx];
977
+ }
978
+
979
+ let attributes = {};
980
+ if (chr === '{') {
981
+ attributes = parseObject(p);
982
+ chr = str[p.idx];
983
+
984
+ while (chr === ' ') chr = str[++p.idx];
985
+ }
986
+
987
+ if (chr !== '>') throw new Error();
988
+ chr = str[++p.idx];
989
+
990
+ if (isString(tag) && p.idx !== p.str.length) throw new Error();
991
+
992
+ return buildDoctypeTag(version, printExpression(attributes));
993
+ };
994
+
995
+ export const parseStreamTag = (tag) => {
996
+ let p = buildTagParser(tag);
997
+ let { str } = p;
998
+ let chr = str[p.idx];
999
+
1000
+ if (chr !== '<') throw new Error();
1001
+ chr = str[++p.idx];
1002
+
1003
+ let processPath = [];
1004
+ let stream = 1;
1005
+
1006
+ if (chr >= '0' && chr <= '9') {
1007
+ do {
1008
+ let digits = [];
1009
+ while (chr >= '0' && chr <= '9') {
1010
+ digits.push(chr);
1011
+ chr = str[++p.idx];
1012
+ }
1013
+ processPath.push(parseInt(digits.join(''), 10));
1014
+ } while (chr === '.');
1015
+ }
1016
+
1017
+ if (chr === '-') {
1018
+ chr = str[++p.idx];
1019
+ if ('1234'.includes(chr)) {
1020
+ stream = parseInt(chr, 10);
1021
+ chr = str[++p.idx];
1022
+ }
1023
+ }
1024
+
1025
+ if (chr !== '>') throw new Error();
1026
+ chr = str[++p.idx];
1027
+
1028
+ return buildStreamTag(processPath, stream);
1029
+ };
1030
+
1031
+ export const parseTag = (tag) => {
1032
+ let p = buildTagParser(tag);
1033
+ if (tag == null) return null;
1034
+
1035
+ let tagType = parseTagType(p);
1036
+
1037
+ switch (tagType) {
1038
+ case DoctypeTag:
1039
+ return parseDoctypeTag(tag);
1040
+ case AttributeDefinition:
1041
+ return parseAttributeDefinitionTag(tag);
1042
+ case OpenNodeTag:
1043
+ return parseOpenNodeTag(tag);
1044
+ case ReferenceTag:
1045
+ return parseReferenceTag(tag);
1046
+ case BindingTag:
1047
+ return parseBindingTag(tag);
1048
+ case LiteralTag:
1049
+ return parseLiteralTag(tag);
1050
+ case CloseNodeTag:
1051
+ return parseCloseNodeTag(tag);
1052
+ case StreamTag:
1053
+ return parseStreamTag(tag);
1054
+ case GapTag:
1055
+ return parseGapTag(tag);
1056
+ case NullTag:
1057
+ return parseNullTag(tag);
1058
+ case ShiftTag:
1059
+ return parseShiftTag(tag);
1060
+ case Property:
1061
+ return tag;
1062
+ case TreeNode:
1063
+ case GapNode:
1064
+ case NullNode:
1065
+ default:
1066
+ throw new Error();
1067
+ }
1068
+ };
1069
+
1070
+ export const isValidTag = (tag) => {
1071
+ if (isString(tag)) return !!parseTag(tag);
1072
+ if (!isFrozen(tag)) return false;
1073
+ if (!hasOwn(tag, 'type') || !hasOwn(tag, 'value')) return false;
1074
+
1075
+ switch (tag.type) {
1076
+ case ReferenceTag:
1077
+ case OpenNodeTag:
1078
+ case AttributeDefinition:
1079
+ case CloseNodeTag:
1080
+ case ShiftTag:
1081
+ case NullTag:
1082
+ case GapTag:
1083
+ case LiteralTag:
1084
+ case BindingTag:
1085
+ case DoctypeTag:
1086
+ return parseTag(tag);
1087
+ case Property:
1088
+ case TreeNode:
1089
+ case GapNode:
1090
+ case NullNode:
1091
+ return false;
1092
+
1093
+ default:
1094
+ throw new Error();
1095
+ }
1096
+ };