grammar-composer 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.
Files changed (57) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +387 -0
  3. package/dist/Exports.d.ts +1 -0
  4. package/dist/Exports.js +2 -0
  5. package/dist/Exports.js.map +1 -0
  6. package/dist/Grammar.d.ts +62 -0
  7. package/dist/Grammar.js +376 -0
  8. package/dist/Grammar.js.map +1 -0
  9. package/dist/GrammarComposer.d.ts +61 -0
  10. package/dist/GrammarComposer.js +383 -0
  11. package/dist/GrammarComposer.js.map +1 -0
  12. package/dist/Grammars/JsonGrammar.d.ts +16 -0
  13. package/dist/Grammars/JsonGrammar.js +74 -0
  14. package/dist/Grammars/JsonGrammar.js.map +1 -0
  15. package/dist/Grammars/XmlGrammar.d.ts +13 -0
  16. package/dist/Grammars/XmlGrammar.js +74 -0
  17. package/dist/Grammars/XmlGrammar.js.map +1 -0
  18. package/dist/ParserTest.d.ts +1 -0
  19. package/dist/ParserTest.js +37 -0
  20. package/dist/ParserTest.js.map +1 -0
  21. package/dist/Test.d.ts +1 -0
  22. package/dist/Test.js +74 -0
  23. package/dist/Test.js.map +1 -0
  24. package/dist/TestData.d.ts +5 -0
  25. package/dist/TestData.js +466 -0
  26. package/dist/TestData.js.map +1 -0
  27. package/dist/TopDownParser.d.ts +13 -0
  28. package/dist/TopDownParser.js +229 -0
  29. package/dist/TopDownParser.js.map +1 -0
  30. package/dist/data/TestData.d.ts +5 -0
  31. package/dist/data/TestData.js +466 -0
  32. package/dist/data/TestData.js.map +1 -0
  33. package/dist/test-data/TestData.d.ts +5 -0
  34. package/dist/test-data/TestData.js +466 -0
  35. package/dist/test-data/TestData.js.map +1 -0
  36. package/dist/test-grammars/JsonGrammar.d.ts +17 -0
  37. package/dist/test-grammars/JsonGrammar.js +74 -0
  38. package/dist/test-grammars/JsonGrammar.js.map +1 -0
  39. package/dist/test-grammars/XmlGrammar.d.ts +14 -0
  40. package/dist/test-grammars/XmlGrammar.js +74 -0
  41. package/dist/test-grammars/XmlGrammar.js.map +1 -0
  42. package/dist/utilities/Timer.d.ts +13 -0
  43. package/dist/utilities/Timer.js +70 -0
  44. package/dist/utilities/Timer.js.map +1 -0
  45. package/dist/utilities/utilities.d.ts +8 -0
  46. package/dist/utilities/utilities.js +26 -0
  47. package/dist/utilities/utilities.js.map +1 -0
  48. package/package.json +40 -0
  49. package/src/GrammarComposer.ts +541 -0
  50. package/src/Test.ts +101 -0
  51. package/src/TopDownParser.ts +304 -0
  52. package/src/test-data/TestData.ts +470 -0
  53. package/src/test-grammars/JsonGrammar.ts +118 -0
  54. package/src/test-grammars/XmlGrammar.ts +141 -0
  55. package/src/utilities/Timer.ts +95 -0
  56. package/src/utilities/Utilities.ts +33 -0
  57. package/tsconfig.json +105 -0
