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.
Files changed (64) hide show
  1. package/README.md +144 -89
  2. package/dist/exports/Exports.d.ts +2 -0
  3. package/dist/exports/Exports.d.ts.map +1 -1
  4. package/dist/exports/Exports.js +2 -0
  5. package/dist/exports/Exports.js.map +1 -1
  6. package/dist/parser-generator/Grammar.d.ts +12 -5
  7. package/dist/parser-generator/Grammar.d.ts.map +1 -1
  8. package/dist/parser-generator/Grammar.js +81 -263
  9. package/dist/parser-generator/Grammar.js.map +1 -1
  10. package/dist/parser-generator/ParseError.d.ts +37 -0
  11. package/dist/parser-generator/ParseError.d.ts.map +1 -0
  12. package/dist/parser-generator/ParseError.js +226 -0
  13. package/dist/parser-generator/ParseError.js.map +1 -0
  14. package/dist/parser-generator/StaticAnalysis.d.ts +6 -0
  15. package/dist/parser-generator/StaticAnalysis.d.ts.map +1 -0
  16. package/dist/parser-generator/StaticAnalysis.js +283 -0
  17. package/dist/parser-generator/StaticAnalysis.js.map +1 -0
  18. package/dist/parser-generator/TerminalToText.d.ts +15 -0
  19. package/dist/parser-generator/TerminalToText.d.ts.map +1 -0
  20. package/dist/parser-generator/TerminalToText.js +158 -0
  21. package/dist/parser-generator/TerminalToText.js.map +1 -0
  22. package/dist/parser-generator/TopDownParser.d.ts +4 -2
  23. package/dist/parser-generator/TopDownParser.d.ts.map +1 -1
  24. package/dist/parser-generator/TopDownParser.js +52 -52
  25. package/dist/parser-generator/TopDownParser.js.map +1 -1
  26. package/dist/tests/Test.js +36 -26
  27. package/dist/tests/Test.js.map +1 -1
  28. package/dist/tests/test-grammars/JsonGrammar.d.ts +12 -11
  29. package/dist/tests/test-grammars/JsonGrammar.d.ts.map +1 -1
  30. package/dist/tests/test-grammars/JsonGrammar.js +25 -10
  31. package/dist/tests/test-grammars/JsonGrammar.js.map +1 -1
  32. package/dist/tests/test-grammars/RegExpGrammar.d.ts +34 -28
  33. package/dist/tests/test-grammars/RegExpGrammar.d.ts.map +1 -1
  34. package/dist/tests/test-grammars/RegExpGrammar.js +104 -60
  35. package/dist/tests/test-grammars/RegExpGrammar.js.map +1 -1
  36. package/dist/tests/test-grammars/SimpleTestGrammar1.d.ts +5 -0
  37. package/dist/tests/test-grammars/SimpleTestGrammar1.d.ts.map +1 -0
  38. package/dist/tests/test-grammars/SimpleTestGrammar1.js +7 -0
  39. package/dist/tests/test-grammars/SimpleTestGrammar1.js.map +1 -0
  40. package/dist/tests/test-grammars/XmlGrammar.d.ts +11 -10
  41. package/dist/tests/test-grammars/XmlGrammar.d.ts.map +1 -1
  42. package/dist/tests/test-grammars/XmlGrammar.js +19 -8
  43. package/dist/tests/test-grammars/XmlGrammar.js.map +1 -1
  44. package/dist/utilities/LineAndColumn.d.ts +7 -0
  45. package/dist/utilities/LineAndColumn.d.ts.map +1 -0
  46. package/dist/utilities/LineAndColumn.js +52 -0
  47. package/dist/utilities/LineAndColumn.js.map +1 -0
  48. package/dist/utilities/Utilities.d.ts +7 -7
  49. package/dist/utilities/Utilities.d.ts.map +1 -1
  50. package/dist/utilities/Utilities.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/exports/Exports.ts +2 -0
  53. package/src/parser-generator/Grammar.ts +120 -308
  54. package/src/parser-generator/ParseError.ts +337 -0
  55. package/src/parser-generator/StaticAnalysis.ts +340 -0
  56. package/src/parser-generator/TerminalToText.ts +196 -0
  57. package/src/parser-generator/TopDownParser.ts +76 -64
  58. package/src/tests/Test.ts +44 -28
  59. package/src/tests/test-grammars/JsonGrammar.ts +28 -10
  60. package/src/tests/test-grammars/RegExpGrammar.ts +193 -96
  61. package/src/tests/test-grammars/SimpleTestGrammar1.ts +10 -0
  62. package/src/tests/test-grammars/XmlGrammar.ts +20 -8
  63. package/src/utilities/LineAndColumn.ts +61 -0
  64. 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 }>(obj: T | (new () => T), startProductionName: keyof T): Grammar<T> {
10
- if (isFunction(obj)) {
11
- obj = new obj()
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 obj) {
19
- const objectProperty = obj[key]
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
- continue
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(obj)
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 = { ...newNonterminal, optional: true }
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 uniqueIdCounter = 0
38
- const uniqueIdSource = () => uniqueIdCounter++
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
- uniqueIdSource)
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(obj[startProductionName] as Function)
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
- uniqueIdCounter
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
- getUniqueId: () => number
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
- if (element.uniqueId === undefined) {
86
- element.uniqueId = getUniqueId()
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
- uniqueId: getUniqueId()
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
- uniqueId: getUniqueId()
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
- uniqueId: getUniqueId()
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 !== undefined || element.uniqueId !== undefined) {
132
- const clonedNonterminal = { ...nonterminal }
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
- for (const element of node.members) {
204
- const result = processDepthFirst(element)
158
+ cached: true,
159
+ cacheId: getNewCacheId(),
205
160
 
206
- if (isBoolean(result)) {
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
- unresolvedNodes.set(node, { dependencies, isChoice: false })
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
- // Process depth first to resolve the easy cases, for productions that contain
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 methods
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 == 0) {
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 == 0) {
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
- // Type definitions
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
- constructor(productions: Record<keyof T, any>, startProductionName: keyof T, maxElementId: number) {
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.maxElementId = maxElementId
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
- uniqueId?: number
578
- cached?: boolean
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)[]