@bablr/boot 0.1.8 → 0.2.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.
@@ -1,118 +1,198 @@
1
- const Regex = require('./regex.js');
2
- const StringLanguage = require('./string.js');
3
- const Number = require('./number.js');
1
+ const objectEntries = require('iter-tools-es/methods/object-entries');
2
+
4
3
  const { buildCovers } = require('../utils.js');
5
4
  const sym = require('../symbols.js');
6
5
 
7
6
  const _ = /\s+/y;
8
7
  const PN = 'Punctuator';
9
8
  const ID = 'Identifier';
9
+ const KW = 'Keyword';
10
10
  const LIT = 'Literal';
11
11
 
12
12
  const name = 'CSTML';
13
13
 
14
- const dependencies = { Regex, String: StringLanguage, Number };
14
+ const canonicalURL = 'https://bablr.org/languages/core/cstml';
15
+
16
+ const dependencies = {};
17
+
18
+ const escapables = new Map(
19
+ objectEntries({
20
+ n: '\n',
21
+ r: '\r',
22
+ t: '\t',
23
+ 0: '\0',
24
+ }),
25
+ );
26
+
27
+ const cookEscape = (escape, span) => {
28
+ let hexMatch;
29
+
30
+ if (!escape.startsWith('\\')) {
31
+ throw new Error('string escape must start with \\');
32
+ }
33
+
34
+ if ((hexMatch = /\\u([0-9a-f]{4})/iy.exec(escape))) {
35
+ //continue
36
+ } else if ((hexMatch = /\\u{([0-9a-f]+)}/iy.exec(escape))) {
37
+ //continue
38
+ }
39
+
40
+ if (hexMatch) {
41
+ return String.fromCodePoint(parseInt(hexMatch[1], 16));
42
+ }
43
+
44
+ const litPattern = span === 'Single' ? /\\([\\gnrt0'])/y : /\\([\\gnrt0"])/y;
45
+ const litMatch = litPattern.exec(escape);
46
+
47
+ if (litMatch) {
48
+ if (litMatch[1] === 'g') {
49
+ return null;
50
+ } else {
51
+ return escapables.get(litMatch[1]) || litMatch[1];
52
+ }
53
+ }
54
+
55
+ throw new Error('unable to cook string escape');
56
+ };
15
57
 
16
58
  const covers = buildCovers({
17
59
  [sym.node]: [
60
+ 'Document',
61
+ 'DocumentVersion',
62
+ 'DoctypeTag',
18
63
  'Attribute',
19
64
  'Property',
65
+ 'Reference',
20
66
  'TagType',
67
+ 'Null',
68
+ 'Gap',
21
69
  'Node',
22
- 'OpenFragmentNodeTag',
23
- 'OpenTerminalNodeTag',
70
+ 'IdentifierPath',
71
+ 'OpenFragmentTag',
24
72
  'OpenNodeTag',
25
73
  'CloseNodeTag',
74
+ 'CloseFragmentTag',
26
75
  'Terminal',
76
+ 'Number',
77
+ 'Digit',
78
+ 'String',
79
+ 'Content',
80
+ 'UnsignedInteger',
27
81
  ],
28
82
  [sym.fragment]: ['Attributes', 'Fragment'],
29
83
  Attribute: ['MappingAttribute', 'BooleanAttribute'],
30
- AttributeValue: ['String:String', 'Number:Number'],
84
+ AttributeValue: ['String', 'Number'],
31
85
  TagType: ['Identifier', 'GlobalIdentifier'],
32
86
  Terminal: ['Literal', 'Trivia', 'Escape'],
87
+ PropertyValue: ['Gap', 'Node', 'Null'],
33
88
  EmbeddedTerminal: ['Literal', 'Escape'],
89
+ Number: ['Integer', 'Infinity'],
34
90
  });
35
91
 
36
92
  const grammar = class CSTMLMiniparserGrammar {
37
93
  Fragment(p) {
38
94
  p.eatMatchTrivia(_);
39
- p.eatProduction('OpenFragmentNodeTag', { path: 'open' });
40
- p.eatProduction('FragmentChildren');
41
- p.eatProduction('CloseNodeTag', { path: 'close' });
42
- p.eatMatchTrivia(_);
43
- }
44
-
45
- FragmentChildren(p) {
95
+ p.eatProduction('OpenFragmentTag', { path: 'open' });
46
96
  p.eatMatchTrivia(_);
47
- while (p.match(/#['"]|\w/y) || p.atExpression) {
48
- if (p.match(/#['"]/y)) {
49
- p.eatProduction('Trivia', { path: 'children[]' });
50
- } else {
51
- p.eatProduction('Property', { path: 'children[]' });
52
- }
97
+ while (p.match(/<[^/]/y) || p.atExpression) {
98
+ p.eatProduction('Node', { path: 'root' });
53
99
  p.eatMatchTrivia(_);
54
100
  }
101
+ p.eatProduction('CloseFragmentTag', { path: 'close' });
102
+ p.eatMatchTrivia(_);
55
103
  }
56
104
 
57
105
  // @Node
58
- Node(p) {
59
- const openType = p.match('<|') ? 'OpenTerminalNodeTag' : 'OpenNodeTag';
60
- const childrenType = openType === 'OpenNodeTag' ? 'NodeChildren' : 'TerminalNodeChildren';
106
+ Document(p) {
107
+ p.eatProduction('DoctypeTag', { path: 'doctype' });
108
+ p.eatProduction('Fragment', { path: 'tree' });
109
+ }
61
110
 
62
- if (p.match('<>')) throw new Error('Fragment is not a node');
111
+ // @Node
112
+ DoctypeTag(p) {
113
+ p.eat('<!', PN, { path: 'open' });
114
+ p.eatProduction('UnsignedInteger', { path: 'version' });
115
+ p.eat(':', PN, { path: 'versionSeparator' });
116
+ p.eat('cstml', KW, { path: 'doctype' });
63
117
 
64
- const open = p.eatProduction(openType, { path: 'open' });
65
- if (!open.attributes.selfClosing) {
66
- p.eatProduction(childrenType);
67
- p.eatProduction('CloseNodeTag', { path: 'close' });
118
+ let sp = p.eatMatchTrivia(_);
119
+
120
+ if ((sp && p.match(/\w+/y)) || p.atExpression) {
121
+ p.eatProduction('Attributes');
122
+ sp = p.eatMatchTrivia(_);
68
123
  }
124
+
125
+ p.eat('>', PN, { path: 'close' });
126
+ }
127
+
128
+ // @Node
129
+ Null(p) {
130
+ p.eat('null', KW, { path: 'value' });
131
+ }
132
+
133
+ // @Node
134
+ Gap(p) {
135
+ p.eat('<//>', PN, { path: 'value' });
69
136
  }
70
137
 
71
- NodeChildren(p) {
72
- let properties = 0;
138
+ // @Node
139
+ Node(p) {
140
+ if (p.match('<>')) throw new Error('Fragment is not a node');
141
+
142
+ let open = p.eatProduction('OpenNodeTag', { path: 'open' });
73
143
 
74
144
  p.eatMatchTrivia(_);
75
- while (p.match(/#['"]|\w/y) || p.atExpression) {
76
- if (p.match(/#['"]/y)) {
77
- p.eatProduction('Trivia', { path: 'children[]' });
78
- } else {
79
- p.eatProduction('Property', { path: 'children[]' });
80
- properties++;
81
- }
145
+
146
+ if (open.properties.flags?.token) {
147
+ p.eatProduction('NodeChild', { path: 'children[]' }, { token: true });
82
148
  p.eatMatchTrivia(_);
149
+ } else {
150
+ while (!p.match('</')) {
151
+ p.eatProduction('NodeChild', { path: 'children[]' });
152
+ p.eatMatchTrivia(_);
153
+ }
83
154
  }
84
155
 
85
- if (!properties) throw new Error('Nodes must match text');
156
+ p.eatProduction('CloseNodeTag', { path: 'close' });
86
157
  }
87
158
 
88
- TerminalNodeChildren(p) {
89
- let properties = 0;
159
+ NodeChild(p, _, props) {
160
+ const { token } = props || {};
90
161
 
91
- p.eatMatchTrivia(_);
92
- while (p.match(/[!#]?['"]|\w/y) || p.atExpression) {
93
- if (p.match(/[!#]?['"]/y)) {
94
- p.eatProduction('Terminal', { path: 'children[]' });
95
- } else {
96
- p.eatProduction('Property', { path: 'children[]' });
97
- properties++;
162
+ if (token) {
163
+ p.eatProduction('Literal');
164
+ } else {
165
+ if (p.match(/<\*?#/y)) {
166
+ p.eatProduction('Node');
167
+ } else if (p.match(/\w/y)) {
168
+ p.eatProduction('Property');
169
+ } else if (p.match(/['"]/y)) {
170
+ p.eatProduction('Literal');
98
171
  }
99
- p.eatMatchTrivia(_);
100
172
  }
101
-
102
- if (!properties) throw new Error('Nodes must match text');
103
173
  }
104
174
 
105
175
  // @Node
106
176
  Property(p) {
107
- p.eat(/\w+/y, ID, { path: 'key' });
177
+ p.eatProduction('Reference', { path: 'reference' });
108
178
  p.eatMatchTrivia(_);
109
179
  p.eat(':', PN, { path: 'mapOperator' });
110
180
  p.eatMatchTrivia(_);
111
- p.eatProduction('Node', { path: 'value' });
181
+ p.eatProduction('PropertyValue', { path: 'value' });
182
+ }
183
+
184
+ PropertyValue(p) {
185
+ if (p.match('null')) {
186
+ p.eatProduction('Null');
187
+ } else if (p.match('<//>')) {
188
+ p.eatProduction('Gap');
189
+ } else {
190
+ p.eatProduction('Node');
191
+ }
112
192
  }
113
193
 
114
194
  // @Node
115
- OpenFragmentNodeTag(p) {
195
+ OpenFragmentTag(p) {
116
196
  p.eat('<', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
117
197
  p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
118
198
  }
@@ -120,30 +200,22 @@ const grammar = class CSTMLMiniparserGrammar {
120
200
  // @Node
121
201
  OpenNodeTag(p) {
122
202
  p.eat('<', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
123
- p.eatProduction('TagType', { path: 'type' });
124
203
 
125
- let sp = p.eatMatchTrivia(_);
126
-
127
- if ((sp && p.match(/\w+/y)) || p.atExpression) {
128
- p.eatProduction('Attributes');
129
- sp = p.eatMatchTrivia(_);
130
- }
204
+ let tr = p.eatMatch('#', PN, { path: 'triviaFlag' });
205
+ let tok = p.eatMatch('*', PN, { path: 'tokenFlag' });
206
+ let esc = p.eatMatch('@', PN, { path: 'escapeFlag' });
207
+ let exp = p.eatMatch('+', PN, { path: 'expressionFlag' });
131
208
 
132
- p.eatMatchTrivia(_);
133
- p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
134
- }
209
+ if ((tr && esc) || (exp && (tr || esc))) throw new Error();
135
210
 
136
- // @Node
137
- OpenTerminalNodeTag(p) {
138
- p.eat('<|', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
139
- p.eatMatchTrivia(_);
140
211
  p.eatProduction('TagType', { path: 'type' });
141
212
 
142
213
  let sp = p.eatMatchTrivia(_);
143
214
 
144
- let value;
145
- if (sp && p.match(/['"/]/y)) {
146
- value = p.eatProduction('EmbeddedTerminal', { path: 'value' });
215
+ let iv;
216
+ if (tok && sp && (p.match(/['"/]/y) || p.atExpression)) {
217
+ iv = p.eatProduction('String', { path: 'intrinsicValue' });
218
+
147
219
  sp = p.eatMatchTrivia(_);
148
220
  }
149
221
 
@@ -153,15 +225,21 @@ const grammar = class CSTMLMiniparserGrammar {
153
225
  }
154
226
 
155
227
  p.eatMatchTrivia(_);
156
- p.eat('|>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
157
-
158
- return { attrs: { selfClosing: !!value } };
228
+ if (iv) {
229
+ p.eat('/', PN, { path: 'selfClosingToken' });
230
+ }
231
+ p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
159
232
  }
160
233
 
161
234
  // @Node
162
235
  CloseNodeTag(p) {
163
236
  p.eat('</', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
164
- // TODO permit .type
237
+ p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
238
+ }
239
+
240
+ // @Node
241
+ CloseFragmentTag(p) {
242
+ p.eat('</', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
165
243
  p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
166
244
  }
167
245
 
@@ -184,7 +262,7 @@ const grammar = class CSTMLMiniparserGrammar {
184
262
 
185
263
  // @Cover
186
264
  Attribute(p) {
187
- if (p.match(/\w+\s*=/y)) {
265
+ if (p.match(/\w[\w-_]*\s*=/y)) {
188
266
  p.eatProduction('MappingAttribute');
189
267
  } else {
190
268
  p.eatProduction('BooleanAttribute');
@@ -193,6 +271,7 @@ const grammar = class CSTMLMiniparserGrammar {
193
271
 
194
272
  // @Node
195
273
  BooleanAttribute(p) {
274
+ p.eat('!', KW, { path: 'negated' });
196
275
  p.eat(/\w+/y, ID, { path: 'key' });
197
276
  }
198
277
 
@@ -208,31 +287,41 @@ const grammar = class CSTMLMiniparserGrammar {
208
287
  // @Cover
209
288
  AttributeValue(p) {
210
289
  if (p.match(/['"]/y)) {
211
- p.eatProduction('String:String');
212
- } else if (p.match(/-|\d/y)) {
213
- p.eatProduction('Number:Number');
290
+ p.eatProduction('String');
291
+ } else if (p.match(/[\d+-]/y)) {
292
+ p.eatProduction('Number');
214
293
  }
215
294
  }
216
295
 
217
- // @Cover
218
296
  TagType(p) {
219
- if (p.match(/\w+:/y)) {
220
- p.eatProduction('GlobalIdentifier');
297
+ if (p.match(/[\w.]+:/y)) {
298
+ p.eatProduction('LanguageReference', { path: 'language' });
299
+ p.eat(':', PN, { path: 'namespaceOperator' });
300
+ p.eatProduction('Identifier', { path: 'type' });
221
301
  } else {
222
- p.eat(/\w+/y, ID, { path: 'type' });
302
+ p.eatProduction('Identifier', { path: 'type' });
223
303
  }
224
304
  }
225
305
 
226
- // @Node
227
- GlobalIdentifier(p) {
228
- p.eat(/\w+/y, ID, { path: 'language' });
229
- p.eat(':', PN, { path: 'namespaceOperator' });
230
- p.eat(/\w+/y, ID, { path: 'type' });
306
+ LanguageReference(p) {
307
+ if (p.match(/['"]/y)) {
308
+ p.eatProduction('String');
309
+ } else {
310
+ p.eatProduction('IdentifierPath');
311
+ }
312
+ }
313
+
314
+ IdentifierPath(p) {
315
+ p.eatProduction('Identifier', { path: 'segments[]' });
316
+ while (p.match('.')) {
317
+ p.eat('.', PN, { path: 'separators[]' });
318
+ p.eatProduction('Identifier', { path: 'segments[]' });
319
+ }
231
320
  }
232
321
 
233
322
  // @Node
234
323
  Identifier(p) {
235
- p.eatLiteral(/\w+/y);
324
+ p.eatLiteral(/\w[\w-_]*/y);
236
325
  }
237
326
 
238
327
  // @Cover
@@ -248,6 +337,13 @@ const grammar = class CSTMLMiniparserGrammar {
248
337
  }
249
338
  }
250
339
 
340
+ // @Node
341
+ Reference(p) {
342
+ p.eatProduction('Identifier', { path: 'name' });
343
+ p.eatMatchTrivia(_);
344
+ p.eatMatch('[]', PN, { path: 'arrayOperator' });
345
+ }
346
+
251
347
  // @Cover
252
348
  EmbeddedTerminal(p) {
253
349
  if (p.match(/!['"]/y)) {
@@ -262,22 +358,91 @@ const grammar = class CSTMLMiniparserGrammar {
262
358
  // @Node
263
359
  Escape(p) {
264
360
  p.eat('!', PN, { path: 'escapeOperator' });
265
- p.eatProduction('String:String', { path: 'rawValue' });
361
+ p.eatProduction('String', { path: 'rawValue' });
266
362
  p.eatMatchTrivia(_);
267
363
  p.eat(':', PN, { path: 'rawOperator' });
268
- p.eatProduction('String:String', { path: 'value' });
364
+ p.eatProduction('String', { path: 'value' });
269
365
  }
270
366
 
271
367
  // @Node
272
368
  Trivia(p) {
273
369
  p.eat('#', PN, { path: 'trivializeOperator' });
274
- p.eatProduction('String:String', { path: 'value' });
370
+ p.eatProduction('String', { path: 'value' });
275
371
  }
276
372
 
277
373
  // @Node
278
374
  Literal(p) {
279
- p.eatProduction('String:String', { path: 'value' });
375
+ p.eatProduction('String', { path: 'value' });
376
+ }
377
+
378
+ Number(p) {
379
+ if (p.match(/-?\d/y)) {
380
+ p.eatProduction('Integer');
381
+ } else {
382
+ p.eatProduction('Infinity');
383
+ }
384
+ }
385
+
386
+ // @Node
387
+ Integer(p) {
388
+ p.eatMatch('-', 'Punctuator', { path: 'negative' });
389
+ p.eatProduction('Digits', { path: 'digits[]' });
390
+ }
391
+
392
+ // @Node
393
+ UnsignedInteger(p) {
394
+ p.eatProduction('Digits', { path: 'digits[]' });
395
+ }
396
+
397
+ // @Node
398
+ Infinity(p) {
399
+ p.eatMatch(/[+-]/, 'Punctuator', { path: 'sign' });
400
+ p.eat('Infinity', 'Keyword', { path: 'value' });
401
+ }
402
+
403
+ Digits(p) {
404
+ while (p.match(/\d/y)) {
405
+ p.eatProduction('Digit');
406
+ }
407
+ }
408
+
409
+ // @Node
410
+ Digit(p) {
411
+ p.eatLiteral(/\d/y);
412
+ }
413
+
414
+ // @Node
415
+ String(p) {
416
+ const q = p.match(/['"]/y) || '"';
417
+
418
+ const span = q === '"' ? 'Double' : 'Single';
419
+
420
+ p.eat(q, PN, { path: 'open', startSpan: span, balanced: q });
421
+ while (p.match(/./sy) || p.atExpression) {
422
+ p.eatProduction('Content', { path: 'content' });
423
+ }
424
+ p.eat(q, PN, { path: 'close', endSpan: span, balancer: true });
425
+ }
426
+
427
+ // @Node
428
+ Content(p) {
429
+ let esc, lit;
430
+ let i = 0;
431
+ do {
432
+ esc =
433
+ p.span.type === 'Single'
434
+ ? p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|[\\gnrt0'])/y)
435
+ : p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|[\\gnrt0"])/y);
436
+ lit =
437
+ p.span.type === 'Single'
438
+ ? p.eatMatchLiteral(/[^\r\n\0\\']+/y)
439
+ : p.eatMatchLiteral(/[^\r\n\0\\"]+/y);
440
+ i++;
441
+ } while (esc || lit);
442
+ if (i === 1 && !esc && !lit) {
443
+ throw new Error('Invalid string content');
444
+ }
280
445
  }
281
446
  };
282
447
 
283
- module.exports = { name, dependencies, covers, grammar };
448
+ module.exports = { name, canonicalURL, dependencies, covers, grammar, cookEscape, escapables };
@@ -1,6 +1,6 @@
1
1
  const Spamex = require('./spamex.js');
2
+ const CSTML = require('./cstml.js');
2
3
  const Regex = require('./regex.js');
3
- const StringLanguage = require('./string.js');
4
4
  const { node } = require('../symbols.js');
5
5
  const { buildCovers } = require('../utils.js');
6
6
 
@@ -12,7 +12,9 @@ const LIT = 'Literal';
12
12
 
13
13
  const name = 'Instruction';
14
14
 
15
- const dependencies = { Spamex, Regex, String: StringLanguage };
15
+ const canonicalURL = 'https://bablr.org/languages/core/bablr-vm-instruction';
16
+
17
+ const dependencies = { Spamex, CSTML, Regex };
16
18
 
17
19
  const covers = buildCovers({
18
20
  [node]: ['Call', 'Punctuator', 'Property', 'Expression'],
@@ -21,7 +23,8 @@ const covers = buildCovers({
21
23
  'Array',
22
24
  'Tuple',
23
25
  'Identifier',
24
- 'String:String',
26
+ 'CSTML:String',
27
+ 'CSTML:Gap',
25
28
  'Regex:Pattern',
26
29
  'Boolean',
27
30
  'Null',
@@ -47,7 +50,9 @@ const grammar = class InstructionMiniparserGrammar {
47
50
  } else if (p.match('(')) {
48
51
  p.eatProduction('Tuple');
49
52
  } else if (p.match(/['"]/y)) {
50
- p.eatProduction('String:String');
53
+ p.eatProduction('CSTML:String');
54
+ } else if (p.match('<//>')) {
55
+ p.eatProduction('CSTML:Gap');
51
56
  } else if (p.match('/')) {
52
57
  p.eatProduction('Regex:Pattern');
53
58
  } else if (p.match(/true|false/y)) {
@@ -133,4 +138,4 @@ const grammar = class InstructionMiniparserGrammar {
133
138
  }
134
139
  };
135
140
 
136
- module.exports = { name, dependencies, covers, grammar };
141
+ module.exports = { name, canonicalURL, dependencies, covers, grammar };