grammar-composer 0.6.0 → 0.6.2

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 (55) hide show
  1. package/dist/exports/Exports.d.ts +4 -3
  2. package/dist/exports/Exports.d.ts.map +1 -1
  3. package/dist/exports/Exports.js +4 -3
  4. package/dist/exports/Exports.js.map +1 -1
  5. package/dist/parser/Builder.d.ts +11 -0
  6. package/dist/parser/Builder.d.ts.map +1 -0
  7. package/dist/{parser-generator/Grammar.js → parser/Builder.js} +10 -97
  8. package/dist/parser/Builder.js.map +1 -0
  9. package/dist/{parser-generator → parser}/Grammar.d.ts +8 -15
  10. package/dist/parser/Grammar.d.ts.map +1 -0
  11. package/dist/parser/Grammar.js +97 -0
  12. package/dist/parser/Grammar.js.map +1 -0
  13. package/dist/{parser-generator → parser}/ParseError.d.ts +0 -2
  14. package/dist/parser/ParseError.d.ts.map +1 -0
  15. package/dist/{parser-generator → parser}/ParseError.js +35 -8
  16. package/dist/parser/ParseError.js.map +1 -0
  17. package/dist/{parser-generator/TopDownParser.d.ts → parser/Parser.d.ts} +4 -3
  18. package/dist/parser/Parser.d.ts.map +1 -0
  19. package/dist/{parser-generator/TopDownParser.js → parser/Parser.js} +40 -6
  20. package/dist/parser/Parser.js.map +1 -0
  21. package/dist/parser/StaticAnalysis.d.ts.map +1 -0
  22. package/dist/{parser-generator → parser}/StaticAnalysis.js +16 -5
  23. package/dist/parser/StaticAnalysis.js.map +1 -0
  24. package/dist/parser/TerminalToText.d.ts.map +1 -0
  25. package/dist/{parser-generator → parser}/TerminalToText.js +1 -1
  26. package/dist/parser/TerminalToText.js.map +1 -0
  27. package/dist/tests/Test.js +2 -2
  28. package/dist/tests/Test.js.map +1 -1
  29. package/dist/utilities/Utilities.d.ts +2 -0
  30. package/dist/utilities/Utilities.d.ts.map +1 -1
  31. package/dist/utilities/Utilities.js +15 -1
  32. package/dist/utilities/Utilities.js.map +1 -1
  33. package/package.json +6 -4
  34. package/src/exports/Exports.ts +4 -3
  35. package/src/{parser-generator/Grammar.ts → parser/Builder.ts} +14 -191
  36. package/src/parser/Grammar.ts +194 -0
  37. package/src/{parser-generator → parser}/ParseError.ts +42 -11
  38. package/src/{parser-generator/TopDownParser.ts → parser/Parser.ts} +59 -10
  39. package/src/{parser-generator → parser}/StaticAnalysis.ts +17 -7
  40. package/src/{parser-generator → parser}/TerminalToText.ts +1 -1
  41. package/src/tests/Test.ts +2 -2
  42. package/src/utilities/Utilities.ts +21 -1
  43. package/tsconfig.json +51 -53
  44. package/dist/parser-generator/Grammar.d.ts.map +0 -1
  45. package/dist/parser-generator/Grammar.js.map +0 -1
  46. package/dist/parser-generator/ParseError.d.ts.map +0 -1
  47. package/dist/parser-generator/ParseError.js.map +0 -1
  48. package/dist/parser-generator/StaticAnalysis.d.ts.map +0 -1
  49. package/dist/parser-generator/StaticAnalysis.js.map +0 -1
  50. package/dist/parser-generator/TerminalToText.d.ts.map +0 -1
  51. package/dist/parser-generator/TerminalToText.js.map +0 -1
  52. package/dist/parser-generator/TopDownParser.d.ts.map +0 -1
  53. package/dist/parser-generator/TopDownParser.js.map +0 -1
  54. /package/dist/{parser-generator → parser}/StaticAnalysis.d.ts +0 -0
  55. /package/dist/{parser-generator → parser}/TerminalToText.d.ts +0 -0