@@ -0,0 +1,541 @@
1
+ import { Pattern, buildRegExp, inputStart, isPatternOptional } from 'regexp-composer'
2
+ import { isArray, isBoolean, isFunction, isString } from "./utilities/Utilities.js"
3
+
4
+ export * from './TopDownParser.js'
5
+
6
+ /////////////////////////////////////////////////////////////////////////////////////////////////
7
+ // Grammar builder method
8
+ /////////////////////////////////////////////////////////////////////////////////////////////////
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()
12
+ }
13
+
14
+ const nameLookup = new Map<any, keyof T>()
15
+ const nonterminalLookup = new Map<Function, Nonterminal>()
16
+ const optionalNonterminalLookup = new Map<Function, Nonterminal>()
17
+
18
+ for (const key in obj) {
19
+ const objectProperty = obj[key]
20
+
21
+ nameLookup.set(objectProperty, key)
22
+
23
+ if (!isFunction(objectProperty)) {
24
+ continue
25
+ }
26
+
27
+ const productionContent = objectProperty()
28
+ const normalizedProduction = productionToGrammarElement(productionContent)
29
+
30
+ const newNonterminal = nonterminal(key, normalizedProduction)
31
+ const newOptionalNonterminal = { ...newNonterminal, optional: true }
32
+
33
+ nonterminalLookup.set(objectProperty, newNonterminal)
34
+ optionalNonterminalLookup.set(objectProperty, newOptionalNonterminal)
35
+ }
36
+
37
+ let uniqueIdCounter = 0
38
+ const uniqueIdSource = () => uniqueIdCounter++
39
+
40
+ for (const [func, nonterminal] of nonterminalLookup) {
41
+ const preparedContent = prepareGrammarElement(nonterminal.content, nameLookup as Map<any, string>, nonterminalLookup, optionalNonterminalLookup, uniqueIdSource)
42
+
43
+ nonterminal.content = preparedContent
44
+
45
+ const optionalNonterminal = optionalNonterminalLookup.get(func)!
46
+ optionalNonterminal.content = preparedContent
47
+ }
48
+
49
+ let startNonterminal = nonterminalLookup.get(obj[startProductionName] as Function)
50
+
51
+ if (!startNonterminal) {
52
+ throw new Error(`Couldn't find a start production named '${startProductionName as string}'.`)
53
+ }
54
+
55
+ detectAndAnnotateOptionalNodes(startNonterminal)
56
+ detectAndErrorOnLeftRecursion(startNonterminal)
57
+
58
+ const nonterminals: { [key in keyof T]: Nonterminal } = {} as any
59
+
60
+ for (const [key, nonterminal] of nonterminalLookup) {
61
+ nonterminals[nameLookup.get(key)!] = nonterminal
62
+ }
63
+
64
+ return {
65
+ rootElement: nonterminals[startProductionName],
66
+ productions: nonterminals,
67
+ maxElementId: uniqueIdCounter
68
+ } as Grammar<T>
69
+ }
70
+
71
+ function prepareGrammarElement(
72
+ rootElement: GrammarElement,
73
+ nameLookup: Map<any, string>,
74
+ nonterminalLookup: Map<Function, Nonterminal>,
75
+ optionalNonterminalLookup: Map<Function, Nonterminal>,
76
+ getUniqueId: () => number
77
+ ): GrammarElement {
78
+ function prepare(element: GrammarElement): GrammarElement {
79
+ switch (element.type) {
80
+ case 'StringTerminal':
81
+ case 'Nonterminal': {
82
+ if (element.uniqueId === undefined) {
83
+ element.uniqueId = getUniqueId()
84
+ }
85
+
86
+ return element
87
+ }
88
+
89
+ case 'PatternTerminal': {
90
+ return {
91
+ ...element,
92
+ name: nameLookup.get(element) ?? '',
93
+ uniqueId: getUniqueId()
94
+ }
95
+ }
96
+
97
+ case 'Repetition': {
98
+ return {
99
+ ...element,
100
+ content: prepare(element.content),
101
+ uniqueId: getUniqueId()
102
+ }
103
+ }
104
+
105
+ case 'Sequence':
106
+ case 'Choice': {
107
+ return {
108
+ ...element,
109
+ members: element.members.map(element => prepare(element)),
110
+ uniqueId: getUniqueId()
111
+ }
112
+ }
113
+
114
+ case 'NonterminalReference': {
115
+ const reference = element.reference
116
+
117
+ let nonterminal: Nonterminal | undefined
118
+
119
+ if (element.optional) {
120
+ nonterminal = optionalNonterminalLookup.get(reference)
121
+ } else {
122
+ nonterminal = nonterminalLookup.get(reference)
123
+ }
124
+
125
+ if (!nonterminal) {
126
+ throw new Error(`Couldn't resolve function reference in grammar element: ${JSON.stringify(element)}`)
127
+ }
128
+
129
+ if (nonterminal.uniqueId === undefined) {
130
+ nonterminal.uniqueId = getUniqueId()
131
+ }
132
+
133
+ return nonterminal
134
+ }
135
+ }
136
+ }
137
+
138
+ return prepare(rootElement)
139
+ }
140
+
141
+ /////////////////////////////////////////////////////////////////////////////////////////////////
142
+ // Internal static analysis methods
143
+ /////////////////////////////////////////////////////////////////////////////////////////////////
144
+ function detectAndAnnotateOptionalNodes(rootNode: GrammarElement) {
145
+ const visitedNodes = new Set<GrammarElement>()
146
+
147
+ const resolvedNodes = new Map<GrammarElement, boolean>()
148
+ const unresolvedNodes = new Map<GrammarElement, Set<GrammarElement>>()
149
+
150
+ function processDepthFirst(node: GrammarElement): boolean | undefined {
151
+ if (visitedNodes.has(node)) {
152
+ return resolvedNodes.get(node)
153
+ }
154
+
155
+ visitedNodes.add(node)
156
+
157
+ switch (node.type) {
158
+ case 'StringTerminal':
159
+ case 'PatternTerminal': {
160
+ resolvedNodes.set(node, node.optional)
161
+
162
+ return node.optional
163
+ }
164
+
165
+ case 'Nonterminal':
166
+ case 'Repetition': {
167
+ const result = processDepthFirst(node.content)
168
+
169
+ if (node.optional) {
170
+ resolvedNodes.set(node, true)
171
+
172
+ return true
173
+ } else if (isBoolean(result)) {
174
+ resolvedNodes.set(node, result)
175
+
176
+ return result
177
+ } else {
178
+ unresolvedNodes.set(node, new Set([node.content]))
179
+
180
+ return undefined
181
+ }
182
+ }
183
+
184
+ case 'Sequence':
185
+ case 'Choice': {
186
+ const dependencies = new Set<GrammarElement>()
187
+
188
+ let allResolvedElementsAreOptional = true
189
+
190
+ for (const element of node.members) {
191
+ const result = processDepthFirst(element)
192
+
193
+ if (isBoolean(result)) {
194
+ if (result == false) {
195
+ allResolvedElementsAreOptional = false
196
+ }
197
+ } else {
198
+ dependencies.add(element)
199
+ }
200
+ }
201
+
202
+ if (node.optional == true) {
203
+ resolvedNodes.set(node, true)
204
+
205
+ return true
206
+ } else if (dependencies.size == 0 || !allResolvedElementsAreOptional) {
207
+ resolvedNodes.set(node, allResolvedElementsAreOptional)
208
+
209
+ return allResolvedElementsAreOptional
210
+ } else {
211
+ unresolvedNodes.set(node, dependencies)
212
+
213
+ return undefined
214
+ }
215
+ }
216
+ }
217
+
218
+ return undefined
219
+ }
220
+
221
+ // Process depth first to resolve the easy cases, for productions that contain
222
+ // no cyclic references:
223
+ processDepthFirst(rootNode)
224
+
225
+ // Now the remainder consists of nodes containing cyclic references that have not yet been resolved.
226
+ // Use a form of iterative elimination and substitution to resolve them:
227
+ while (unresolvedNodes.size > 0) {
228
+ // This variable tracks whether at least one dependency was resolved, in any node.
229
+ // If it stays false, it means that no improvement was made during the iteration,
230
+ // and we should exit the loop.
231
+ let atLastOneDependencyResolvedInAnyNode = false
232
+
233
+ // Scan the unresolved nodes to locate any new resolved dependencies
234
+ for (const [node, dependencies] of unresolvedNodes) {
235
+ let nonOptionalDependencyFound = false
236
+
237
+ // Iterate over all unresolved dependencies for the node
238
+ for (const dependency of dependencies) {
239
+ // Check if the dependency has been resolved
240
+ const value = resolvedNodes.get(dependency)
241
+
242
+ if (value !== undefined) {
243
+ // If it did, record that some dependencies were resolved
244
+ atLastOneDependencyResolvedInAnyNode = true
245
+
246
+ if (value === false) {
247
+ // If the value was false, then the entire target node must not be optional
248
+ nonOptionalDependencyFound = true
249
+
250
+ break
251
+ } else {
252
+ // If the result was true, remove the dependency from the set
253
+ dependencies.delete(dependency)
254
+ }
255
+ }
256
+ }
257
+
258
+ // If either a non-optional dependency was found, or all dependencies were resolved,
259
+ // resolve the target node:
260
+ if (nonOptionalDependencyFound || dependencies.size === 0) {
261
+ const isOptional = !nonOptionalDependencyFound
262
+
263
+ resolvedNodes.set(node, isOptional)
264
+ unresolvedNodes.delete(node)
265
+ }
266
+ }
267
+
268
+ // If not even one dependency was eliminated for any node,
269
+ // it means that only mutually cyclic nodes are left unresolved, so exit the loop.
270
+ if (!atLastOneDependencyResolvedInAnyNode) {
271
+ break
272
+ }
273
+ }
274
+
275
+ // All remaining unresolved nodes must now be optional,
276
+ // since they are all mutually cyclic and all their non-cyclic grammar elements are known to be optional.
277
+ for (const node of unresolvedNodes.keys()) {
278
+ resolvedNodes.set(node, true)
279
+ unresolvedNodes.delete(node)
280
+ }
281
+
282
+ // Finally set the 'optional' property of all nodes based on the detected values.
283
+ for (const [node, isOptional] of resolvedNodes) {
284
+ node.optional = isOptional
285
+ }
286
+ }
287
+
288
+ function detectAndErrorOnLeftRecursion(rootNode: GrammarElement) {
289
+ const currentlyIteratedNodes = new Set<GrammarElement>()
290
+
291
+ function detect(node: GrammarElement) {
292
+ if (currentlyIteratedNodes.has(node)) {
293
+ if (node.type === 'Nonterminal') {
294
+ throw new Error(`Detected left recursion for nonterminal '${node.name}'.`)
295
+ } else {
296
+ throw new Error(`Detected left recursion for node: ${JSON.stringify(node, undefined, 4)}`)
297
+ }
298
+ }
299
+
300
+ currentlyIteratedNodes.add(node)
301
+
302
+ switch (node.type) {
303
+ case 'Nonterminal':
304
+ case 'Repetition': {
305
+ detect(node.content)
306
+
307
+ break
308
+ }
309
+
310
+ case 'Sequence': {
311
+ for (const member of node.members) {
312
+ detect(member)
313
+
314
+ if (!member.optional) {
315
+ break
316
+ }
317
+ }
318
+
319
+ break
320
+ }
321
+
322
+ case 'Choice': {
323
+ for (const member of node.members) {
324
+ detect(member)
325
+ }
326
+
327
+ break
328
+ }
329
+ }
330
+
331
+ currentlyIteratedNodes.delete(node)
332
+ }
333
+
334
+ detect(rootNode)
335
+ }
336
+
337
+ /////////////////////////////////////////////////////////////////////////////////////////////////
338
+ // Exported builder methods
339
+ /////////////////////////////////////////////////////////////////////////////////////////////////
340
+ export function zeroOrMore(content: Production): Repetition {
341
+ return {
342
+ type: 'Repetition',
343
+ content: productionToGrammarElement(content),
344
+ optional: true
345
+ }
346
+ }
347
+
348
+ export function oneOrMore(content: Production): Repetition {
349
+ return {
350
+ type: 'Repetition',
351
+ content: productionToGrammarElement(content),
352
+ optional: false
353
+ }
354
+ }
355
+
356
+ export function anyOf(...members: Production[]): Choice {
357
+ if (members.length == 0) {
358
+ throw new Error(`'anyOf' requires at least one member.`)
359
+ }
360
+
361
+ const normalizedMembers = members.map(member => productionToGrammarElement(member))
362
+
363
+ return {
364
+ type: 'Choice',
365
+ members: normalizedMembers,
366
+ optional: false,
367
+ exhaustive: false
368
+ }
369
+ }
370
+
371
+ export function bestOf(...members: Production[]): Choice {
372
+ if (members.length == 0) {
373
+ throw new Error(`'bestOf' requires at least one member.`)
374
+ }
375
+
376
+ const normalizedMembers = members.map(member => productionToGrammarElement(member))
377
+
378
+ return {
379
+ type: 'Choice',
380
+ members: normalizedMembers,
381
+ optional: false,
382
+ exhaustive: true
383
+ }
384
+ }
385
+
386
+ export function possibly<T extends Production>(content: Production): T {
387
+ return { ...productionToGrammarElement(content), optional: true } as T
388
+ }
389
+
390
+ export function pattern(pattern: Pattern): PatternTerminal {
391
+ if (isArray(pattern)) {
392
+ pattern = [inputStart, ...pattern]
393
+ } else {
394
+ pattern = [inputStart, pattern]
395
+ }
396
+
397
+ const regExp = buildRegExp(pattern)
398
+ const optional = isPatternOptional(pattern)
399
+
400
+ return {
401
+ type: 'PatternTerminal',
402
+ name: '[Pattern]',
403
+ pattern,
404
+ regExp,
405
+ optional,
406
+ }
407
+ }
408
+
409
+ export function cached<T extends Production>(content: Production): T {
410
+ return { ...productionToGrammarElement(content), cached: true } as T
411
+ }
412
+
413
+ export function uncached<T extends Production>(content: Production): T {
414
+ return { ...productionToGrammarElement(content), cached: false } as T
415
+ }
416
+
417
+ /////////////////////////////////////////////////////////////////////////////////////////////////
418
+ // Internal builder methods
419
+ /////////////////////////////////////////////////////////////////////////////////////////////////
420
+ function stringTerminal(content: string): StringTerminal {
421
+ if (content.length < 1) {
422
+ throw new Error(`A string terminal must have a length of at least 1 character`)
423
+ }
424
+
425
+ return {
426
+ type: 'StringTerminal',
427
+ content,
428
+ optional: false
429
+ }
430
+ }
431
+
432
+ function nonterminal(name: string, content: GrammarElement): Nonterminal {
433
+ if (name.length < 1) {
434
+ throw new Error(`A nonterminal name must include at least 1 character.`)
435
+ }
436
+
437
+ return {
438
+ type: 'Nonterminal',
439
+ name,
440
+ content,
441
+ optional: false,
442
+ }
443
+ }
444
+
445
+ function sequence(members: GrammarElement[]): Sequence {
446
+ return {
447
+ type: 'Sequence',
448
+ members,
449
+ optional: false
450
+ }
451
+ }
452
+
453
+ function unresolvedReference(reference: Function): NonterminalReference {
454
+ return {
455
+ type: 'NonterminalReference',
456
+ reference,
457
+ optional: false
458
+ }
459
+ }
460
+
461
+ function productionToGrammarElement(production: Production): GrammarElement {
462
+ if (isString(production)) {
463
+ return stringTerminal(production)
464
+ } else if (isArray(production)) {
465
+ const normalizedMembers = production.map(element => productionToGrammarElement(element))
466
+
467
+ return sequence(normalizedMembers)
468
+ } else if (isFunction(production)) {
469
+ return unresolvedReference(production)
470
+ } else {
471
+ return production
472
+ }
473
+ }
474
+
475
+ /////////////////////////////////////////////////////////////////////////////////////////////////
476
+ // Type definitions
477
+ /////////////////////////////////////////////////////////////////////////////////////////////////
478
+ export interface Grammar<T> {
479
+ rootElement: Nonterminal
480
+ productions: { [key in keyof T]: any }
481
+ maxElementId: number
482
+ }
483
+
484
+ export type Production = string | GrammarElement | (() => Production) | Production[]
485
+
486
+ export type GrammarElement =
487
+ StringTerminal |
488
+ PatternTerminal |
489
+ Nonterminal |
490
+ Sequence |
491
+ Repetition |
492
+ Choice |
493
+ NonterminalReference
494
+
495
+ interface GrammarElementBase {
496
+ type: string
497
+ optional: boolean
498
+ uniqueId?: number
499
+ cached?: boolean
500
+ }
501
+
502
+ export type Terminal = StringTerminal | PatternTerminal
503
+
504
+ export interface StringTerminal extends GrammarElementBase {
505
+ type: 'StringTerminal'
506
+ content: string
507
+ }
508
+
509
+ export interface PatternTerminal extends GrammarElementBase {
510
+ type: 'PatternTerminal'
511
+ name: string
512
+ pattern: Pattern | Pattern[]
513
+ regExp: RegExp
514
+ }
515
+
516
+ export interface Nonterminal extends GrammarElementBase {
517
+ type: 'Nonterminal'
518
+ name: string
519
+ content: GrammarElement
520
+ }
521
+
522
+ export interface Sequence extends GrammarElementBase {
523
+ type: 'Sequence'
524
+ members: GrammarElement[]
525
+ }
526
+
527
+ export interface Repetition extends GrammarElementBase {
528
+ type: 'Repetition'
529
+ content: GrammarElement
530
+ }
531
+
532
+ export interface Choice extends GrammarElementBase {
533
+ type: 'Choice'
534
+ members: GrammarElement[]
535
+ exhaustive: boolean
536
+ }
537
+
538
+ export interface NonterminalReference extends GrammarElementBase {
539
+ type: 'NonterminalReference'
540
+ reference: Function
541
+ }
package/src/Test.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { Timer } from "./utilities/Timer.js"
2
+ import { jsonSample2 } from "./test-data/TestData.js"
3
+ import { anyOf, buildGrammar, parse } from "./GrammarComposer.js"
4
+ import { JsonGrammar } from "./test-grammars/JsonGrammar.js"
5
+ import { XmlGrammar } from "./test-grammars/XmlGrammar.js"
6
+
7
+ const log = console.log
8
+
9
+ function testBasic() {
10
+ class MyGrammar {
11
+ p1 = () => ['a', 'b', 'c', anyOf(this.p2, this.p3)]
12
+
13
+ p2 = () => ['x', this.p4, 'z']
14
+
15
+ p3 = () => ['x', this.p4, 'z', 'u']
16
+
17
+ p4 = () => ['y']
18
+ }
19
+
20
+ const grammar = buildGrammar(MyGrammar, 'p1')
21
+
22
+ const result = parse('abcxyzu', grammar)
23
+
24
+ console.log(JSON.stringify(result, undefined, 4))
25
+ }
26
+
27
+
28
+ function testJsonParser() {
29
+ const jsonString = jsonSample2
30
+
31
+ const grammar = buildGrammar(JsonGrammar, 'expression')
32
+
33
+ function run() {
34
+ const timer = new Timer()
35
+ const result1 = parse(jsonString, grammar)
36
+ timer.logAndRestart('Parse')
37
+
38
+ const result2 = JSON.parse(jsonString)
39
+ timer.logAndRestart('JSON.Parse')
40
+
41
+ log(JSON.stringify(result2, undefined, 4))
42
+ }
43
+
44
+ for (let i = 0; i < 1; i++) {
45
+ run()
46
+ }
47
+ }
48
+
49
+ function testXmlParser() {
50
+ const xmlString = `
51
+ <!DOCTYPE web-app>
52
+
53
+ <menu>
54
+ <header>Adobe SVG Viewer</header>
55
+ <item action="Open" id="Open">Open</item>
56
+ <item action="OpenNew" id="OpenNew">Open New</item>
57
+ <separator/>
58
+ <item action="ZoomIn" id="ZoomIn">Zoom In</item>
59
+ <item action="ZoomOut" id="ZoomOut">Zoom Out</item>
60
+ <separator/>
61
+ <item action="Quality" id="Quality">Quality</item>
62
+ <item action="Pause" id="Pause">Pause</item>
63
+ <item action="Mute" id="Mute">Mute</item>
64
+ <separator/>
65
+ <item action="Find" id="Find">Find...</item>
66
+ <item action="FindAgain" id="FindAgain">Find Again</item>
67
+ <item action="Copy" id="Copy">Copy</item>
68
+ </menu>
69
+
70
+ `
71
+ // Build the grammar. 'document' is the starting production
72
+ const grammar = buildGrammar(XmlGrammar, 'document')
73
+
74
+ // Parse the XML string
75
+ const parseTree = parse(xmlString, grammar)
76
+
77
+ log(JSON.stringify(parseTree, undefined, 4))
78
+ }
79
+
80
+
81
+ async function testParserError1() {
82
+ const xmlData = `<hello> wo rld <!!! `
83
+
84
+ const grammar = buildGrammar(XmlGrammar, 'document')
85
+
86
+ const result = parse(xmlData, grammar)
87
+
88
+ console.log(JSON.stringify(result, undefined, 4))
89
+ }
90
+
91
+ async function testParserError2() {
92
+ const jsonData = `{ "asdf": 12.5 `
93
+
94
+ const grammar = buildGrammar(JsonGrammar, 'expression')
95
+
96
+ const result = parse(jsonData, grammar)
97
+
98
+ console.log(JSON.stringify(result, undefined, 4))
99
+ }
100
+
101
+ testXmlParser()