@bablr/boot 0.1.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.
@@ -0,0 +1,150 @@
1
+ const Regex = require('./regex.js');
2
+ const StringLanguage = require('./string.js');
3
+ const { buildCovers } = require('../utils.js');
4
+ const sym = require('../symbols.js');
5
+
6
+ const _ = /\s+/y;
7
+ const PN = 'Punctuator';
8
+ const KW = 'Keyword';
9
+ const ID = 'Identifier';
10
+ const LIT = 'Literal';
11
+
12
+ const name = 'Spamex';
13
+
14
+ const dependencies = { Regex, String: StringLanguage };
15
+
16
+ const covers = buildCovers({
17
+ [sym.node]: ['Attribute', 'TagType', 'Matcher', 'Literal'],
18
+ Attribute: ['StringAttribute', 'BooleanAttribute'],
19
+ Matcher: ['NodeMatcher', 'TerminalMatcher', 'TriviaTerminalMatcher', 'StringMatcher'],
20
+ StringMatcher: ['String:String', 'Regex:Pattern'],
21
+ TagType: ['Identifier', 'GlobalIdentifier'],
22
+ });
23
+
24
+ const grammar = class SpamexMiniparserGrammar {
25
+ // @Cover
26
+ Matcher(p) {
27
+ if (p.match('<| |>')) {
28
+ p.eatProduction('TriviaTerminalMatcher');
29
+ } else if (p.match(/<(?:\w|$)/y)) {
30
+ p.eatProduction('NodeMatcher');
31
+ } else if (p.match('<|')) {
32
+ p.eatProduction('TerminalMatcher');
33
+ } else if (p.match(/['"]/y)) {
34
+ p.eatProduction('String:String');
35
+ } else if (p.match('/')) {
36
+ p.eatProduction('Regex:Pattern');
37
+ } else {
38
+ throw new Error(`Unexpected character ${p.chr}`);
39
+ }
40
+ }
41
+
42
+ // @Node
43
+ TriviaTerminalMatcher(p) {
44
+ p.eat('<|', PN, { path: 'open', startSpan: 'Tag', balanced: '|>' });
45
+ p.eat(' ', KW, { path: 'value' });
46
+ p.eat('|>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
47
+ }
48
+
49
+ // @Node
50
+ NodeMatcher(p) {
51
+ p.eat('<', PN, { path: 'open', startSpan: 'Tag', balanced: '>' });
52
+ p.eatProduction('TagType', { path: 'type' });
53
+
54
+ let sp = p.eatMatchTrivia(_);
55
+
56
+ if ((sp && p.match(/\w+/y)) || p.atExpression) {
57
+ p.eatProduction('Attributes', { path: '[attributes]' });
58
+ sp = p.eatMatchTrivia(_);
59
+ }
60
+
61
+ p.eatMatchTrivia(_);
62
+ p.eat('>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
63
+ }
64
+
65
+ // @Node
66
+ TerminalMatcher(p) {
67
+ p.eat('<|', PN, { path: 'open', startSpan: 'Tag', balanced: '|>' });
68
+ p.eatMatchTrivia(_);
69
+ p.eatProduction('TagType', { path: 'type' });
70
+ let sp = p.eatMatchTrivia(_);
71
+
72
+ if (sp && p.match(/['"/]/y)) {
73
+ p.eatProduction('StringMatcher', { path: 'value' });
74
+ sp = p.eatMatchTrivia(_);
75
+ }
76
+
77
+ if (sp && p.match(/\w+/y)) {
78
+ p.eatProduction('Attributes', { path: '[attributes]' });
79
+ sp = p.eatMatchTrivia(_);
80
+ }
81
+
82
+ p.eatMatchTrivia(_);
83
+ p.eat('|>', PN, { path: 'close', endSpan: 'Tag', balancer: true });
84
+ }
85
+
86
+ Attributes(p) {
87
+ let sp = true;
88
+ while (sp && (p.match(/\w+/y) || p.atExpression)) {
89
+ p.eatProduction('Attribute');
90
+ if (p.match(/\s+\w/y)) {
91
+ sp = p.eatMatchTrivia(_);
92
+ }
93
+ }
94
+ }
95
+
96
+ // @Cover
97
+ Attribute(p) {
98
+ if (p.match(/\w+\s*=/y)) {
99
+ p.eatProduction('StringAttribute');
100
+ } else {
101
+ p.eatProduction('BooleanAttribute');
102
+ }
103
+ }
104
+
105
+ // @Node
106
+ BooleanAttribute(p) {
107
+ p.eat(/\w+/y, LIT, { path: 'key' });
108
+ }
109
+
110
+ // @Node
111
+ StringAttribute(p) {
112
+ p.eat(/\w+/y, LIT, { path: 'key' });
113
+ p.eatMatchTrivia(_);
114
+ p.eat('=', PN, { path: 'mapOperator' });
115
+ p.eatMatchTrivia(_);
116
+ p.eatProduction('String:String', { path: 'value' });
117
+ }
118
+
119
+ // @Cover
120
+ TagType(p) {
121
+ if (p.match(/\w+:/y)) {
122
+ p.eatProduction('GlobalIdentifier');
123
+ } else {
124
+ p.eat(/\w+/y, ID, { path: 'type' });
125
+ }
126
+ }
127
+
128
+ // @Node
129
+ GlobalIdentifier(p) {
130
+ p.eat(/\w+/y, ID, { path: 'language' });
131
+ p.eat(':', PN, { path: 'namespaceOperator' });
132
+ p.eat(/\w+/y, ID, { path: 'type' });
133
+ }
134
+
135
+ // @Cover
136
+ StringMatcher(p) {
137
+ if (p.match(/['"]/y)) {
138
+ p.eatProduction('String:String');
139
+ } else {
140
+ p.eatProduction('Regex:Pattern');
141
+ }
142
+ }
143
+
144
+ // @Node
145
+ Identifier(p) {
146
+ p.eatLiteral(/\w+/y);
147
+ }
148
+ };
149
+
150
+ module.exports = { name, dependencies, covers, grammar };
@@ -0,0 +1,92 @@
1
+ const objectEntries = require('iter-tools-es/methods/object-entries');
2
+ const { buildCovers } = require('../utils.js');
3
+ const sym = require('../symbols.js');
4
+
5
+ const name = 'String';
6
+
7
+ const dependencies = {};
8
+
9
+ const covers = buildCovers({
10
+ [sym.node]: ['String', 'Content'],
11
+ });
12
+
13
+ const PN = 'Punctuator';
14
+
15
+ const escapables = new Map(
16
+ objectEntries({
17
+ n: '\n'.codePointAt(0),
18
+ r: '\r'.codePointAt(0),
19
+ t: '\t'.codePointAt(0),
20
+ 0: '\0'.codePointAt(0),
21
+ }),
22
+ );
23
+
24
+ const cookEscape = (escape, span) => {
25
+ let hexMatch;
26
+
27
+ if (!span.type.startsWith('String')) {
28
+ throw new Error();
29
+ }
30
+
31
+ if (!escape.startsWith('\\')) {
32
+ throw new Error('string escape must start with \\');
33
+ }
34
+
35
+ if ((hexMatch = /\\x([0-9a-f]{2})/iy.exec(escape))) {
36
+ //continue
37
+ } else if ((hexMatch = /\\u([0-9a-f]{4})/iy.exec(escape))) {
38
+ //continue
39
+ } else if ((hexMatch = /\\u{([0-9a-f]+)}/iy.exec(escape))) {
40
+ //continue
41
+ }
42
+
43
+ if (hexMatch) {
44
+ return parseInt(hexMatch[1], 16);
45
+ }
46
+
47
+ const litPattern = span === 'String:Single' ? /\\([\\nrt0'])/y : /\\([\\nrt0"])/y;
48
+ const litMatch = litPattern.exec(escape);
49
+
50
+ if (litMatch) {
51
+ return escapables.get(litMatch[1]);
52
+ }
53
+
54
+ throw new Error('unable to cook string escape');
55
+ };
56
+
57
+ const grammar = class StringMiniparserGrammar {
58
+ // @Node
59
+ String(p) {
60
+ const q = p.match(/['"]/y) || '"';
61
+
62
+ const span = q === '"' ? 'Double' : 'Single';
63
+
64
+ p.eat(q, PN, { path: 'open', startSpan: span, balanced: q });
65
+ while (p.match(/./sy) || p.atExpression) {
66
+ p.eatProduction('Content', { path: 'content' });
67
+ }
68
+ p.eat(q, PN, { path: 'close', endSpan: span, balancer: true });
69
+ }
70
+
71
+ // @Node
72
+ Content(p) {
73
+ let esc, lit;
74
+ let i = 0;
75
+ do {
76
+ esc =
77
+ p.span.type === 'Single'
78
+ ? p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|x[0-9a-fA-F]{2}|[\\nrt0'])/y)
79
+ : p.eatMatchEscape(/\\(u(\{\d{1,6}\}|\d{4})|x[0-9a-fA-F]{2}|[\\nrt0"])/y);
80
+ lit =
81
+ p.span.type === 'Single'
82
+ ? p.eatMatchLiteral(/[^\r\n\\']+/y)
83
+ : p.eatMatchLiteral(/[^\r\n\\"]+/y);
84
+ i++;
85
+ } while (esc || lit);
86
+ if (i === 1 && !esc && !lit) {
87
+ throw new Error('Invalid string content');
88
+ }
89
+ }
90
+ };
91
+
92
+ module.exports = { name, dependencies, covers, grammar, escapables, cookEscape };
package/lib/match.js ADDED
@@ -0,0 +1,84 @@
1
+ const { Path, buildNode } = require('./path.js');
2
+ const { resolveDependentLanguage } = require('./utils.js');
3
+ const sym = require('./symbols.js');
4
+
5
+ class Match {
6
+ constructor(parent, resolvedLanguage, id, attributes, path) {
7
+ this.parent = parent;
8
+ this.resolvedLanguage = resolvedLanguage;
9
+ this.id = id;
10
+ this.attributes = attributes;
11
+ this.path = path;
12
+
13
+ this.grammar =
14
+ parent?.resolvedLanguage === resolvedLanguage
15
+ ? parent.grammar
16
+ : new resolvedLanguage.grammar();
17
+ }
18
+
19
+ get isNode() {
20
+ const { type, resolvedLanguage } = this;
21
+ const { covers } = resolvedLanguage;
22
+
23
+ return covers.get(sym.node).has(type) && !covers.has(type);
24
+ }
25
+
26
+ get language() {
27
+ return this.id.language;
28
+ }
29
+
30
+ get type() {
31
+ return this.id.type;
32
+ }
33
+
34
+ get attrs() {
35
+ return this.attributes;
36
+ }
37
+
38
+ static from(language, id, attrs = {}) {
39
+ const resolvedLanguage = resolveDependentLanguage(language, id.language);
40
+ const { covers } = resolvedLanguage;
41
+ const { type } = id;
42
+ const isCover = covers.has(type);
43
+ const isNode = covers.get(sym.node).has(type);
44
+ const isFragment = covers.get(sym.fragment)?.has(type);
45
+
46
+ if (!isNode && !isFragment) {
47
+ throw new Error(`Top {type: ${type}} must be a node or fragment`);
48
+ }
49
+
50
+ const path = Path.from(id, attrs);
51
+
52
+ if (isFragment || (isNode && !isCover)) {
53
+ path.node = buildNode(id);
54
+ }
55
+
56
+ return new Match(null, resolvedLanguage, id, attrs, path);
57
+ }
58
+
59
+ generate(id, attrs) {
60
+ const resolvedLanguage = resolveDependentLanguage(this.resolvedLanguage, id.language);
61
+ const { covers } = resolvedLanguage;
62
+ const { type } = id;
63
+ const isCover = covers.has(type);
64
+ const isNode = covers.get(sym.node).has(type) && !isCover;
65
+
66
+ const baseAttrs = this.isNode ? {} : this.attrs;
67
+
68
+ let { path } = this;
69
+
70
+ if (isNode) {
71
+ if (!path.node) {
72
+ const relativeType = id.language === this.language ? id.type : `${id.language}:${id.type}`;
73
+ if (!this.resolvedLanguage.covers.get(path.type).has(relativeType)) throw new Error();
74
+ } else {
75
+ path = path.generate(id, attrs);
76
+ }
77
+ path.node = buildNode(id);
78
+ }
79
+
80
+ return new Match(this, resolvedLanguage, id, { ...baseAttrs, ...attrs }, path);
81
+ }
82
+ }
83
+
84
+ module.exports = { Match };