@@ -0,0 +1,194 @@
1
+ import { Pattern, buildRegExp, inputStart, isPatternOptional } from 'regexp-composer'
2
+ import { isArray } from '../utilities/Utilities.js'
3
+
4
+ import { parse } from './Parser.js'
5
+ import { validatePatternCaptureGroups } from './StaticAnalysis.js'
6
+ import { productionToGrammarElement } from './Builder.js'
7
+
8
+ /////////////////////////////////////////////////////////////////////////////////////////////////
9
+ // Grammar class
10
+ /////////////////////////////////////////////////////////////////////////////////////////////////
11
+ export class Grammar<T> {
12
+ readonly productions: Record<keyof T, any>
13
+ readonly startProductionName: keyof T
14
+
15
+ readonly maxCacheId: number
16
+
17
+ constructor(
18
+ productions: Record<keyof T, any>,
19
+ startProductionName: keyof T,
20
+ maxCacheId: number) {
21
+
22
+ this.startProductionName = startProductionName
23
+ this.productions = productions
24
+ this.maxCacheId = maxCacheId
25
+ }
26
+
27
+ parse(text: string) {
28
+ return parse(text, this)
29
+ }
30
+
31
+ get rootElement() {
32
+ return this.productions[this.startProductionName]
33
+ }
34
+ }
35
+
36
+ /////////////////////////////////////////////////////////////////////////////////////////////////
37
+ // Exported grammar builder functions
38
+ /////////////////////////////////////////////////////////////////////////////////////////////////
39
+ export function zeroOrMore(content: Production): Repetition {
40
+ return {
41
+ type: 'Repetition',
42
+ content: productionToGrammarElement(content),
43
+ optional: true,
44
+ cached: false,
45
+ }
46
+ }
47
+
48
+ export function oneOrMore(content: Production): Repetition {
49
+ return {
50
+ type: 'Repetition',
51
+ content: productionToGrammarElement(content),
52
+ optional: false,
53
+ cached: false,
54
+ }
55
+ }
56
+
57
+ export function anyOf(...members: Production[]): Choice {
58
+ if (members.length === 0) {
59
+ throw new Error(`'anyOf' requires at least one member.`)
60
+ }
61
+
62
+ const normalizedMembers = members.map(member => productionToGrammarElement(member))
63
+
64
+ return {
65
+ type: 'Choice',
66
+ members: normalizedMembers,
67
+ optional: false,
68
+ exhaustive: false,
69
+ cached: false,
70
+ }
71
+ }
72
+
73
+ export function bestOf(...members: Production[]): Choice {
74
+ if (members.length === 0) {
75
+ throw new Error(`'bestOf' requires at least one member.`)
76
+ }
77
+
78
+ const normalizedMembers = members.map(member => productionToGrammarElement(member))
79
+
80
+ return {
81
+ type: 'Choice',
82
+ members: normalizedMembers,
83
+ optional: false,
84
+ exhaustive: true,
85
+
86
+ cached: false,
87
+ }
88
+ }
89
+
90
+ export function possibly<T extends Production>(content: Production): T {
91
+ return { ...productionToGrammarElement(content), optional: true } as T
92
+ }
93
+
94
+ export function pattern(pattern: Pattern): PatternTerminal {
95
+ if (isArray(pattern)) {
96
+ pattern = [inputStart, ...pattern]
97
+ } else {
98
+ pattern = [inputStart, pattern]
99
+ }
100
+
101
+ validatePatternCaptureGroups(pattern)
102
+
103
+ const regExp = buildRegExp(pattern)
104
+ const optional = isPatternOptional(pattern)
105
+
106
+ return {
107
+ type: 'PatternTerminal',
108
+ pattern,
109
+ regExp,
110
+ optional,
111
+
112
+ cached: false,
113
+ }
114
+ }
115
+
116
+ export function cached<T extends Production>(content: Production): T {
117
+ return { ...productionToGrammarElement(content), cached: true } as T
118
+ }
119
+
120
+ export function uncached<T extends Production>(content: Production): T {
121
+ return { ...productionToGrammarElement(content), cached: false } as T
122
+ }
123
+
124
+ /////////////////////////////////////////////////////////////////////////////////////////////////
125
+ // Type definitions
126
+ /////////////////////////////////////////////////////////////////////////////////////////////////
127
+ export type Production = string | GrammarElement | (() => Production) | Production[]
128
+
129
+ export type GrammarElement =
130
+ StringTerminal |
131
+ PatternTerminal |
132
+ Nonterminal |
133
+ Sequence |
134
+ Repetition |
135
+ Choice |
136
+ NonterminalReference
137
+
138
+ interface GrammarElementBase {
139
+ type: string
140
+ optional: boolean
141
+
142
+ cached: boolean
143
+ cacheId?: number
144
+
145
+ // The name of the grammar property this element was assigned to, if any.
146
+ // Used by the parser to refer to the element by name in error messages.
147
+ name?: string
148
+ }
149
+
150
+ export type Terminal = StringTerminal | PatternTerminal
151
+
152
+ export interface StringTerminal extends GrammarElementBase {
153
+ type: 'StringTerminal'
154
+ content: string
155
+ }
156
+
157
+ export interface PatternTerminal extends GrammarElementBase {
158
+ type: 'PatternTerminal'
159
+ pattern: Pattern
160
+ regExp: RegExp
161
+ }
162
+
163
+ export interface Nonterminal extends GrammarElementBase {
164
+ type: 'Nonterminal'
165
+ name: string
166
+ content: GrammarElement
167
+ unwrapped: boolean
168
+
169
+ // The canonical nonterminal as defined in the grammar. Clones created for
170
+ // optional references and cached references keep a reference to the original,
171
+ // so identity comparisons work across clones.
172
+ grammarNonterminal?: Nonterminal
173
+ }
174
+
175
+ export interface Sequence extends GrammarElementBase {
176
+ type: 'Sequence'
177
+ members: GrammarElement[]
178
+ }
179
+
180
+ export interface Repetition extends GrammarElementBase {
181
+ type: 'Repetition'
182
+ content: GrammarElement
183
+ }
184
+
185
+ export interface Choice extends GrammarElementBase {
186
+ type: 'Choice'
187
+ members: GrammarElement[]
188
+ exhaustive: boolean
189
+ }
190
+
191
+ export interface NonterminalReference extends GrammarElementBase {
192
+ type: 'NonterminalReference'
193
+ reference: Function
194
+ }
@@ -122,21 +122,24 @@ export function analyzeFailedMatches(failedMatches: FailedMatch[]): FailedMatchA
122
122
  // A candidate is redundant when the same terminal is also expected by a deeper
