@bablr/language-en-regex-vm-pattern 0.7.1 → 0.8.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/grammar.js +147 -10216
- package/lib/grammar.macro.js +121 -118
- package/package.json +15 -8
package/lib/grammar.macro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { re, spam as m } from '@bablr/boot';
|
|
2
2
|
import {
|
|
3
3
|
Node,
|
|
4
4
|
CoveredBy,
|
|
@@ -8,7 +8,16 @@ import {
|
|
|
8
8
|
} from '@bablr/helpers/decorators';
|
|
9
9
|
import objectEntries from 'iter-tools-es/methods/object-entries';
|
|
10
10
|
import * as Shared from '@bablr/helpers/productions';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
eat,
|
|
13
|
+
eatMatch,
|
|
14
|
+
match,
|
|
15
|
+
holdForMatch,
|
|
16
|
+
guard,
|
|
17
|
+
bindAttribute,
|
|
18
|
+
fail,
|
|
19
|
+
} from '@bablr/helpers/grammar';
|
|
20
|
+
import { buildString, buildBoolean } from '@bablr/helpers/builders';
|
|
12
21
|
|
|
13
22
|
export const canonicalURL = 'https://bablr.org/languages/core/en/bablr-regex-pattern';
|
|
14
23
|
|
|
@@ -66,17 +75,17 @@ const getSpecialPattern = (span) => {
|
|
|
66
75
|
export const grammar = class RegexGrammar {
|
|
67
76
|
@Node
|
|
68
77
|
*Pattern() {
|
|
69
|
-
yield
|
|
70
|
-
yield
|
|
71
|
-
yield
|
|
72
|
-
yield
|
|
78
|
+
yield eat(m`openToken: <*Punctuator '/' { balanced: '/', balancedSpan: 'Pattern' } />`);
|
|
79
|
+
yield eat(m`<Alternatives />`);
|
|
80
|
+
yield eat(m`closeToken: <*Punctuator '/' { balancer: true } />`);
|
|
81
|
+
yield eat(m`flags$: <Flags />`);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
@UnboundAttributes(Object.keys(flagCharacters))
|
|
76
85
|
@AllowEmpty
|
|
77
86
|
@Node
|
|
78
87
|
*Flags({ ctx }) {
|
|
79
|
-
const flags = yield
|
|
88
|
+
const flags = yield match(re`/[gimsuy]+/`);
|
|
80
89
|
|
|
81
90
|
const flagsStr = ctx.sourceTextFor(flags) || '';
|
|
82
91
|
|
|
@@ -84,115 +93,115 @@ export const grammar = class RegexGrammar {
|
|
|
84
93
|
|
|
85
94
|
for (const { 0: name, 1: chr } of Object.entries(flagCharacters)) {
|
|
86
95
|
if (flagsStr.includes(chr)) {
|
|
87
|
-
yield
|
|
96
|
+
yield bindAttribute(name, true);
|
|
88
97
|
} else {
|
|
89
|
-
yield
|
|
98
|
+
yield bindAttribute(name, false);
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
for (const flagChr of flagsStr) {
|
|
94
|
-
yield
|
|
103
|
+
yield eat(m`tokens[]: <*Keyword ${buildString(flagChr)} />`);
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
@AllowEmpty
|
|
99
108
|
*Alternatives() {
|
|
100
109
|
do {
|
|
101
|
-
yield
|
|
102
|
-
} while (yield
|
|
110
|
+
yield eat(m`alternatives[]$: <Alternative />`);
|
|
111
|
+
} while (yield eatMatch(m`separatorTokens[]: <*Punctuator '|' />`));
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
@AllowEmpty
|
|
106
115
|
@Node
|
|
107
116
|
*Alternative() {
|
|
108
|
-
yield
|
|
117
|
+
yield eat(m`elements[]$: <Elements />`);
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
@AllowEmpty
|
|
112
121
|
*Elements() {
|
|
113
|
-
yield
|
|
114
|
-
while (yield
|
|
115
|
-
yield
|
|
122
|
+
yield eat(m`.[]: []`);
|
|
123
|
+
while (yield match(re`/[^|]/`)) {
|
|
124
|
+
yield eat(m`.[]+: <Element />`);
|
|
116
125
|
}
|
|
117
126
|
}
|
|
118
127
|
|
|
119
128
|
*Element() {
|
|
120
|
-
yield
|
|
121
|
-
|
|
122
|
-
yield
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (yield
|
|
132
|
-
return
|
|
129
|
+
yield guard(m`<*Keyword /[*+?]/ />`);
|
|
130
|
+
|
|
131
|
+
yield eat(m`<Any />`, [
|
|
132
|
+
m`<CharacterClass '[' />`,
|
|
133
|
+
m`<Group '(?:' />`,
|
|
134
|
+
m`<Assertion /[$^]|\\b/i />`,
|
|
135
|
+
m`<Gap '\\g' />`,
|
|
136
|
+
m`<CharacterSet /\.|\\[dswp]/i />`,
|
|
137
|
+
m`<*Character />`,
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
if (yield match(re`/[*+?{]/`)) {
|
|
141
|
+
return holdForMatch(m`<Quantifier />`);
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
@CoveredBy('Element')
|
|
137
146
|
@Node
|
|
138
147
|
*Group() {
|
|
139
|
-
yield
|
|
140
|
-
yield
|
|
141
|
-
yield
|
|
148
|
+
yield eat(m`openToken: <*Punctuator '(?:' { balanced: ')' } />`);
|
|
149
|
+
yield eat(m`<Alternatives />`);
|
|
150
|
+
yield eat(m`closeToken: <*Punctuator ')' { balancer: true } />`);
|
|
142
151
|
}
|
|
143
152
|
|
|
144
153
|
@Node
|
|
145
154
|
*CapturingGroup() {
|
|
146
|
-
yield
|
|
147
|
-
yield
|
|
148
|
-
yield
|
|
155
|
+
yield eat(m`openToken: <*Punctuator '(' { balanced: ')' } />`);
|
|
156
|
+
yield eat(m`<Alternatives />`);
|
|
157
|
+
yield eat(m`closeToken: <*Punctuator ')' { balancer: true } />`);
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
@CoveredBy('Element')
|
|
152
161
|
*Assertion() {
|
|
153
|
-
yield
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
])
|
|
162
|
+
yield eat(m`<Any />`, [
|
|
163
|
+
m`<*StartOfInputAssertion '^' />`,
|
|
164
|
+
m`<*EndOfInputAssertion '$' />`,
|
|
165
|
+
m`<*WordBoundaryAssertion /\\b/i />`,
|
|
166
|
+
]);
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
@CoveredBy('Assertion')
|
|
161
170
|
@Node
|
|
162
171
|
*StartOfInputAssertion() {
|
|
163
|
-
yield
|
|
172
|
+
yield eat(m`sigilToken: <*Keyword '^' />`);
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
@CoveredBy('Assertion')
|
|
167
176
|
@Node
|
|
168
177
|
*EndOfInputAssertion() {
|
|
169
|
-
yield
|
|
178
|
+
yield eatMatch(m`sigilToken: <*Keyword '$' />`);
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
@UnboundAttributes(['negate'])
|
|
173
182
|
@CoveredBy('Assertion')
|
|
174
183
|
@Node
|
|
175
184
|
*WordBoundaryAssertion({ ctx }) {
|
|
176
|
-
yield
|
|
177
|
-
const
|
|
178
|
-
yield
|
|
185
|
+
yield eatMatch(m`escapeToken: <*Punctuator '\\' />`);
|
|
186
|
+
const m_ = yield eat(m`value: <*Keyword /b/i />`);
|
|
187
|
+
yield bindAttribute('negate', buildBoolean(ctx.sourceTextFor(m_) === 'B'));
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
@CoveredBy('Assertion')
|
|
182
191
|
@Node
|
|
183
192
|
*Gap() {
|
|
184
|
-
yield
|
|
185
|
-
yield
|
|
193
|
+
yield eatMatch(m`escapeToken: <*Punctuator '\\' />`);
|
|
194
|
+
yield eat(m`value: <*Keyword 'g' />`);
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
@CoveredBy('Element')
|
|
189
198
|
@CoveredBy('CharacterClassElement')
|
|
190
199
|
@Node
|
|
191
200
|
*Character() {
|
|
192
|
-
if (yield
|
|
193
|
-
yield
|
|
201
|
+
if (yield match('\\')) {
|
|
202
|
+
yield eat(m`@: <EscapeSequence />`);
|
|
194
203
|
} else {
|
|
195
|
-
yield
|
|
204
|
+
yield eat(re`/[^\r\n\t]/`);
|
|
196
205
|
}
|
|
197
206
|
}
|
|
198
207
|
|
|
@@ -200,96 +209,92 @@ export const grammar = class RegexGrammar {
|
|
|
200
209
|
@CoveredBy('Element')
|
|
201
210
|
@Node
|
|
202
211
|
*CharacterClass() {
|
|
203
|
-
yield
|
|
212
|
+
yield eat(m`openToken: <*Punctuator '[' { balancedSpan: 'CharacterClass', balanced: ']' } />`);
|
|
204
213
|
|
|
205
|
-
let
|
|
214
|
+
let negate = yield eatMatch(m`negateToken: <*Keyword '^' />`);
|
|
206
215
|
|
|
207
|
-
yield
|
|
216
|
+
yield bindAttribute('negate', !!negate);
|
|
208
217
|
|
|
209
|
-
while (yield
|
|
210
|
-
yield
|
|
218
|
+
while (yield match(re`/./s`)) {
|
|
219
|
+
yield eat(m`elements[]+$: <CharacterClassElement />`);
|
|
211
220
|
}
|
|
212
221
|
|
|
213
|
-
yield
|
|
222
|
+
yield eat(m`closeToken: <*Punctuator ']' { balancer: true } />`);
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
*CharacterClassElement() {
|
|
217
|
-
yield
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
])`;
|
|
222
|
-
|
|
223
|
-
if (yield i`match('-')`) {
|
|
224
|
-
return i`holdForMatch(<+CharacterClassRange />)`;
|
|
226
|
+
yield eat(m`<Any />`, [m`<CharacterSet /\\[dswp]/i />`, m`<Gap '\\g' />`, m`<*Character />`]);
|
|
227
|
+
|
|
228
|
+
if (yield match('-')) {
|
|
229
|
+
return holdForMatch(m`<CharacterClassRange />`);
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
@CoveredBy('CharacterClassElement')
|
|
229
234
|
@Node
|
|
230
235
|
*CharacterClassRange() {
|
|
231
|
-
yield
|
|
232
|
-
yield
|
|
233
|
-
yield
|
|
236
|
+
yield eat(m`min+$: <*Character />`);
|
|
237
|
+
yield eat(m`sigilToken: <*Punctuator '-' />`);
|
|
238
|
+
yield eat(m`max+$: <*Character />`);
|
|
234
239
|
}
|
|
235
240
|
|
|
236
241
|
@CoveredBy('Element')
|
|
237
242
|
*CharacterSet() {
|
|
238
|
-
yield
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
])
|
|
243
|
+
yield eat(m`<Any />`, [
|
|
244
|
+
m`<AnyCharacterSet '.' />`,
|
|
245
|
+
m`<DigitCharacterSet /\\[dD]/ />`,
|
|
246
|
+
m`<SpaceCharacterSet /\\[sS]/ />`,
|
|
247
|
+
m`<WordCharacterSet /\\[wW]/ />`,
|
|
248
|
+
]);
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
@CoveredBy('CharacterSet')
|
|
247
252
|
@Node
|
|
248
253
|
*AnyCharacterSet() {
|
|
249
|
-
yield
|
|
254
|
+
yield eat(m`sigilToken: <*Keyword '.' />`);
|
|
250
255
|
}
|
|
251
256
|
|
|
252
257
|
@UnboundAttributes(['negate'])
|
|
253
258
|
@CoveredBy('CharacterSet')
|
|
254
259
|
@Node
|
|
255
260
|
*DigitCharacterSet({ ctx }) {
|
|
256
|
-
yield
|
|
261
|
+
yield eat(m`escapeToken: <*Punctuator '\\' />`);
|
|
257
262
|
|
|
258
|
-
let code = yield
|
|
263
|
+
let code = yield eat(m`value: <*Keyword /[dD]/ />`);
|
|
259
264
|
|
|
260
|
-
yield
|
|
265
|
+
yield bindAttribute('negate', buildBoolean(ctx.sourceTextFor(code) === 'D'));
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
@UnboundAttributes(['negate'])
|
|
264
269
|
@CoveredBy('CharacterSet')
|
|
265
270
|
@Node
|
|
266
271
|
*SpaceCharacterSet({ ctx }) {
|
|
267
|
-
yield
|
|
272
|
+
yield eat(m`escapeToken: <*Punctuator '\\' />`);
|
|
268
273
|
|
|
269
|
-
let code = yield
|
|
274
|
+
let code = yield eat(m`value: <*Keyword /[sS]/ />`);
|
|
270
275
|
|
|
271
|
-
yield
|
|
276
|
+
yield bindAttribute('negate', buildBoolean(ctx.sourceTextFor(code) === 'S'));
|
|
272
277
|
}
|
|
273
278
|
|
|
274
279
|
@UnboundAttributes(['negate'])
|
|
275
280
|
@CoveredBy('CharacterSet')
|
|
276
281
|
@Node
|
|
277
282
|
*WordCharacterSet({ ctx }) {
|
|
278
|
-
yield
|
|
283
|
+
yield eat(m`escapeToken: <*Punctuator '\\' />`);
|
|
279
284
|
|
|
280
|
-
let code = yield
|
|
285
|
+
let code = yield eat(m`value: <*Keyword /[wW]/ />`);
|
|
281
286
|
|
|
282
|
-
yield
|
|
287
|
+
yield bindAttribute('negate', ctx.sourceTextFor(code) === 'W');
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
@UnboundAttributes(['min', 'max'])
|
|
286
291
|
@Node
|
|
287
292
|
*Quantifier({ ctx }) {
|
|
288
|
-
yield
|
|
293
|
+
yield eat(m`element+$: <Element />`);
|
|
289
294
|
|
|
290
295
|
let attrs, sigil;
|
|
291
296
|
|
|
292
|
-
if ((sigil = yield
|
|
297
|
+
if ((sigil = yield eatMatch(m`sigilToken: <*Keyword /[*+?]/ />`))) {
|
|
293
298
|
switch (ctx.sourceTextFor(sigil)) {
|
|
294
299
|
case '*':
|
|
295
300
|
attrs = { min: 0, max: Infinity };
|
|
@@ -301,12 +306,12 @@ export const grammar = class RegexGrammar {
|
|
|
301
306
|
attrs = { min: 0, max: 1 };
|
|
302
307
|
break;
|
|
303
308
|
}
|
|
304
|
-
} else if (yield
|
|
309
|
+
} else if (yield eat(m`openToken: <*Punctuator '{' { balanced: '}' } />`)) {
|
|
305
310
|
let max;
|
|
306
|
-
let min = yield
|
|
311
|
+
let min = yield eat(m`min$: <*UnsignedInteger />`);
|
|
307
312
|
|
|
308
|
-
if (yield
|
|
309
|
-
max = yield
|
|
313
|
+
if (yield eatMatch(m`separator: <*Punctuator ',' />`)) {
|
|
314
|
+
max = yield eatMatch(m`max$: <*UnsignedInteger />`);
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
min = min && ctx.sourceTextFor(min);
|
|
@@ -317,65 +322,63 @@ export const grammar = class RegexGrammar {
|
|
|
317
322
|
|
|
318
323
|
attrs = { min, max };
|
|
319
324
|
|
|
320
|
-
yield
|
|
325
|
+
yield eat(m`closeToken: <*Punctuator '}' { balancer: true } />`);
|
|
321
326
|
}
|
|
322
327
|
|
|
323
|
-
yield
|
|
324
|
-
yield
|
|
328
|
+
yield bindAttribute('min', attrs.min);
|
|
329
|
+
yield bindAttribute('max', attrs.max);
|
|
325
330
|
}
|
|
326
331
|
|
|
327
332
|
@Node
|
|
328
333
|
*UnsignedInteger() {
|
|
329
|
-
yield
|
|
334
|
+
yield eat(re`/\d+/`);
|
|
330
335
|
}
|
|
331
336
|
|
|
332
337
|
@Node
|
|
333
338
|
*EscapeSequence({ state, ctx, value: props }) {
|
|
334
339
|
const parentSpan = state.span;
|
|
335
340
|
|
|
336
|
-
yield
|
|
341
|
+
yield eat(m`escape: <*Punctuator '\\' { openSpan: 'Escape' } />`);
|
|
337
342
|
|
|
338
|
-
let
|
|
343
|
+
let m_;
|
|
339
344
|
|
|
340
|
-
if ((
|
|
341
|
-
const match_ = ctx.sourceTextFor(
|
|
342
|
-
yield
|
|
343
|
-
} else if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
yield
|
|
348
|
-
} else if (yield i`match(/[ux]/)`) {
|
|
349
|
-
yield i`eat(<EscapeCode closeSpan='Escape' /> 'code')`;
|
|
345
|
+
if ((m_ = yield match(re`/[\\/nrt0]/`))) {
|
|
346
|
+
const match_ = ctx.sourceTextFor(m_);
|
|
347
|
+
yield eat(m`code: <*Keyword ${buildString(match_)} { closeSpan: 'Escape' } />`);
|
|
348
|
+
} else if ((m_ = yield match(getSpecialPattern(parentSpan)))) {
|
|
349
|
+
const match_ = ctx.sourceTextFor(m_);
|
|
350
|
+
yield eat(m`code: <*Keyword ${buildString(match_)} { closeSpan: 'Escape' } />`);
|
|
351
|
+
} else if (yield match(re`/[ux]/`)) {
|
|
352
|
+
yield eat(m`code: <EscapeCode { closeSpan: 'Escape' } />`);
|
|
350
353
|
} else {
|
|
351
|
-
yield
|
|
354
|
+
yield fail();
|
|
352
355
|
}
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
@Node
|
|
356
359
|
*EscapeCode() {
|
|
357
|
-
if (yield
|
|
358
|
-
if (yield
|
|
359
|
-
yield
|
|
360
|
-
yield
|
|
360
|
+
if (yield eatMatch(m`type: <*Keyword 'u' />`)) {
|
|
361
|
+
if (yield eatMatch(m`openToken: <*Punctuator '{' />`)) {
|
|
362
|
+
yield eatMatch(m`value$: <*UnsignedInteger />`);
|
|
363
|
+
yield eat(m`closeToken: <*Punctuator '}' />`);
|
|
361
364
|
} else {
|
|
362
|
-
yield
|
|
363
|
-
yield
|
|
365
|
+
yield eat(m`value$: <*UnsignedInteger /\d{4}/ />`);
|
|
366
|
+
yield eat(m`closeToken: null`);
|
|
364
367
|
}
|
|
365
|
-
} else if (yield
|
|
366
|
-
yield
|
|
367
|
-
yield
|
|
368
|
-
yield
|
|
368
|
+
} else if (yield eatMatch(m`type: <*Keyword 'x' />`)) {
|
|
369
|
+
yield eat(m`openToken: null`);
|
|
370
|
+
yield eat(m`value$: <*UnsignedInteger /\d{2}/ />`);
|
|
371
|
+
yield eat(m`closeToken: null`);
|
|
369
372
|
}
|
|
370
373
|
}
|
|
371
374
|
|
|
372
375
|
*Digits() {
|
|
373
|
-
while (yield
|
|
376
|
+
while (yield eatMatch(m`<*Digit />`));
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
@Node
|
|
377
380
|
*Digit() {
|
|
378
|
-
yield
|
|
381
|
+
yield eat(re`/\d/`);
|
|
379
382
|
}
|
|
380
383
|
|
|
381
384
|
@InjectFrom(Shared)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/language-en-regex-vm-pattern",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A BABLR language for nonbacktracking JS-style regexes",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=12.0.0"
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
".": "./lib/grammar.js",
|
|
11
11
|
"./package.json": "./package.json"
|
|
12
12
|
},
|
|
13
|
-
"files": [
|
|
13
|
+
"files": [
|
|
14
|
+
"lib/**/*.js"
|
|
15
|
+
],
|
|
14
16
|
"sideEffects": false,
|
|
15
17
|
"scripts": {
|
|
16
18
|
"build": "macrome build",
|
|
@@ -20,13 +22,13 @@
|
|
|
20
22
|
},
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"@babel/runtime": "^7.23.2",
|
|
23
|
-
"@bablr/helpers": "
|
|
24
|
-
"@bablr/agast-helpers": "
|
|
25
|
-
"@bablr/agast-vm-helpers": "
|
|
26
|
-
"iter-tools-es": "
|
|
25
|
+
"@bablr/helpers": "0.21.1",
|
|
26
|
+
"@bablr/agast-helpers": "0.6.0",
|
|
27
|
+
"@bablr/agast-vm-helpers": "0.6.0",
|
|
28
|
+
"iter-tools-es": "7.5.3"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
|
-
"@bablr/boot": "^0.
|
|
31
|
+
"@bablr/boot": "^0.7.0",
|
|
30
32
|
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#49f5952efed27f94ee9b94340eb1563c440bf64e",
|
|
31
33
|
"@bablr/macrome": "^0.1.3",
|
|
32
34
|
"@bablr/macrome-generator-bablr": "^0.3.2",
|
|
@@ -40,7 +42,12 @@
|
|
|
40
42
|
"mocha": "^10.4.0",
|
|
41
43
|
"prettier": "^2.0.5"
|
|
42
44
|
},
|
|
43
|
-
"keywords": [
|
|
45
|
+
"keywords": [
|
|
46
|
+
"bablr-language",
|
|
47
|
+
"grammar",
|
|
48
|
+
"english",
|
|
49
|
+
"regex"
|
|
50
|
+
],
|
|
44
51
|
"repository": "git@github.com:bablr-lang/language-en-regex-vm-pattern.git",
|
|
45
52
|
"homepage": "https://github.com/bablr-lang/language-en-regex-vm-pattern",
|
|
46
53
|
"author": "Conrad Buck <conartist6@gmail.com>",
|