@bablr/agast-helpers 0.10.9 → 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,89 +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();
29
-
30
- for (const value of values(item)) {
31
- if (isObject(value)) {
32
- list = list.push(value);
33
- }
34
- }
35
-
36
- freeze(item);
37
- }
25
+ export const symbolName = (name) => {
26
+ return isString(name) ? Symbol.for(name) : name;
38
27
  };
39
28
 
40
- export const buildSpan = (name, guard = null, props = {}) => {
29
+ export const buildSpan = (name, guard = null, props = '{}') => {
41
30
  if (!name) throw new Error();
42
- deepFreeze(props);
43
- return freeze({ type: 'Explicit', name, guard, props });
31
+ if (!isString(props)) throw new Error();
32
+
33
+ return freezeRecord({ name, guard, props });
44
34
  };
45
35
 
46
- export const buildTypedSpan = (type, name, guard = null, props = {}) => {
47
- if (!type || !name) throw new Error();
48
- deepFreeze(props);
49
- return freeze({ type, name, guard, props });
36
+ export const buildSpanEntry = (name, guard = null, props = '{}') => {
37
+ return freezeRecord([name, buildSpan(name, guard, props)]);
50
38
  };
51
39
 
52
40
  export const buildProperty = (tags, shift) => {
53
41
  if (shift && !shift.index) throw new Error();
54
- freeze(tags);
55
-
56
- if (tags[0] && ![ReferenceTag, ShiftTag].includes(tags[0].type)) throw new Error();
57
- if (tags.length > 1 && !isArray(tags[1])) throw new Error();
42
+ let { 0: open, 1: bindings, 2: node } = tags[1];
58
43
 
59
- 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();
60
53
 
61
54
  // if (property.node && !(tags.length === 3)) throw new Error();
62
55
  // if (tags[0].type === ShiftTag && !property.shift) throw new Error();
63
56
 
64
- if ((tags[0]?.type == ShiftTag) !== !!shift) throw new Error();
65
- 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();
66
59
 
67
- return freeze({
60
+ return freezeRecord({
68
61
  tags,
69
- reference: tags[0]?.type === ShiftTag ? null : tags[0]?.value || buildReference(),
70
- bindings: freeze(tags[1]?.map((tag) => tag.value) || []),
71
- node: tags[2] || null,
72
- 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,
73
68
  });
74
69
  };
75
70
 
76
71
  export const buildPathFrame = (property, parentIndex = null, isGap = false) => {
77
- return freeze({ property, parentIndex, isGap });
72
+ return freezeRecord({ property, parentIndex, isGap });
78
73
  };
79
74
 
80
75
  export const buildPropertyTag = (tags, shift) => {
81
- 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));
82
78
  };
83
79
 
84
80
  export const buildDocument = (doctypeTag, tree) => {
85
- return freeze({ type: Document, value: freeze({ doctypeTag, tree }) });
81
+ return buildTag(Document, { doctypeTag, tree });
86
82
  };
87
83
 
88
84
  export const buildBeginningOfStreamToken = () => {
89
- return freeze({ type: Symbol.for('@bablr/beginning-of-stream'), value: undefined });
85
+ return buildTag(Symbol.for('@bablr/beginning-of-stream'));
90
86
  };
91
87
 
92
88
  export const buildReferenceTag = (type = null, name = null, flags = referenceFlags) => {
93
- if (!Object.isFrozen(flags)) throw new Error();
94
-
95
- return freeze({
96
- type: ReferenceTag,
97
- value: buildReference(type, name, flags),
98
- });
89
+ return buildTag(ReferenceTag, buildReference(type, name, flags));
99
90
  };
100
91
 
101
92
  export const buildReference = (type = null, name = null, flags = referenceFlags) => {
93
+ if (!Object.isFrozen(flags)) throw new Error();
102
94
  if (type != null && !['_', '.', '#', '@'].includes(type)) throw new Error();
103
95
  if (type && ['_', '#', '@'].includes(type) && (flags.intrinsic || flags.hasGap))
104
96
  throw new Error();
@@ -112,74 +104,78 @@ export const buildReference = (type = null, name = null, flags = referenceFlags)
112
104
 
113
105
  let type_ = type == null && name == null ? '.' : type;
114
106
 
115
- 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
+ });
116
112
  };
117
113
 
118
114
  export const referenceFromMatcher = (matcher) => {
115
+ if (!matcher) return buildReference();
119
116
  let { type, name, flags } = matcher;
117
+
120
118
  return buildReference(type, name, flags);
121
119
  };
122
120
 
121
+ export const buildBounds = (leading, trailing) => {
122
+ return freezeRecord({ leading, trailing });
123
+ };
124
+
123
125
  export const buildShift = (index, height) => {
124
126
  if (index == null || height == null) throw new Error();
125
- return freeze({ index, height });
127
+
128
+ return freezeRecord({ index, height });
126
129
  };
127
130
 
128
131
  export const buildNullTag = () => {
129
- return freeze({ type: NullTag, value: undefined });
132
+ return buildTag(NullTag);
130
133
  };
131
134
 
132
- export const buildBinding = (segments = []) => {
133
- // TODO relax this restriction
134
- if (!isArray(segments) || !segments.length) throw new Error();
135
- if (segments.includes(undefined)) throw new Error();
136
- return freeze({
137
- segments: segments.map((segment) =>
138
- freeze(isString(segment) ? { type: null, name: segment } : segment),
139
- ),
140
- });
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 });
141
141
  };
142
142
 
143
- export const buildBindingTag = (segments = []) => {
144
- return freeze({
145
- type: BindingTag,
146
- value: buildBinding(segments),
147
- });
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 });
148
151
  };
