@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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/lib/index.js +38 -0
- package/lib/languages/cstml.js +131 -0
- package/lib/languages/instruction.js +135 -0
- package/lib/languages/regex.js +319 -0
- package/lib/languages/spamex.js +150 -0
- package/lib/languages/string.js +92 -0
- package/lib/match.js +84 -0
- package/lib/miniparser.js +434 -0
- package/lib/path.js +47 -0
- package/lib/symbols.js +4 -0
- package/lib/utils.js +119 -0
- package/package.json +43 -0
|
@@ -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 };
|