123
123
  // production within the same context, i.e. when another candidate's production
124
124
  // appears below this candidate's production in its own stack.
125
+ //
126
+ // Compare within the other candidate's own stack: if there exists another
127
+ // candidate `other` such that `candidate` is an ancestor of `other` in
128
+ // `other`'s stack, keep only `other` (innermost).
125
129
  function isRedundantCandidate(candidate: TerminalCandidate, candidates: TerminalCandidate[]): boolean {
126
- const candidateIndex = stackIndexOf(candidate.productionStack, candidate.production)
127
-
128
130
  for (const other of candidates) {
129
131
  if (other === candidate) {
130
132
  continue
131
133
  }
132
134
 
133
- const otherIndex = stackIndexOf(candidate.productionStack, other.production)
135
+ const candidateIndexInOther = stackIndexOf(other.productionStack, candidate.production)
136
+
137
+ const otherIndexInOther = stackIndexOf(other.productionStack, other.production)
134
138
 
135
- if (otherIndex > candidateIndex) {
139
+ if (candidateIndexInOther !== -1 && otherIndexInOther !== -1 && otherIndexInOther > candidateIndexInOther) {
136
140
  return true
137
141
  }
138
142
  }
139
-
140
143
  return false
141
144
  }
142
145
 
@@ -148,6 +151,26 @@ function stackIndexOf(productionStack: Nonterminal[], production: Nonterminal):
148
151
  return productionStack.findIndex(nonterminal => (nonterminal.grammarNonterminal ?? nonterminal) === canonical)
149
152
  }
150
153
 
