grammar-composer 0.3.1 → 0.5.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/README.md +144 -89
- package/dist/exports/Exports.d.ts +2 -0
- package/dist/exports/Exports.d.ts.map +1 -1
- package/dist/exports/Exports.js +2 -0
- package/dist/exports/Exports.js.map +1 -1
- package/dist/parser-generator/Grammar.d.ts +12 -5
- package/dist/parser-generator/Grammar.d.ts.map +1 -1
- package/dist/parser-generator/Grammar.js +81 -263
- package/dist/parser-generator/Grammar.js.map +1 -1
- package/dist/parser-generator/ParseError.d.ts +37 -0
- package/dist/parser-generator/ParseError.d.ts.map +1 -0
- package/dist/parser-generator/ParseError.js +226 -0
- package/dist/parser-generator/ParseError.js.map +1 -0
- package/dist/parser-generator/StaticAnalysis.d.ts +6 -0
- package/dist/parser-generator/StaticAnalysis.d.ts.map +1 -0
- package/dist/parser-generator/StaticAnalysis.js +283 -0
- package/dist/parser-generator/StaticAnalysis.js.map +1 -0
- package/dist/parser-generator/TerminalToText.d.ts +15 -0
- package/dist/parser-generator/TerminalToText.d.ts.map +1 -0
- package/dist/parser-generator/TerminalToText.js +158 -0
- package/dist/parser-generator/TerminalToText.js.map +1 -0
- package/dist/parser-generator/TopDownParser.d.ts +4 -2
- package/dist/parser-generator/TopDownParser.d.ts.map +1 -1
- package/dist/parser-generator/TopDownParser.js +52 -52
- package/dist/parser-generator/TopDownParser.js.map +1 -1
- package/dist/tests/Test.js +36 -26
- package/dist/tests/Test.js.map +1 -1
- package/dist/tests/test-grammars/JsonGrammar.d.ts +12 -11
- package/dist/tests/test-grammars/JsonGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/JsonGrammar.js +25 -10
- package/dist/tests/test-grammars/JsonGrammar.js.map +1 -1
- package/dist/tests/test-grammars/RegExpGrammar.d.ts +34 -28
- package/dist/tests/test-grammars/RegExpGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/RegExpGrammar.js +104 -60
- package/dist/tests/test-grammars/RegExpGrammar.js.map +1 -1
- package/dist/tests/test-grammars/SimpleTestGrammar1.d.ts +5 -0
- package/dist/tests/test-grammars/SimpleTestGrammar1.d.ts.map +1 -0
- package/dist/tests/test-grammars/SimpleTestGrammar1.js +7 -0
- package/dist/tests/test-grammars/SimpleTestGrammar1.js.map +1 -0
- package/dist/tests/test-grammars/XmlGrammar.d.ts +11 -10
- package/dist/tests/test-grammars/XmlGrammar.d.ts.map +1 -1
- package/dist/tests/test-grammars/XmlGrammar.js +19 -8
- package/dist/tests/test-grammars/XmlGrammar.js.map +1 -1
- package/dist/utilities/LineAndColumn.d.ts +7 -0
- package/dist/utilities/LineAndColumn.d.ts.map +1 -0
- package/dist/utilities/LineAndColumn.js +52 -0
- package/dist/utilities/LineAndColumn.js.map +1 -0
- package/dist/utilities/Utilities.d.ts +7 -7
- package/dist/utilities/Utilities.d.ts.map +1 -1
- package/dist/utilities/Utilities.js.map +1 -1
- package/package.json +2 -2
- package/src/exports/Exports.ts +2 -0
- package/src/parser-generator/Grammar.ts +120 -308
- package/src/parser-generator/ParseError.ts +337 -0
- package/src/parser-generator/StaticAnalysis.ts +340 -0
- package/src/parser-generator/TerminalToText.ts +196 -0
- package/src/parser-generator/TopDownParser.ts +76 -64
- package/src/tests/Test.ts +44 -28
- package/src/tests/test-grammars/JsonGrammar.ts +28 -10
- package/src/tests/test-grammars/RegExpGrammar.ts +193 -96
- package/src/tests/test-grammars/SimpleTestGrammar1.ts +10 -0
- package/src/tests/test-grammars/XmlGrammar.ts +20 -8
- package/src/utilities/LineAndColumn.ts +61 -0
- package/src/utilities/Utilities.ts +7 -7
|
@@ -2,47 +2,67 @@ import { Pattern, buildRegExp, inputStart, isPatternOptional } from 'regexp-comp
|
|
|
2
2
|
import { isArray, isBoolean, isFunction, isString } from '../utilities/Utilities.js'
|
|
3
3
|
|
|
4
4
|
import { parse } from './TopDownParser.js'
|
|
5
|
+
import { detectAndAnnotateOptionalNodes, detectAndErrorOnLeftRecursion, validatePatternCaptureGroups } from './StaticAnalysis.js'
|
|
5
6
|
|
|
6
7
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
7
8
|
// Grammar builder method
|
|
8
9
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
9
|
-
export function buildGrammar<T extends { [key: string]: any }>(
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
export function buildGrammar<T extends { [key: string]: any }>(
|
|
11
|
+
definitionObject: T | (new () => T),
|
|
12
|
+
startProductionName: keyof T,
|
|
13
|
+
options?: GrammarBuilderOptions<T>): Grammar<T> {
|
|
14
|
+
|
|
15
|
+
options = {
|
|
16
|
+
unwrappedNonterminalNames: [],
|
|
17
|
+
...options
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (isFunction(definitionObject)) {
|
|
21
|
+
definitionObject = new definitionObject()
|
|
12
22
|
}
|
|
13
23
|
|
|
14
24
|
const nameLookup = new Map<any, keyof T>()
|
|
15
25
|
const nonterminalLookup = new Map<Function, Nonterminal>()
|
|
16
26
|
const optionalNonterminalLookup = new Map<Function, Nonterminal>()
|
|
17
27
|
|
|
18
|
-
for (const key in
|
|
19
|
-
const objectProperty =
|
|
28
|
+
for (const key in definitionObject) {
|
|
29
|
+
const objectProperty = definitionObject[key]
|
|
20
30
|
|
|
21
31
|
nameLookup.set(objectProperty, key)
|
|
22
32
|
|
|
23
33
|
if (!isFunction(objectProperty)) {
|
|
24
|
-
|
|
34
|
+
throw new Error(`Grammar definition contains a property '${key}', which is not a function. All object properties must be productions, declared as functions.`)
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
const productionContent = objectProperty.call(
|
|
37
|
+
const productionContent = objectProperty.call(definitionObject)
|
|
28
38
|
const normalizedProduction = productionToGrammarElement(productionContent)
|
|
39
|
+
const isUnwrappedNonterminal = options.unwrappedNonterminalNames?.includes(key) === true
|
|
29
40
|
|
|
30
|
-
const newNonterminal = nonterminal(key, normalizedProduction)
|
|
31
|
-
const newOptionalNonterminal = {
|
|
41
|
+
const newNonterminal = nonterminal(key, normalizedProduction, isUnwrappedNonterminal)
|
|
42
|
+
const newOptionalNonterminal = {
|
|
43
|
+
...newNonterminal,
|
|
44
|
+
optional: true,
|
|
45
|
+
grammarNonterminal: newNonterminal,
|
|
46
|
+
}
|
|
32
47
|
|
|
33
48
|
nonterminalLookup.set(objectProperty, newNonterminal)
|
|
34
49
|
optionalNonterminalLookup.set(objectProperty, newOptionalNonterminal)
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
let
|
|
38
|
-
const
|
|
52
|
+
let cacheIdCounter = 0
|
|
53
|
+
const getNewCacheId = () => {
|
|
54
|
+
const newCacheId = cacheIdCounter
|
|
55
|
+
cacheIdCounter += 1
|
|
56
|
+
|
|
57
|
+
return newCacheId
|
|
58
|
+
}
|
|
39
59
|
|
|
40
60
|
for (const [func, nonterminal] of nonterminalLookup) {
|
|
41
61
|
const preparedContent = prepareGrammarElement(
|
|
42
62
|
nonterminal.content,
|
|
43
63
|
nonterminalLookup,
|
|
44
64
|
optionalNonterminalLookup,
|
|
45
|
-
|
|
65
|
+
getNewCacheId)
|
|
46
66
|
|
|
47
67
|
nonterminal.content = preparedContent
|
|
48
68
|
|
|
@@ -50,7 +70,7 @@ export function buildGrammar<T extends { [key: string]: any }>(obj: T | (new ()
|
|
|
50
70
|
optionalNonterminal.content = preparedContent
|
|
51
71
|
}
|
|
52
72
|
|
|
53
|
-
const startNonterminal = nonterminalLookup.get(
|
|
73
|
+
const startNonterminal = nonterminalLookup.get(definitionObject[startProductionName] as Function)
|
|
54
74
|
|
|
55
75
|
if (!startNonterminal) {
|
|
56
76
|
throw new Error(`Couldn't find a start production named '${startProductionName as string}'.`)
|
|
@@ -68,7 +88,7 @@ export function buildGrammar<T extends { [key: string]: any }>(obj: T | (new ()
|
|
|
68
88
|
return new Grammar<T>(
|
|
69
89
|
nonterminals,
|
|
70
90
|
startProductionName,
|
|
71
|
-
|
|
91
|
+
cacheIdCounter
|
|
72
92
|
)
|
|
73
93
|
}
|
|
74
94
|
|
|
@@ -76,41 +96,44 @@ function prepareGrammarElement(
|
|
|
76
96
|
rootElement: GrammarElement,
|
|
77
97
|
nonterminalLookup: Map<Function, Nonterminal>,
|
|
78
98
|
optionalNonterminalLookup: Map<Function, Nonterminal>,
|
|
79
|
-
|
|
99
|
+
getNewCacheId: () => number
|
|
80
100
|
): GrammarElement {
|
|
81
101
|
function prepare(element: GrammarElement): GrammarElement {
|
|
102
|
+
const setCacheIdIfNeeded = (element: GrammarElement) => {
|
|
103
|
+
if (element.cached === true) {
|
|
104
|
+
element.cacheId = getNewCacheId()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return element
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
switch (element.type) {
|
|
83
111
|
case 'StringTerminal':
|
|
84
112
|
case 'Nonterminal': {
|
|
85
|
-
|
|
86
|
-
element
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return element
|
|
113
|
+
return setCacheIdIfNeeded({
|
|
114
|
+
...element
|
|
115
|
+
})
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
case 'PatternTerminal': {
|
|
93
|
-
return {
|
|
119
|
+
return setCacheIdIfNeeded({
|
|
94
120
|
...element,
|
|
95
|
-
|
|
96
|
-
}
|
|
121
|
+
})
|
|
97
122
|
}
|
|
98
123
|
|
|
99
124
|
case 'Repetition': {
|
|
100
|
-
return {
|
|
125
|
+
return setCacheIdIfNeeded({
|
|
101
126
|
...element,
|
|
102
127
|
content: prepare(element.content),
|
|
103
|
-
|
|
104
|
-
}
|
|
128
|
+
})
|
|
105
129
|
}
|
|
106
130
|
|
|
107
131
|
case 'Sequence':
|
|
108
132
|
case 'Choice': {
|
|
109
|
-
return {
|
|
133
|
+
return setCacheIdIfNeeded({
|
|
110
134
|
...element,
|
|
111
135
|
members: element.members.map(element => prepare(element)),
|
|
112
|
-
|
|
113
|
-
}
|
|
136
|
+
})
|
|
114
137
|
}
|
|
115
138
|
|
|
116
139
|
case 'NonterminalReference': {
|
|
@@ -128,286 +151,34 @@ function prepareGrammarElement(
|
|
|
128
151
|
throw new Error(`Couldn't resolve function reference in grammar element: ${JSON.stringify(element)}`)
|
|
129
152
|
}
|
|
130
153
|
|
|
131
|
-
if (element.cached
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (element.cached !== undefined) {
|
|
135
|
-
clonedNonterminal.cached = element.cached
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
clonedNonterminal.uniqueId = getUniqueId()
|
|
139
|
-
|
|
140
|
-
return clonedNonterminal
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (nonterminal.uniqueId === undefined) {
|
|
144
|
-
nonterminal.uniqueId = getUniqueId()
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return nonterminal
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return prepare(rootElement)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
156
|
-
// Internal static analysis methods
|
|
157
|
-
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
158
|
-
function detectAndAnnotateOptionalNodes(rootNode: GrammarElement) {
|
|
159
|
-
const visitedNodes = new Set<GrammarElement>()
|
|
160
|
-
|
|
161
|
-
const resolvedNodes = new Map<GrammarElement, boolean>()
|
|
162
|
-
const unresolvedNodes = new Map<GrammarElement, { dependencies: Set<GrammarElement>, isChoice: boolean }>()
|
|
163
|
-
|
|
164
|
-
function processDepthFirst(node: GrammarElement): boolean | undefined {
|
|
165
|
-
if (visitedNodes.has(node)) {
|
|
166
|
-
return resolvedNodes.get(node)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
visitedNodes.add(node)
|
|
170
|
-
|
|
171
|
-
switch (node.type) {
|
|
172
|
-
case 'StringTerminal':
|
|
173
|
-
case 'PatternTerminal': {
|
|
174
|
-
resolvedNodes.set(node, node.optional)
|
|
175
|
-
|
|
176
|
-
return node.optional
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
case 'Nonterminal':
|
|
180
|
-
case 'Repetition': {
|
|
181
|
-
const result = processDepthFirst(node.content)
|
|
182
|
-
|
|
183
|
-
if (node.optional) {
|
|
184
|
-
resolvedNodes.set(node, true)
|
|
185
|
-
|
|
186
|
-
return true
|
|
187
|
-
} else if (isBoolean(result)) {
|
|
188
|
-
resolvedNodes.set(node, result)
|
|
189
|
-
|
|
190
|
-
return result
|
|
191
|
-
} else {
|
|
192
|
-
unresolvedNodes.set(node, { dependencies: new Set([node.content]), isChoice: false })
|
|
193
|
-
|
|
194
|
-
return undefined
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
case 'Sequence': {
|
|
199
|
-
const dependencies = new Set<GrammarElement>()
|
|
200
|
-
|
|
201
|
-
let hasNonOptionalResolvedMember = false
|
|
154
|
+
if (element.cached === true) {
|
|
155
|
+
return {
|
|
156
|
+
...nonterminal,
|
|
202
157
|
|
|
203
|
-
|
|
204
|
-
|
|
158
|
+
cached: true,
|
|
159
|
+
cacheId: getNewCacheId(),
|
|
205
160
|
|
|
206
|
-
|
|
207
|
-
if (result === false) {
|
|
208
|
-
hasNonOptionalResolvedMember = true
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
dependencies.add(element)
|
|
161
|
+
grammarNonterminal: nonterminal.grammarNonterminal ?? nonterminal
|
|
212
162
|
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (node.optional == true) {
|
|
216
|
-
resolvedNodes.set(node, true)
|
|
217
|
-
|
|
218
|
-
return true
|
|
219
|
-
} else if (hasNonOptionalResolvedMember) {
|
|
220
|
-
resolvedNodes.set(node, false)
|
|
221
|
-
|
|
222
|
-
return false
|
|
223
|
-
} else if (dependencies.size == 0) {
|
|
224
|
-
resolvedNodes.set(node, true)
|
|
225
|
-
|
|
226
|
-
return true
|
|
227
163
|
} else {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return undefined
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
case 'Choice': {
|
|
235
|
-
const dependencies = new Set<GrammarElement>()
|
|
236
|
-
let hasOptionalResolvedMember = false
|
|
237
|
-
|
|
238
|
-
for (const element of node.members) {
|
|
239
|
-
const result = processDepthFirst(element)
|
|
240
|
-
|
|
241
|
-
if (isBoolean(result)) {
|
|
242
|
-
if (result === true) {
|
|
243
|
-
hasOptionalResolvedMember = true
|
|
244
|
-
}
|
|
245
|
-
} else {
|
|
246
|
-
dependencies.add(element)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (node.optional == true) {
|
|
251
|
-
resolvedNodes.set(node, true)
|
|
252
|
-
|
|
253
|
-
return true
|
|
254
|
-
} else if (hasOptionalResolvedMember) {
|
|
255
|
-
resolvedNodes.set(node, true)
|
|
256
|
-
|
|
257
|
-
return true
|
|
258
|
-
} else if (dependencies.size == 0) {
|
|
259
|
-
resolvedNodes.set(node, false)
|
|
260
|
-
|
|
261
|
-
return false
|
|
262
|
-
} else {
|
|
263
|
-
unresolvedNodes.set(node, { dependencies, isChoice: true })
|
|
264
|
-
|
|
265
|
-
return undefined
|
|
164
|
+
return nonterminal
|
|
266
165
|
}
|
|
267
166
|
}
|
|
268
167
|
}
|
|
269
|
-
|
|
270
|
-
return undefined
|
|
271
168
|
}
|
|
272
169
|
|
|
273
|
-
|
|
274
|
-
// no cyclic references:
|
|
275
|
-
processDepthFirst(rootNode)
|
|
276
|
-
|
|
277
|
-
// Now the remainder consists of nodes containing cyclic references that have not yet been resolved.
|
|
278
|
-
// Use a form of iterative elimination and substitution to resolve them:
|
|
279
|
-
while (unresolvedNodes.size > 0) {
|
|
280
|
-
// This variable tracks whether at least one dependency was resolved, in any node.
|
|
281
|
-
// If it stays false, it means that no improvement was made during the iteration,
|
|
282
|
-
// and we should exit the loop.
|
|
283
|
-
let atLastOneDependencyResolvedInAnyNode = false
|
|
284
|
-
const nodesToDelete: GrammarElement[] = []
|
|
285
|
-
|
|
286
|
-
// Scan the unresolved nodes to locate any new resolved dependencies
|
|
287
|
-
for (const [node, { dependencies, isChoice }] of unresolvedNodes) {
|
|
288
|
-
let nodeResolved = false
|
|
289
|
-
|
|
290
|
-
// Iterate over all unresolved dependencies for the node
|
|
291
|
-
for (const dependency of Array.from(dependencies)) {
|
|
292
|
-
// Check if the dependency has been resolved
|
|
293
|
-
const value = resolvedNodes.get(dependency)
|
|
294
|
-
|
|
295
|
-
if (value !== undefined) {
|
|
296
|
-
// If it did, record that some dependencies were resolved
|
|
297
|
-
atLastOneDependencyResolvedInAnyNode = true
|
|
298
|
-
|
|
299
|
-
if (isChoice) {
|
|
300
|
-
if (value === true) {
|
|
301
|
-
resolvedNodes.set(node, true)
|
|
302
|
-
nodeResolved = true
|
|
303
|
-
break
|
|
304
|
-
}
|
|
305
|
-
} else {
|
|
306
|
-
if (value === false) {
|
|
307
|
-
resolvedNodes.set(node, false)
|
|
308
|
-
nodeResolved = true
|
|
309
|
-
break
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
dependencies.delete(dependency)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (nodeResolved) {
|
|
318
|
-
nodesToDelete.push(node)
|
|
319
|
-
} else if (dependencies.size === 0) {
|
|
320
|
-
if (isChoice) {
|
|
321
|
-
resolvedNodes.set(node, false)
|
|
322
|
-
} else {
|
|
323
|
-
resolvedNodes.set(node, true)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
nodesToDelete.push(node)
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
for (const node of nodesToDelete) {
|
|
331
|
-
unresolvedNodes.delete(node)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// If not even one dependency was eliminated for any node,
|
|
335
|
-
// it means that only mutually cyclic nodes are left unresolved, so exit the loop.
|
|
336
|
-
if (!atLastOneDependencyResolvedInAnyNode) {
|
|
337
|
-
break
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// All remaining unresolved nodes must now be optional,
|
|
342
|
-
// since they are all mutually cyclic and all their non-cyclic grammar elements are known to be optional.
|
|
343
|
-
for (const node of unresolvedNodes.keys()) {
|
|
344
|
-
resolvedNodes.set(node, true)
|
|
345
|
-
unresolvedNodes.delete(node)
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Finally set the 'optional' property of all nodes based on the detected values.
|
|
349
|
-
for (const [node, isOptional] of resolvedNodes) {
|
|
350
|
-
node.optional = isOptional
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function detectAndErrorOnLeftRecursion(rootNode: GrammarElement) {
|
|
355
|
-
const currentlyIteratedNodes = new Set<GrammarElement>()
|
|
356
|
-
|
|
357
|
-
function detect(node: GrammarElement) {
|
|
358
|
-
if (currentlyIteratedNodes.has(node)) {
|
|
359
|
-
if (node.type === 'Nonterminal') {
|
|
360
|
-
throw new Error(`Detected left recursion for nonterminal '${node.name}'.`)
|
|
361
|
-
} else {
|
|
362
|
-
throw new Error(`Detected left recursion for node: ${JSON.stringify(node, undefined, 4)}`)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
currentlyIteratedNodes.add(node)
|
|
367
|
-
|
|
368
|
-
switch (node.type) {
|
|
369
|
-
case 'Nonterminal':
|
|
370
|
-
case 'Repetition': {
|
|
371
|
-
detect(node.content)
|
|
372
|
-
|
|
373
|
-
break
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
case 'Sequence': {
|
|
377
|
-
for (const member of node.members) {
|
|
378
|
-
detect(member)
|
|
379
|
-
|
|
380
|
-
if (!member.optional) {
|
|
381
|
-
break
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
break
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
case 'Choice': {
|
|
389
|
-
for (const member of node.members) {
|
|
390
|
-
detect(member)
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
break
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
currentlyIteratedNodes.delete(node)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
detect(rootNode)
|
|
170
|
+
return prepare(rootElement)
|
|
401
171
|
}
|
|
402
172
|
|
|
403
173
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
404
|
-
// Exported builder
|
|
174
|
+
// Exported AST builder functions
|
|
405
175
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
406
176
|
export function zeroOrMore(content: Production): Repetition {
|
|
407
177
|
return {
|
|
408
178
|
type: 'Repetition',
|
|
409
179
|
content: productionToGrammarElement(content),
|
|
410
|
-
optional: true
|
|
180
|
+
optional: true,
|
|
181
|
+
cached: false,
|
|
411
182
|
}
|
|
412
183
|
}
|
|
413
184
|
|
|
@@ -415,12 +186,13 @@ export function oneOrMore(content: Production): Repetition {
|
|
|
415
186
|
return {
|
|
416
187
|
type: 'Repetition',
|
|
417
188
|
content: productionToGrammarElement(content),
|
|
418
|
-
optional: false
|
|
189
|
+
optional: false,
|
|
190
|
+
cached: false,
|
|
419
191
|
}
|
|
420
192
|
}
|
|
421
193
|
|
|
422
194
|
export function anyOf(...members: Production[]): Choice {
|
|
423
|
-
if (members.length
|
|
195
|
+
if (members.length === 0) {
|
|
424
196
|
throw new Error(`'anyOf' requires at least one member.`)
|
|
425
197
|
}
|
|
426
198
|
|
|
@@ -430,12 +202,13 @@ export function anyOf(...members: Production[]): Choice {
|
|
|
430
202
|
type: 'Choice',
|
|
431
203
|
members: normalizedMembers,
|
|
432
204
|
optional: false,
|
|
433
|
-
exhaustive: false
|
|
205
|
+
exhaustive: false,
|
|
206
|
+
cached: false,
|
|
434
207
|
}
|
|
435
208
|
}
|
|
436
209
|
|
|
437
210
|
export function bestOf(...members: Production[]): Choice {
|
|
438
|
-
if (members.length
|
|
211
|
+
if (members.length === 0) {
|
|
439
212
|
throw new Error(`'bestOf' requires at least one member.`)
|
|
440
213
|
}
|
|
441
214
|
|
|
@@ -445,7 +218,9 @@ export function bestOf(...members: Production[]): Choice {
|
|
|
445
218
|
type: 'Choice',
|
|
446
219
|
members: normalizedMembers,
|
|
447
220
|
optional: false,
|
|
448
|
-
exhaustive: true
|
|
221
|
+
exhaustive: true,
|
|
222
|
+
|
|
223
|
+
cached: false,
|
|
449
224
|
}
|
|
450
225
|
}
|
|
451
226
|
|
|
@@ -460,6 +235,8 @@ export function pattern(pattern: Pattern): PatternTerminal {
|
|
|
460
235
|
pattern = [inputStart, pattern]
|
|
461
236
|
}
|
|
462
237
|
|
|
238
|
+
validatePatternCaptureGroups(pattern)
|
|
239
|
+
|
|
463
240
|
const regExp = buildRegExp(pattern)
|
|
464
241
|
const optional = isPatternOptional(pattern)
|
|
465
242
|
|
|
@@ -468,6 +245,8 @@ export function pattern(pattern: Pattern): PatternTerminal {
|
|
|
468
245
|
pattern,
|
|
469
246
|
regExp,
|
|
470
247
|
optional,
|
|
248
|
+
|
|
249
|
+
cached: false,
|
|
471
250
|
}
|
|
472
251
|
}
|
|
473
252
|
|
|
@@ -480,7 +259,7 @@ export function uncached<T extends Production>(content: Production): T {
|
|
|
480
259
|
}
|
|
481
260
|
|
|
482
261
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
483
|
-
// Internal builder methods
|
|
262
|
+
// Internal AST builder methods
|
|
484
263
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
485
264
|
function stringTerminal(content: string): StringTerminal {
|
|
486
265
|
if (content.length < 1) {
|
|
@@ -490,11 +269,13 @@ function stringTerminal(content: string): StringTerminal {
|
|
|
490
269
|
return {
|
|
491
270
|
type: 'StringTerminal',
|
|
492
271
|
content,
|
|
493
|
-
optional: false
|
|
272
|
+
optional: false,
|
|
273
|
+
|
|
274
|
+
cached: false,
|
|
494
275
|
}
|
|
495
276
|
}
|
|
496
277
|
|
|
497
|
-
function nonterminal(name: string, content: GrammarElement): Nonterminal {
|
|
278
|
+
function nonterminal(name: string, content: GrammarElement, unwrapped: boolean): Nonterminal {
|
|
498
279
|
if (name.length < 1) {
|
|
499
280
|
throw new Error(`A nonterminal name must include at least 1 character.`)
|
|
500
281
|
}
|
|
@@ -503,7 +284,10 @@ function nonterminal(name: string, content: GrammarElement): Nonterminal {
|
|
|
503
284
|
type: 'Nonterminal',
|
|
504
285
|
name,
|
|
505
286
|
content,
|
|
287
|
+
|
|
506
288
|
optional: false,
|
|
289
|
+
cached: false,
|
|
290
|
+
unwrapped,
|
|
507
291
|
}
|
|
508
292
|
}
|
|
509
293
|
|
|
@@ -511,7 +295,9 @@ function sequence(members: GrammarElement[]): Sequence {
|
|
|
511
295
|
return {
|
|
512
296
|
type: 'Sequence',
|
|
513
297
|
members,
|
|
514
|
-
optional: false
|
|
298
|
+
optional: false,
|
|
299
|
+
|
|
300
|
+
cached: false,
|
|
515
301
|
}
|
|
516
302
|
}
|
|
517
303
|
|
|
@@ -519,7 +305,9 @@ function unresolvedReference(reference: Function): NonterminalReference {
|
|
|
519
305
|
return {
|
|
520
306
|
type: 'NonterminalReference',
|
|
521
307
|
reference,
|
|
522
|
-
optional: false
|
|
308
|
+
optional: false,
|
|
309
|
+
|
|
310
|
+
cached: false,
|
|
523
311
|
}
|
|
524
312
|
}
|
|
525
313
|
|
|
@@ -538,17 +326,18 @@ function productionToGrammarElement(production: Production): GrammarElement {
|
|
|
538
326
|
}
|
|
539
327
|
|
|
540
328
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
541
|
-
//
|
|
329
|
+
// Grammar class
|
|
542
330
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
543
331
|
export class Grammar<T> {
|
|
544
332
|
readonly productions: Record<keyof T, any>
|
|
545
333
|
readonly startProductionName: keyof T
|
|
546
|
-
readonly maxElementId: number
|
|
547
334
|
|
|
548
|
-
|
|
335
|
+
readonly maxCacheId: number
|
|
336
|
+
|
|
337
|
+
constructor(productions: Record<keyof T, any>, startProductionName: keyof T, maxCacheId: number) {
|
|
549
338
|
this.startProductionName = startProductionName
|
|
550
339
|
this.productions = productions
|
|
551
|
-
this.
|
|
340
|
+
this.maxCacheId = maxCacheId
|
|
552
341
|
}
|
|
553
342
|
|
|
554
343
|
parse(text: string) {
|
|
@@ -560,6 +349,9 @@ export class Grammar<T> {
|
|
|
560
349
|
}
|
|
561
350
|
}
|
|
562
351
|
|
|
352
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
353
|
+
// Type definitions
|
|
354
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
563
355
|
export type Production = string | GrammarElement | (() => Production) | Production[]
|
|
564
356
|
|
|
565
357
|
export type GrammarElement =
|
|
@@ -574,8 +366,13 @@ export type GrammarElement =
|
|
|
574
366
|
interface GrammarElementBase {
|
|
575
367
|
type: string
|
|
576
368
|
optional: boolean
|
|
577
|
-
|
|
578
|
-
cached
|
|
369
|
+
|
|
370
|
+
cached: boolean
|
|
371
|
+
cacheId?: number
|
|
372
|
+
|
|
373
|
+
// The name of the grammar property this element was assigned to, if any.
|
|
374
|
+
// Used by the parser to refer to the element by name in error messages.
|
|
375
|
+
name?: string
|
|
579
376
|
}
|
|
580
377
|
|
|
581
378
|
export type Terminal = StringTerminal | PatternTerminal
|
|
@@ -595,6 +392,12 @@ export interface Nonterminal extends GrammarElementBase {
|
|
|
595
392
|
type: 'Nonterminal'
|
|
596
393
|
name: string
|
|
597
394
|
content: GrammarElement
|
|
395
|
+
unwrapped: boolean
|
|
396
|
+
|
|
397
|
+
// The canonical nonterminal as defined in the grammar. Clones created for
|
|
398
|
+
// optional references and cached references keep a reference to the original,
|
|
399
|
+
// so identity comparisons work across clones.
|
|
400
|
+
grammarNonterminal?: Nonterminal
|
|
598
401
|
}
|
|
599
402
|
|
|
600
403
|
export interface Sequence extends GrammarElementBase {
|
|
@@ -617,3 +420,12 @@ export interface NonterminalReference extends GrammarElementBase {
|
|
|
617
420
|
type: 'NonterminalReference'
|
|
618
421
|
reference: Function
|
|
619
422
|
}
|
|
423
|
+
|
|
424
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
425
|
+
// Builder type definitions
|
|
426
|
+
/////////////////////////////////////////////////////////////////////////////////////////////////
|
|
427
|
+
export interface GrammarBuilderOptions<T> {
|
|
428
|
+
unwrappedNonterminalNames?: GrammarNonterminalNames<T>
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export type GrammarNonterminalNames<T> = (keyof T)[]
|