grammatik 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 +443 -0
- package/bin/grammatik.js +115 -0
- package/dist/cjs/index.js +3010 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/runtime.js +639 -0
- package/dist/esm/index.js +2959 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/runtime.js +605 -0
- package/dist/types/index.d.ts +214 -0
- package/dist/types/runtime.d.ts +40 -0
- package/package.json +65 -0
- package/src/analyze.d.ts +78 -0
- package/src/analyze.js +602 -0
- package/src/codegen.d.ts +36 -0
- package/src/codegen.js +635 -0
- package/src/definition.d.ts +133 -0
- package/src/definition.js +291 -0
- package/src/emit.d.ts +23 -0
- package/src/emit.js +374 -0
- package/src/firstchars.d.ts +6 -0
- package/src/firstchars.js +353 -0
- package/src/index.d.ts +7 -0
- package/src/index.js +15 -0
- package/src/lexer.d.ts +96 -0
- package/src/lexer.js +358 -0
- package/src/methods.d.ts +20 -0
- package/src/methods.js +61 -0
- package/src/methodsblock.js +197 -0
- package/src/parser.d.ts +130 -0
- package/src/parser.js +194 -0
- package/src/plan.d.ts +29 -0
- package/src/plan.js +938 -0
- package/src/runtime.js +16 -0
- package/src/state.d.ts +98 -0
- package/src/state.js +121 -0
- package/src/values.js +67 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Static analysis of a regex source to find which characters a match can begin with.
|
|
6
|
+
*
|
|
7
|
+
* This drives the lexer's first-character dispatch: at each offset we only try
|
|
8
|
+
* the patterns that could possibly match the character sitting there. Profiling
|
|
9
|
+
* showed ~44% of lexing time was `RegExp.exec`, most of it on patterns that
|
|
10
|
+
* could never have matched — the JSON string pattern alone was being run at
|
|
11
|
+
* every single offset.
|
|
12
|
+
*
|
|
13
|
+
* The one invariant that matters for correctness: the returned set must be a
|
|
14
|
+
* **superset** of the characters the pattern can really start with. Anything
|
|
15
|
+
* this analyzer does not fully understand makes it return `null`, meaning
|
|
16
|
+
* "unknown — always try this pattern". Being wrong in that direction only costs
|
|
17
|
+
* speed; being wrong in the other direction would silently drop tokens.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const DIGITS = []
|
|
21
|
+
for (let c = 0x30; c <= 0x39; c++) DIGITS.push(c)
|
|
22
|
+
|
|
23
|
+
const WORD = DIGITS.concat([0x5f])
|
|
24
|
+
for (let c = 0x41; c <= 0x5a; c++) WORD.push(c)
|
|
25
|
+
for (let c = 0x61; c <= 0x7a; c++) WORD.push(c)
|
|
26
|
+
|
|
27
|
+
const SPACE = [0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0xa0, 0x1680, 0x2028, 0x2029, 0x202f, 0x205f, 0x3000, 0xfeff]
|
|
28
|
+
for (let c = 0x2000; c <= 0x200a; c++) SPACE.push(c)
|
|
29
|
+
|
|
30
|
+
const SIMPLE_ESCAPES = {
|
|
31
|
+
n: 0x0a, r: 0x0d, t: 0x09, f: 0x0c, v: 0x0b, 0: 0x00
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} source Regex source text.
|
|
36
|
+
* @param {boolean} ignoreCase
|
|
37
|
+
* @returns {Set<number>|null} Possible first char codes, or null if undetermined.
|
|
38
|
+
*/
|
|
39
|
+
export function firstCharSet (source, ignoreCase) {
|
|
40
|
+
/** @type {Set<number>} */
|
|
41
|
+
const found = new Set()
|
|
42
|
+
let pos = 0
|
|
43
|
+
let bailed = false
|
|
44
|
+
|
|
45
|
+
const bail = () => { bailed = true }
|
|
46
|
+
|
|
47
|
+
/** Adds a char code, plus its case counterpart when the pattern is case-insensitive. */
|
|
48
|
+
function add (code) {
|
|
49
|
+
found.add(code)
|
|
50
|
+
if (!ignoreCase) return
|
|
51
|
+
const char = String.fromCharCode(code)
|
|
52
|
+
const upper = char.toUpperCase()
|
|
53
|
+
const lower = char.toLowerCase()
|
|
54
|
+
if (upper.length === 1) found.add(upper.charCodeAt(0))
|
|
55
|
+
if (lower.length === 1) found.add(lower.charCodeAt(0))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function addAll (codes) {
|
|
59
|
+
for (const code of codes) add(code)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses `a|b|c` up to a closing paren or end of input.
|
|
64
|
+
* @returns {boolean} whether the whole alternation can match empty
|
|
65
|
+
*/
|
|
66
|
+
function alternation (collect) {
|
|
67
|
+
let nullable = false
|
|
68
|
+
for (;;) {
|
|
69
|
+
if (sequence(collect)) nullable = true
|
|
70
|
+
if (bailed) return true
|
|
71
|
+
if (source[pos] === '|') {
|
|
72
|
+
pos++
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
return nullable
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parses a concatenation. Characters are only collected while every element so
|
|
82
|
+
* far could match empty — once something must consume, nothing after it can
|
|
83
|
+
* contribute to the first set.
|
|
84
|
+
* @returns {boolean} whether the sequence can match empty
|
|
85
|
+
*/
|
|
86
|
+
function sequence (collect) {
|
|
87
|
+
let stillNullable = true
|
|
88
|
+
while (pos < source.length && source[pos] !== '|' && source[pos] !== ')') {
|
|
89
|
+
const nullable = element(collect && stillNullable)
|
|
90
|
+
if (bailed) return true
|
|
91
|
+
if (!nullable) stillNullable = false
|
|
92
|
+
}
|
|
93
|
+
return stillNullable
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parses one atom plus its quantifier.
|
|
98
|
+
* @returns {boolean} whether it can match empty
|
|
99
|
+
*/
|
|
100
|
+
function element (collect) {
|
|
101
|
+
const zeroWidth = atom(collect)
|
|
102
|
+
if (bailed) return true
|
|
103
|
+
|
|
104
|
+
const quantifier = source[pos]
|
|
105
|
+
|
|
106
|
+
if (quantifier === '?' || quantifier === '*') {
|
|
107
|
+
pos++
|
|
108
|
+
if (source[pos] === '?' || source[pos] === '+') pos++
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (quantifier === '+') {
|
|
113
|
+
pos++
|
|
114
|
+
if (source[pos] === '?' || source[pos] === '+') pos++
|
|
115
|
+
return zeroWidth
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (quantifier === '{') {
|
|
119
|
+
const close = source.indexOf('}', pos)
|
|
120
|
+
if (close === -1) { bail(); return true }
|
|
121
|
+
const body = source.slice(pos + 1, close)
|
|
122
|
+
if (!/^\d+(,\d*)?$/.test(body)) { bail(); return true }
|
|
123
|
+
pos = close + 1
|
|
124
|
+
if (source[pos] === '?' || source[pos] === '+') pos++
|
|
125
|
+
const min = parseInt(body, 10)
|
|
126
|
+
return min === 0 ? true : zeroWidth
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return zeroWidth
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parses a single atom.
|
|
134
|
+
* @returns {boolean} whether the atom is zero-width (an anchor)
|
|
135
|
+
*/
|
|
136
|
+
function atom (collect) {
|
|
137
|
+
const char = source[pos]
|
|
138
|
+
|
|
139
|
+
if (char === '(') {
|
|
140
|
+
// A lookaround is zero-width. Treating it as such and letting whatever
|
|
141
|
+
// follows contribute its own first characters still yields a superset,
|
|
142
|
+
// which is all the invariant requires.
|
|
143
|
+
let lookaround = false
|
|
144
|
+
if (source.startsWith('(?:', pos)) pos += 3
|
|
145
|
+
else if (source.startsWith('(?=', pos) || source.startsWith('(?!', pos)) {
|
|
146
|
+
pos += 3
|
|
147
|
+
lookaround = true
|
|
148
|
+
} else if (source.startsWith('(?<=', pos) || source.startsWith('(?<!', pos)) {
|
|
149
|
+
pos += 4
|
|
150
|
+
lookaround = true
|
|
151
|
+
} else if (source.startsWith('(?<', pos)) {
|
|
152
|
+
// Named capture group: skip the name.
|
|
153
|
+
const close = source.indexOf('>', pos)
|
|
154
|
+
if (close === -1) { if (collect) bail(); return true }
|
|
155
|
+
pos = close + 1
|
|
156
|
+
} else pos += 1
|
|
157
|
+
|
|
158
|
+
const nullable = alternation(collect && !lookaround)
|
|
159
|
+
if (bailed) return true
|
|
160
|
+
if (source[pos] !== ')') { if (collect) bail(); return true }
|
|
161
|
+
pos++
|
|
162
|
+
return lookaround ? true : nullable
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (char === '[') return charClass(collect)
|
|
166
|
+
|
|
167
|
+
if (char === '\\') return escape(collect)
|
|
168
|
+
|
|
169
|
+
if (char === '.') {
|
|
170
|
+
// Matches nearly everything, so there is no useful set to report — but
|
|
171
|
+
// only when we are still collecting. Past the first mandatory element it
|
|
172
|
+
// is just another atom to step over.
|
|
173
|
+
if (collect) { bail(); return true }
|
|
174
|
+
pos++
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (char === '^' || char === '$') {
|
|
179
|
+
pos++
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (char === undefined) { bail(); return true }
|
|
184
|
+
|
|
185
|
+
// An ordinary literal character.
|
|
186
|
+
if (collect) add(source.charCodeAt(pos))
|
|
187
|
+
pos++
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Steps over a character class without interpreting it. */
|
|
192
|
+
function skipCharClass () {
|
|
193
|
+
pos++ // past '['
|
|
194
|
+
if (source[pos] === '^') pos++
|
|
195
|
+
if (source[pos] === ']') pos++ // a literal ']' first is allowed
|
|
196
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
197
|
+
if (source[pos] === '\\') pos += 2
|
|
198
|
+
else pos++
|
|
199
|
+
}
|
|
200
|
+
if (source[pos] !== ']') { bail(); return false }
|
|
201
|
+
pos++
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** @returns {boolean} always false (a class consumes one character) */
|
|
206
|
+
function charClass (collect) {
|
|
207
|
+
// A negated class is an enormous set; not worth enumerating. Once we are
|
|
208
|
+
// past the first mandatory element we only need to step over it.
|
|
209
|
+
if (source[pos + 1] === '^') {
|
|
210
|
+
if (collect) { bail(); return false }
|
|
211
|
+
return skipCharClass()
|
|
212
|
+
}
|
|
213
|
+
if (!collect) return skipCharClass()
|
|
214
|
+
|
|
215
|
+
pos++ // past '['
|
|
216
|
+
|
|
217
|
+
while (pos < source.length && source[pos] !== ']') {
|
|
218
|
+
let low
|
|
219
|
+
if (source[pos] === '\\') {
|
|
220
|
+
const before = pos
|
|
221
|
+
const zero = escape(collect, true)
|
|
222
|
+
if (bailed) return false
|
|
223
|
+
if (zero === null) {
|
|
224
|
+
// A class shorthand like \d inside the class: already collected.
|
|
225
|
+
low = null
|
|
226
|
+
} else {
|
|
227
|
+
low = zero
|
|
228
|
+
}
|
|
229
|
+
if (pos === before) { bail(); return false }
|
|
230
|
+
} else {
|
|
231
|
+
low = source.charCodeAt(pos)
|
|
232
|
+
pos++
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (low !== null && source[pos] === '-' && source[pos + 1] !== ']' && pos + 1 < source.length) {
|
|
236
|
+
pos++
|
|
237
|
+
let high
|
|
238
|
+
if (source[pos] === '\\') {
|
|
239
|
+
high = escape(collect, true)
|
|
240
|
+
if (bailed) return false
|
|
241
|
+
if (high === null) { bail(); return false }
|
|
242
|
+
} else {
|
|
243
|
+
high = source.charCodeAt(pos)
|
|
244
|
+
pos++
|
|
245
|
+
}
|
|
246
|
+
if (high < low) { bail(); return false }
|
|
247
|
+
// Guard against absurd ranges blowing up memory.
|
|
248
|
+
if (high - low > 0x1000) { bail(); return false }
|
|
249
|
+
if (collect) for (let c = low; c <= high; c++) add(c)
|
|
250
|
+
} else if (low !== null && collect) {
|
|
251
|
+
add(low)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (source[pos] !== ']') { bail(); return false }
|
|
256
|
+
pos++
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Parses an escape sequence.
|
|
262
|
+
* @param {boolean} collect
|
|
263
|
+
* @param {boolean} [inClass]
|
|
264
|
+
* @returns {any} In a class: the char code, or null when it was a shorthand
|
|
265
|
+
* set (already collected). Outside: whether it is zero-width.
|
|
266
|
+
*/
|
|
267
|
+
function escape (collect, inClass = false) {
|
|
268
|
+
pos++ // past '\'
|
|
269
|
+
const char = source[pos]
|
|
270
|
+
if (char === undefined) { bail(); return inClass ? null : true }
|
|
271
|
+
|
|
272
|
+
if (char === 'd' || char === 'w' || char === 's') {
|
|
273
|
+
pos++
|
|
274
|
+
if (collect) addAll(char === 'd' ? DIGITS : char === 'w' ? WORD : SPACE)
|
|
275
|
+
return inClass ? null : false
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (char === 'D' || char === 'W' || char === 'S' || char === 'p' || char === 'P') {
|
|
279
|
+
// Complement / unicode property classes: too large to enumerate. Only a
|
|
280
|
+
// problem while collecting; otherwise just step over it.
|
|
281
|
+
if (collect) { bail(); return inClass ? null : false }
|
|
282
|
+
pos++
|
|
283
|
+
if ((char === 'p' || char === 'P') && source[pos] === '{') {
|
|
284
|
+
const close = source.indexOf('}', pos)
|
|
285
|
+
if (close === -1) { bail(); return inClass ? null : false }
|
|
286
|
+
pos = close + 1
|
|
287
|
+
}
|
|
288
|
+
return inClass ? null : false
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (char === 'b' || char === 'B') {
|
|
292
|
+
pos++
|
|
293
|
+
if (inClass) {
|
|
294
|
+
// \b inside a class is a backspace.
|
|
295
|
+
if (collect) add(0x08)
|
|
296
|
+
return 0x08
|
|
297
|
+
}
|
|
298
|
+
return true // zero-width assertion
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (char === 'u') {
|
|
302
|
+
if (source[pos + 1] === '{') {
|
|
303
|
+
const close = source.indexOf('}', pos)
|
|
304
|
+
if (close === -1) { bail(); return inClass ? null : false }
|
|
305
|
+
const code = parseInt(source.slice(pos + 2, close), 16)
|
|
306
|
+
pos = close + 1
|
|
307
|
+
if (Number.isNaN(code)) { bail(); return inClass ? null : false }
|
|
308
|
+
if (collect) add(code)
|
|
309
|
+
return inClass ? code : false
|
|
310
|
+
}
|
|
311
|
+
const hex = source.slice(pos + 1, pos + 5)
|
|
312
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) { bail(); return inClass ? null : false }
|
|
313
|
+
pos += 5
|
|
314
|
+
const code = parseInt(hex, 16)
|
|
315
|
+
if (collect) add(code)
|
|
316
|
+
return inClass ? code : false
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (char === 'x') {
|
|
320
|
+
const hex = source.slice(pos + 1, pos + 3)
|
|
321
|
+
if (!/^[0-9a-fA-F]{2}$/.test(hex)) { bail(); return inClass ? null : false }
|
|
322
|
+
pos += 3
|
|
323
|
+
const code = parseInt(hex, 16)
|
|
324
|
+
if (collect) add(code)
|
|
325
|
+
return inClass ? code : false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (char >= '1' && char <= '9' && !inClass) {
|
|
329
|
+
// A backreference can start with anything the referenced group can.
|
|
330
|
+
if (collect) { bail(); return false }
|
|
331
|
+
while (source[pos] >= '0' && source[pos] <= '9') pos++
|
|
332
|
+
return false
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (char in SIMPLE_ESCAPES) {
|
|
336
|
+
pos++
|
|
337
|
+
const code = SIMPLE_ESCAPES[char]
|
|
338
|
+
if (collect) add(code)
|
|
339
|
+
return inClass ? code : false
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// An escaped literal such as \. or \[
|
|
343
|
+
pos++
|
|
344
|
+
const code = char.charCodeAt(0)
|
|
345
|
+
if (collect) add(code)
|
|
346
|
+
return inClass ? code : false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
alternation(true)
|
|
350
|
+
|
|
351
|
+
if (bailed || pos !== source.length || found.size === 0) return null
|
|
352
|
+
return found
|
|
353
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { emitModule } from "./emit.js";
|
|
2
|
+
export { createParser, GrammarError } from "./parser.js";
|
|
3
|
+
export { createLexer, LexError } from "./lexer.js";
|
|
4
|
+
export { analyze, buildDispatch, firstOf, PARSER_OPS } from "./analyze.js";
|
|
5
|
+
export { createMethods, OPERATORS } from "./methods.js";
|
|
6
|
+
export { definitionGrammar, definitionTokens, definitionRules, CONSTRUCTOR_NAMES, createDefinitionParser, parseDefinition, createParserFromSource } from "./definition.js";
|
|
7
|
+
export { FAIL, ParseError, createState } from "./state.js";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
export { createParser, GrammarError } from './parser.js'
|
|
5
|
+
export { createLexer, LexError } from './lexer.js'
|
|
6
|
+
export { analyze, buildDispatch, firstOf, PARSER_OPS } from './analyze.js'
|
|
7
|
+
export { createMethods, OPERATORS } from './methods.js'
|
|
8
|
+
export { createValueMethods } from './values.js'
|
|
9
|
+
export {
|
|
10
|
+
definitionGrammar, definitionTokens, definitionRules, CONSTRUCTOR_NAMES,
|
|
11
|
+
createDefinitionParser, parseDefinition, createParserFromSource
|
|
12
|
+
} from './definition.js'
|
|
13
|
+
export { emitModule } from './emit.js'
|
|
14
|
+
export { splitMethodsBlock, analyzeMethodsBlock, evaluateMethodsBlock } from './methodsblock.js'
|
|
15
|
+
export { FAIL, ParseError, createState } from './state.js'
|
package/src/lexer.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles token definitions into a fast sticky-regex lexer.
|
|
3
|
+
*
|
|
4
|
+
* Literal tokens are matched with `startsWith` rather than a regex, which is a
|
|
5
|
+
* large win for punctuation-heavy grammars (JSON is ~60% punctuation by token count).
|
|
6
|
+
*
|
|
7
|
+
* @param {TokenDef[]} defs
|
|
8
|
+
* @param {{ positions?: 'full' | 'offset' }} [options]
|
|
9
|
+
* `full` (default) records line and column on every token. `offset` records
|
|
10
|
+
* only `start`/`end`, which skips a scan of the entire input for newlines —
|
|
11
|
+
* worth taking when errors do not need to name a line, or when positions are
|
|
12
|
+
* resolved lazily from the offset afterwards.
|
|
13
|
+
* @returns {{ tokenize: (text: string) => Token[], tokenNames: string[], positions: string }}
|
|
14
|
+
*/
|
|
15
|
+
export function createLexer(defs: TokenDef[], options?: {
|
|
16
|
+
positions?: "full" | "offset";
|
|
17
|
+
}): {
|
|
18
|
+
tokenize: (text: string) => Token[];
|
|
19
|
+
tokenNames: string[];
|
|
20
|
+
positions: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A Chevrotain-style lexer. Token definitions are plain JSON, so a whole
|
|
24
|
+
* grammar (tokens + rules) stays serializable.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} TokenDef
|
|
27
|
+
* @property {string} name The token type produced.
|
|
28
|
+
* @property {string} [pattern] A regex source string, matched sticky at the current offset.
|
|
29
|
+
* @property {string} [literal] A literal string to match. Cheaper than a pattern; prefer it.
|
|
30
|
+
* @property {boolean} [skip] When true the match is consumed but produces no token (whitespace, comments).
|
|
31
|
+
* @property {string} [longerAlt] Name of another token to prefer when it matches a longer string
|
|
32
|
+
* at the same offset (the classic `if` / `identifier` problem).
|
|
33
|
+
* @property {boolean} [ignoreCase] Case-insensitive matching.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} Token
|
|
37
|
+
* @property {string} type
|
|
38
|
+
* @property {string} image
|
|
39
|
+
* @property {number} start
|
|
40
|
+
* @property {number} end
|
|
41
|
+
* @property {number} line
|
|
42
|
+
* @property {number} col
|
|
43
|
+
*/
|
|
44
|
+
export class LexError extends Error {
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} message
|
|
47
|
+
* @param {{ offset: number, line: number, col: number }} position
|
|
48
|
+
*/
|
|
49
|
+
constructor(message: string, position: {
|
|
50
|
+
offset: number;
|
|
51
|
+
line: number;
|
|
52
|
+
col: number;
|
|
53
|
+
});
|
|
54
|
+
offset: number;
|
|
55
|
+
line: number;
|
|
56
|
+
col: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A Chevrotain-style lexer. Token definitions are plain JSON, so a whole
|
|
60
|
+
* grammar (tokens + rules) stays serializable.
|
|
61
|
+
*/
|
|
62
|
+
export type TokenDef = {
|
|
63
|
+
/**
|
|
64
|
+
* The token type produced.
|
|
65
|
+
*/
|
|
66
|
+
name: string;
|
|
67
|
+
/**
|
|
68
|
+
* A regex source string, matched sticky at the current offset.
|
|
69
|
+
*/
|
|
70
|
+
pattern?: string;
|
|
71
|
+
/**
|
|
72
|
+
* A literal string to match. Cheaper than a pattern; prefer it.
|
|
73
|
+
*/
|
|
74
|
+
literal?: string;
|
|
75
|
+
/**
|
|
76
|
+
* When true the match is consumed but produces no token (whitespace, comments).
|
|
77
|
+
*/
|
|
78
|
+
skip?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Name of another token to prefer when it matches a longer string
|
|
81
|
+
* at the same offset (the classic `if` / `identifier` problem).
|
|
82
|
+
*/
|
|
83
|
+
longerAlt?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Case-insensitive matching.
|
|
86
|
+
*/
|
|
87
|
+
ignoreCase?: boolean;
|
|
88
|
+
};
|
|
89
|
+
export type Token = {
|
|
90
|
+
type: string;
|
|
91
|
+
image: string;
|
|
92
|
+
start: number;
|
|
93
|
+
end: number;
|
|
94
|
+
line: number;
|
|
95
|
+
col: number;
|
|
96
|
+
};
|