149
152
 
150
153
  export const buildChild = (type, value) => {
151
154
  if (!isSymbol(type)) throw new Error();
152
- return freeze({ type, value });
155
+ let child = buildTag(type, value);
156
+ if (!parseTag(child)) throw new Error();
157
+ return child;
153
158
  };
154
159
 
155
160
  export const buildGapTag = () => {
156
- return freeze({ type: GapTag, value: undefined });
161
+ return buildTag(GapTag);
157
162
  };
158
163
 
159
164
  export const buildShiftTag = () => {
160
- let value = Object.defineProperties(
161
- {},
162
- {
163
- index: {
164
- get() {
165
- throw new Error('moved');
166
- },
167
- },
168
- height: {
169
- get() {
170
- throw new Error('moved');
171
- },
172
- },
173
- },
174
- );
175
- return freeze({ type: ShiftTag, value });
165
+ return buildTag(ShiftTag);
176
166
  };
177
167
 
178
- export const buildDoctypeTag = (version = 0, attributes = freeze({})) => {
179
- return freeze({
180
- type: DoctypeTag,
181
- value: freeze({ doctype: 'cstml', version, attributes }),
182
- });
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));
183
179
  };
184
180
 
185
181
  export const buildOpenFragmentTag = (flags = nodeFlags, selfClosing = false) => {
@@ -194,55 +190,67 @@ export const buildOpenNodeTag = (
194
190
  flags,
195
191
  name,
196
192
  literalValue = null,
197
- attributes = {},
193
+ attributes = '{}',
198
194
  selfClosing = !!literalValue,
199
195
  ) => {
200
- if (!name && !flags.token) throw new Error();
201
196
  return buildFullOpenNodeTag(flags, null, name, literalValue, attributes, selfClosing);
202
197
  };
203
198
 
204
- export const buildFullOpenNodeTag = (
199
+ export const buildOpenNode = (
205
200
  flags = nodeFlags,
206
201
  type = Symbol.for('__'),
207
202
  name = null,
208
203
  literalValue = null,
209
- attributes = {},
204
+ attributes = '{}',
210
205
  selfClosing = !!literalValue,
211
206
  ) => {
212
- if (!isObject(attributes)) throw new Error();
207
+ if (!isString(attributes)) throw new Error();
213
208
  if (literalValue && !isString(literalValue)) throw new Error();
214
- if (!type && !name && !flags.token && !literalValue) throw new Error();
215
-
216
- deepFreeze(attributes);
217
-
218
- return freeze({
219
- type: OpenNodeTag,
220
- value: freeze({
221
- flags: freeze(flags),
222
- name: isString(name) ? Symbol.for(name) : name,
223
- type: isString(type) ? Symbol.for(type) : type,
224
- literalValue,
225
- attributes,
226
- selfClosing,
227
- }),
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,
228
219
  });
229
220
  };
230
221
 
231
- export const buildCloseNodeTag = () => {
232
- 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
+ );
233
235
  };
234
236
 
235
- const isString = (val) => typeof val === 'string';
237
+ export const buildCloseNodeTag = () => {
238
+ return buildTag(CloseNodeTag);
239
+ };
236
240
 
237
241
  export const buildLiteralTag = (value) => {
238
242
  if (!isString(value)) throw new Error('invalid literal');
239
- return freeze({ type: LiteralTag, value });
243
+ return buildTag(LiteralTag, value);
240
244
  };
241
245
 
242
246
  export const buildAttributeDefinition = (key, value) => {
243
247
  if (!key?.length) throw new Error();
244
- freeze(key);
245
- 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));
246
254
  };
247
255
 
248
256
  const flagsWithGap = new WeakMap();
@@ -252,7 +260,7 @@ export const getFlagsWithGap = (flags, hasGap = true) => {
252
260
 
253
261
  let gapFlags = flagsWithGap.get(flags);
254
262
  if (!gapFlags) {
255
- gapFlags = freeze({
263
+ gapFlags = freezeRecord({
256
264
  token: flags.token,
257
265
  hasGap,
258
266
  });
@@ -261,40 +269,828 @@ export const getFlagsWithGap = (flags, hasGap = true) => {
261
269
  return gapFlags;
262
270
  };
263
271
 
264
- export const nodeFlags = freeze({
272
+ export const nodeFlags = freezeRecord({
265
273
  token: false,
266
274
  hasGap: false,
267
275
  });
268
276
 
269
- export const tokenFlags = freeze({
277
+ export const tokenFlags = freezeRecord({
270
278
  token: true,
271
279
  hasGap: false,
272
280
  });
273
281
 
274
- export const referenceFlags = freeze({
282
+ export const referenceFlags = freezeRecord({
275
283
  array: false,
276
284
  expression: false,
277
285
  intrinsic: false,
278
286
  hasGap: false,
279
287
  });
280
288
 
281
- export const intrinsicReferenceFlags = freeze({
289
+ export const intrinsicReferenceFlags = freezeRecord({
282
290
  array: false,
283
291
  expression: false,
284
292
  intrinsic: true,
285
293
  hasGap: false,
286
294
  });
287
295
 
288
- export const gapReferenceFlags = freeze({
296
+ export const gapReferenceFlags = freezeRecord({
289
297
  array: false,
290
298
  expression: false,
291
299
  intrinsic: false,
292
300
  hasGap: true,
293
301
  });
294
302
 
295
- export const expressionReferenceFlags = freeze({
303
+ export const expressionReferenceFlags = freezeRecord({
296
304
  array: false,
297
305
  expression: true,
298
306
  intrinsic: false,
299
307
  hasGap: false,
300
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
+ };