154
+ function areTerminalsEqual(a: any, b: any): boolean {
155
+ if (a === b) {
156
+ return true
157
+ }
158
+
159
+ if (a?.type !== b?.type) {
160
+ return false
161
+ }
162
+
163
+ if (a.type === 'StringTerminal') {
164
+ return a.content === b.content
165
+ }
166
+
167
+ if (a.type === 'PatternTerminal') {
168
+ return a.regExp.source === b.regExp.source && a.regExp.flags === b.regExp.flags
169
+ }
170
+
171
+ return false
172
+ }
173
+
151
174
  function stringifyFailedTerminal(failedMatch: FailedMatch): string {
152
175
  const { terminal, productionStack } = failedMatch
153
176
 
@@ -155,10 +178,18 @@ function stringifyFailedTerminal(failedMatch: FailedMatch): string {
155
178
  return terminal.name
156
179
  }
157
180
 
158
- const innermostProduction = productionStack[productionStack.length - 1]
159
-
160
- if (innermostProduction !== undefined && innermostProduction.content === terminal) {
161
- return innermostProduction.name
181
+ // Compare terminals by structural identity: PatternTerminals are
182
+ // reconstructed objects, StringTerminals are cloned, so object
183
+ // identity vs innermostProduction.content is not reliable.
184
+ //
185
+ // Instead check if the failed terminal equals the content of any
186
+ // production in the stack by value.
187
+ for (let i = productionStack.length - 1; i >= 0; i--) {
188
+ const production = productionStack[i]
189
+
190
+ if (areTerminalsEqual(production.content, terminal)) {
191
+ return production.name
192
+ }
162
193
  }
163
194
 
164
195
  const stringifiedLengthLimit = 100
@@ -176,7 +207,7 @@ function stringifyFailedTerminal(failedMatch: FailedMatch): string {
176
207
  // during a single parse: the grammar builder creates spread copies of the canonical
177
208
  // nonterminal for optional references and for references wrapped in cached(). Each clone
178
209
  // keeps a reference to the original in 'grammarNonterminal'.
179
- export function findCommonProductionPrefix(productionStacks: Nonterminal[][]): Nonterminal[] {
210
+ function findCommonProductionPrefix(productionStacks: Nonterminal[][]): Nonterminal[] {
180
211
  if (productionStacks.length === 0) {
181
212
  return []
182
213
  }
@@ -251,7 +282,7 @@ function buildExpectedGroupsMessage(expected: readonly ExpectedTerminal[]): stri
251
282
  .join('\n\n')
252
283
  }
253
284
 
254
- export function formatErrorContext(input: string, offset: number, maxDisplayedLineLength: number): string {
285
+ function formatErrorContext(input: string, offset: number, maxDisplayedLineLength: number): string {
255
286
  const { line, column } = getLineAndColumn(input, offset)
256
287
 
257
288
  let lineStartOffset = offset
@@ -5,7 +5,7 @@ import { type FailedMatch, ParseError } from './ParseError.js'
5
5
  //////////////////////////////////////////////////////////////////////////////////////////////
6
6
  // Main parser function
7
7
  //////////////////////////////////////////////////////////////////////////////////////////////
8
- export function parse(inputString: string, grammar: Grammar<any>, options?: TopDownParserOptions) {
8
+ export function parse(inputString: string, grammar: Grammar<any>, options?: ParserOptions) {
9
9
  options = { ...options }
10
10
 
11
11
  const inputLength = inputString.length
@@ -15,7 +15,7 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
15
15
  let bestFailedMatches: FailedMatch[] = []
16
16
  let bestFailedMatchesOffset = -1
17
17
 
18
- const cacheKeyOffsetMultiplier = grammar.maxCacheId + 1
18
+ const cacheKeyOffsetMultiplier = grammar.maxCacheId > 0 ? grammar.maxCacheId + 1 : 1
19
19
  const parseResultsCache = new Map<number, ParseResult | null>()
20
20
 
21
21
  function updateBestFailedMatchesIfNeeded(terminal: Terminal, startOffset: number) {
@@ -58,6 +58,21 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
58
58
  }
59
59
 
60
60
  function tryParseUncached(grammarElement: GrammarElement, startOffset: number): ParseResult | null {
61
+ const result = tryParseElement(grammarElement, startOffset)
62
+
63
+ if (result === null && grammarElement.optional === true) {
64
+ // An optional element that doesn't match is "skipped": it succeeds
65
+ // by consuming no input and producing no nodes. This uniformly
66
+ // implements the `possibly()` "or skip" semantics wherever the
67
+ // element appears (sequence members, repetition content, choice
68
+ // members, or as the content of a production itself).
69
+ return { endOffset: startOffset, nodes: undefined, skipped: true }
70
+ }
71
+
72
+ return result
73
+ }
74
+
75
+ function tryParseElement(grammarElement: GrammarElement, startOffset: number): ParseResult | null {
61
76
  switch (grammarElement.type) {
62
77
  case 'StringTerminal': {
63
78
  const target = grammarElement.content
@@ -102,16 +117,35 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
102
117
  const groupsIndices = matchResults.indices
103
118
 
104
119
  if (groupsIndices !== undefined) {
105
- let namedGroupIndicesIdentifiers: string[] | undefined = undefined
120
+ const children: ParseTreeNode[] = []
121
+
122
+ // Build a reverse lookup from indices entry -> group name by identity.
123
+ // This is robust even when some groups do not participate (undefined)
124
+ // and avoids relying on Object.keys order aligning with numeric indices.
125
+ let indexToName: (string | undefined)[] | undefined = undefined
106
126
 
107
127
  if (groupsIndices.groups) {
108
- namedGroupIndicesIdentifiers = Object.keys(groupsIndices.groups)
109
- }
128
+ indexToName = new Array(groupsIndices.length)
110
129
 
111
- const children: ParseTreeNode[] = []
130
+ for (const [name, range] of Object.entries(groupsIndices.groups)) {
131
+ if (range === undefined) {
132
+ continue
133
+ }
112
134
 
113
- for (let i = 1; i < groupsIndices.length; i++) {
114
- const groupIndices = groupsIndices[i]
135
+ // `range` is the same array instance as groupsIndices[index]
136
+ const index = groupsIndices.indexOf(range)
137
+
138
+ if (index >= 0) {
139
+ indexToName[index] = name
140
+ }
141
+ }
142
+
143
+ // Mark which indices actually correspond to named groups
144
+ // (validatePatternCaptureGroups guarantees all-or-nothing, but be defensive)
145
+ }
146
+
147
+ for (let i = 1; i < (groupsIndices as unknown as number[][]).length; i++) {
148
+ const groupIndices = (groupsIndices as unknown as (number[] | undefined)[])[i]
115
149
 
116
150
  if (groupIndices === undefined) {
117
151
  continue
@@ -120,8 +154,10 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
120
154
  const groupStartOffset = startOffset + groupIndices[0]
121
155
  const groupEndOffset = startOffset + groupIndices[1]
122
156
 
157
+ const name = indexToName?.[i] ?? i.toString()
158
+
123
159
  children.push({
124
- name: namedGroupIndicesIdentifiers ? namedGroupIndicesIdentifiers[i - 1] : i.toString(),
160
+ name,
125
161
  startOffset: groupStartOffset,
126
162
  endOffset: groupEndOffset,
127
163
  sourceText: inputString.substring(groupStartOffset, groupEndOffset),
@@ -151,6 +187,13 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
151
187
  return null
152
188
  }
153
189
 
190
+ if (result.skipped === true) {
191
+ // The content was skipped (it was optional and didn't match),
192
+ // so no node should be created for this nonterminal either,
193
+ // and the skip is propagated to the parent.
194
+ return { endOffset: startOffset, nodes: undefined, skipped: true }
195
+ }
196
+
154
197
  const grammarElementName = grammarElement.name
155
198
 
156
199
  if (grammarElement.unwrapped) {
@@ -297,6 +340,12 @@ export function parse(inputString: string, grammar: Grammar<any>, options?: TopD
297
340
  export interface ParseResult {
298
341
  endOffset: number
299
342
  nodes: ParseTreeNode[] | undefined
343
+
344
+ // Marks a result produced by an optional element that was "skipped" (it
345
+ // didn't match, so the parse succeeded by consuming no input and producing
346
+ // no nodes). Set internally by the parser; a receiving `Nonterminal` uses
347
+ // it to avoid creating a node for skipped content.
348
+ skipped?: boolean
300
349
  }
301
350
 
302
351
  export interface ParseTreeNode {
@@ -310,5 +359,5 @@ export interface ParseTreeNode {
310
359
  children?: ParseTreeNode[]
311
360
  }
312
361
 
313
- export interface TopDownParserOptions {
362
+ export interface ParserOptions {
314
363
  }
@@ -131,7 +131,7 @@ export function detectAndAnnotateOptionalNodes(rootNode: GrammarElement) {
131
131
  // If it stays false, it means that no improvement was made during the iteration,
132
132
  // and we should exit the loop.
133
133
  let atLastOneDependencyResolvedInAnyNode = false
134
-
134
+
135
135
  const nodesToDelete: GrammarElement[] = []
136
136
 
137
137
  // Scan the unresolved nodes to locate any new resolved dependencies
@@ -189,10 +189,12 @@ export function detectAndAnnotateOptionalNodes(rootNode: GrammarElement) {
189
189
  }
190
190
  }
191
191
 
192
- // All remaining unresolved nodes must now be optional,
193
- // since they are all mutually cyclic and all their non-cyclic grammar elements are known to be optional.
192
+ // All remaining unresolved nodes are part of a strongly-connected cyclic
193
+ // component with no resolvable nullable base case. The least fixed-point
194
+ // for nullability is `false` (not optional): a cycle without an empty
195
+ // alternative cannot derive the empty string.
194
196
  for (const node of unresolvedNodes.keys()) {
195
- resolvedNodes.set(node, true)
197
+ resolvedNodes.set(node, false)
196
198
  unresolvedNodes.delete(node)
197
199
  }
198
200
 
@@ -210,7 +212,11 @@ export function detectAndErrorOnLeftRecursion(rootNode: GrammarElement) {
210
212
  if (node.type === 'Nonterminal') {
211
213
  throw new Error(`Detected left recursion for nonterminal '${node.name}'.`)
212
214
  } else {
213
- throw new Error(`Detected left recursion for node: ${JSON.stringify(node, undefined, 4)}`)
215
+ try {
216
+ throw new Error(`Detected left recursion for node: ${JSON.stringify(node, undefined, 4)}`)
217
+ } catch {
218
+ throw new Error(`Detected left recursion for node type '${(node as any).type}'.`)
219
+ }
214
220
  }
215
221
  }
216
222
 
@@ -282,7 +288,7 @@ export function validatePatternCaptureGroups(pattern: Pattern): void {
282
288
  case 'capture': {
283
289
  if (node.name !== undefined) {
284
290
  if (seenNamedCaptureGroupNames.has(node.name)) {
285
- throw new Error(`The regular expression pattern contains multiple named capture groups with the same name '${node.name}'. Named capture groups must have unique names.`)
291
+ throw new Error(`The regular expression pattern contains multiple named capture groups with the same name '${(node as any).name}'. Named capture groups must have unique names.`)
286
292
  }
287
293
 
288
294
  seenNamedCaptureGroupNames.add(node.name)
@@ -296,7 +302,11 @@ export function validatePatternCaptureGroups(pattern: Pattern): void {
296
302
  return
297
303
  }
298
304
 
299
- // Composite nodes that wrap content without creating a capturing group:
305
+ // Composite nodes that wrap content without creating a capturing group.
306
+ // regexp-composer uses a `greedy` boolean to distinguish `zeroOrMore` vs
307
+ // `zeroOrMoreNonGreedy` etc, but both share the same `type` string
308
+ // (`zeroOrMore`, `oneOrMore`, `repeated`). Handle any type that wraps
309
+ // a single `content` field.
300
310
  case 'possibly':
301
311
  case 'zeroOrMore':
302
312
  case 'oneOrMore':
@@ -179,7 +179,7 @@ export class TerminalToText {
179
179
 
180
180
  export function escapeChar(char: string): string {
181
181
  const charMapping: Record<string, string> = {
182
- //'\\': '\\\\',
182
+ '\\': '\\\\',
183
183
  "'": "\\'",
184
184
  '\n': '\\n',
185
185
  '\r': '\\r',
package/src/tests/Test.ts CHANGED
@@ -134,7 +134,7 @@ function test1() {
134
134
  //testParserError2()
135
135
 
136
136
  //testJsonParser()
137
- testXmlParser()
138
- //testRegExpParser()
137
+ //testXmlParser()
138
+ testRegExpParser()
139
139
 
140
140
  //test1()
@@ -1,3 +1,5 @@
1
+ import { type ParseTreeNode } from '../exports/Exports.js'
2
+
1
3
  export function roundToDigits(val: number, digits = 3) {
2
4
  const multiplier = 10 ** digits
3
5
 
@@ -17,7 +19,7 @@ export function isBigInt(value: unknown): value is bigint {
17
19
  }
18
20
 
19
21
  export function isObject(value: unknown): value is object {
20
- return typeof value === 'object' && !Array.isArray(value)
22
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
21
23
  }
22
24
 
23
25
  export function isArray(value: unknown): value is unknown[] {
@@ -31,3 +33,21 @@ export function isBoolean(value: unknown): value is boolean {
31
33
  export function isFunction(value: unknown): value is Function {
32
34
  return typeof value === 'function'
33
35
  }
36
+
37
+ export function stringifyParseTree(parseTree: ParseTreeNode[], depth = 0): string {
38
+ let result = ''
39
+
40
+ const getNewlineWithIndent = (depth: number) => `\n${'\t'.repeat(depth)}`
41
+
42
+ for (const node of parseTree) {
43
+ result += `${getNewlineWithIndent(depth)}${node.name}`
44
+
45
+ if (node.children && node.children.length > 0) {
46
+ result += stringifyParseTree(node.children, depth + 1)
47
+ } else {
48
+ result += `${getNewlineWithIndent(depth + 1)}'${node.sourceText}'`
49
+ }
50
+ }
51
+
52
+ return result
53
+ }
package/tsconfig.json CHANGED
@@ -1,55 +1,53 @@
1
1
  {
2
- // Visit https://aka.ms/tsconfig to read more about this file
3
- "compilerOptions": {
4
- // File Layout
5
- "rootDir": "./src",
6
- "outDir": "./dist",
7
-
8
- // Environment Settings
9
- // See also https://aka.ms/tsconfig/module
10
- "module": "nodenext",
11
- "target": "esnext",
12
- "types": ["node"],
13
- // For nodejs:
14
- // "lib": ["esnext"],
15
- // "types": ["node"],
16
- // and npm install -D @types/node
17
-
18
- // Other Outputs
19
- "sourceMap": true,
20
- "declaration": true,
21
- "declarationMap": true,
22
-
23
- // Stricter Typechecking Options
24
- // "noUncheckedIndexedAccess": true,
25
- // "exactOptionalPropertyTypes": true,
26
-
27
- // Style Options
28
- "noImplicitReturns": true,
29
- // "noImplicitOverride": true,
30
- // "noUnusedLocals": true,
31
- // "noUnusedParameters": true,
32
- // "noFallthroughCasesInSwitch": true,
33
- // "noPropertyAccessFromIndexSignature": true,
34
-
35
- // Recommended Options
36
- "strict": true,
37
- "jsx": "react-jsx",
38
- // "verbatimModuleSyntax": true,
39
- "isolatedModules": true,
40
- "noUncheckedSideEffectImports": true,
41
- "moduleDetection": "force",
42
- "skipLibCheck": true,
43
-
44
- // Extended options not in generated tsconfig.json
45
- "newLine": "lf",
46
- "forceConsistentCasingInFileNames": true,
47
- "alwaysStrict": true,
48
-
49
- "noImplicitThis": true,
50
- "strictBindCallApply": true,
51
- "strictFunctionTypes": true,
52
-
53
- "noErrorTruncation": true,
54
- }
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ // Only compile library sources; tests under /tests are run by vitest and typechecked separately (tsconfig.tests.json)
4
+ "compilerOptions": {
5
+ // File Layout
6
+ "rootDir": "./src",
7
+ "outDir": "./dist",
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "nodenext",
11
+ "target": "esnext",
12
+ "types": [
13
+ "node"
14
+ ],
15
+ // For nodejs:
16
+ // "lib": ["esnext"],
17
+ // "types": ["node"],
18
+ // and npm install -D @types/node
19
+ // Other Outputs
20
+ "sourceMap": true,
21
+ "declaration": true,
22
+ "declarationMap": true,
23
+ // Stricter Typechecking Options
24
+ // "noUncheckedIndexedAccess": true,
25
+ // "exactOptionalPropertyTypes": true,
26
+ // Style Options
27
+ "noImplicitReturns": true,
28
+ // "noImplicitOverride": true,
29
+ // "noUnusedLocals": true,
30
+ // "noUnusedParameters": true,
31
+ // "noFallthroughCasesInSwitch": true,
32
+ // "noPropertyAccessFromIndexSignature": true,
33
+ // Recommended Options
34
+ "strict": true,
35
+ "jsx": "react-jsx",
36
+ // "verbatimModuleSyntax": true,
37
+ "isolatedModules": true,
38
+ "noUncheckedSideEffectImports": true,
39
+ "moduleDetection": "force",
40
+ "skipLibCheck": true,
41
+ // Extended options not in generated tsconfig.json
42
+ "newLine": "lf",
43
+ "forceConsistentCasingInFileNames": true,
44
+ "alwaysStrict": true,
45
+ "noImplicitThis": true,
46
+ "strictBindCallApply": true,
47
+ "strictFunctionTypes": true,
48
+ "noErrorTruncation": true,
49
+ },
50
+ "include": [
51
+ "src"
52
+ ]
55
53
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"Grammar.d.ts","sourceRoot":"","sources":["../../src/parser-generator/Grammar.ts"],"names":[],"mappings":"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"}