expressir 2.1.30 → 2.2.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 (165) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +99 -0
  3. data/.github/workflows/links.yml +100 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +11 -0
  6. data/.github/workflows/validate_schemas.yml +1 -1
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +1 -1
  9. data/.rubocop_todo.yml +267 -53
  10. data/Gemfile +2 -1
  11. data/README.adoc +993 -55
  12. data/docs/Gemfile +12 -0
  13. data/docs/_config.yml +141 -0
  14. data/docs/_guides/changes/changes-format.adoc +778 -0
  15. data/docs/_guides/changes/importing-eengine.adoc +898 -0
  16. data/docs/_guides/changes/index.adoc +396 -0
  17. data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
  18. data/docs/_guides/changes/validating-changes.adoc +681 -0
  19. data/docs/_guides/cli/benchmark-performance.adoc +834 -0
  20. data/docs/_guides/cli/coverage-analysis.adoc +921 -0
  21. data/docs/_guides/cli/format-schemas.adoc +547 -0
  22. data/docs/_guides/cli/index.adoc +8 -0
  23. data/docs/_guides/cli/managing-changes.adoc +927 -0
  24. data/docs/_guides/cli/validate-ascii.adoc +645 -0
  25. data/docs/_guides/cli/validate-schemas.adoc +534 -0
  26. data/docs/_guides/formatter/formatter-architecture.adoc +401 -0
  27. data/docs/_guides/index.adoc +165 -0
  28. data/docs/_guides/ler/creating-packages.adoc +664 -0
  29. data/docs/_guides/ler/index.adoc +305 -0
  30. data/docs/_guides/ler/loading-packages.adoc +707 -0
  31. data/docs/_guides/ler/package-formats.adoc +748 -0
  32. data/docs/_guides/ler/querying-packages.adoc +826 -0
  33. data/docs/_guides/ler/validating-packages.adoc +750 -0
  34. data/docs/_guides/liquid/basic-templates.adoc +813 -0
  35. data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
  36. data/docs/_guides/liquid/drops-reference.adoc +829 -0
  37. data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
  38. data/docs/_guides/liquid/index.adoc +468 -0
  39. data/docs/_guides/manifests/creating-manifests.adoc +483 -0
  40. data/docs/_guides/manifests/index.adoc +307 -0
  41. data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
  42. data/docs/_guides/manifests/validating-manifests.adoc +713 -0
  43. data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
  44. data/docs/_guides/ruby-api/index.adoc +257 -0
  45. data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
  46. data/docs/_guides/ruby-api/search-engine.adoc +609 -0
  47. data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
  48. data/docs/_pages/data-model.adoc +665 -0
  49. data/docs/_pages/express-language.adoc +506 -0
  50. data/docs/_pages/getting-started.adoc +414 -0
  51. data/docs/_pages/index.adoc +116 -0
  52. data/docs/_pages/introduction.adoc +256 -0
  53. data/docs/_pages/ler-packages.adoc +837 -0
  54. data/docs/_pages/parsers.adoc +709 -0
  55. data/docs/_pages/schema-manifests.adoc +431 -0
  56. data/docs/_references/index.adoc +228 -0
  57. data/docs/_tutorials/creating-ler-package.adoc +735 -0
  58. data/docs/_tutorials/documentation-coverage.adoc +795 -0
  59. data/docs/_tutorials/index.adoc +221 -0
  60. data/docs/_tutorials/liquid-templates.adoc +806 -0
  61. data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
  62. data/docs/_tutorials/querying-schemas.adoc +751 -0
  63. data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
  64. data/docs/index.adoc +242 -0
  65. data/docs/lychee.toml +87 -0
  66. data/examples/demo_ler_usage.sh +86 -0
  67. data/examples/ler/README.md +111 -0
  68. data/examples/ler/simple_example.ler +0 -0
  69. data/examples/ler/simple_schema.exp +33 -0
  70. data/examples/ler_build.rb +75 -0
  71. data/examples/ler_cli.rb +79 -0
  72. data/examples/ler_demo_complete.rb +276 -0
  73. data/examples/ler_query.rb +91 -0
  74. data/examples/ler_query_examples.rb +305 -0
  75. data/examples/ler_stats.rb +81 -0
  76. data/examples/phase3_demo.rb +159 -0
  77. data/examples/query_demo_simple.rb +131 -0
  78. data/expressir.gemspec +4 -2
  79. data/lib/expressir/benchmark.rb +6 -6
  80. data/lib/expressir/cli.rb +21 -4
  81. data/lib/expressir/commands/format.rb +28 -0
  82. data/lib/expressir/commands/manifest.rb +427 -0
  83. data/lib/expressir/commands/package.rb +1274 -0
  84. data/lib/expressir/commands/validate.rb +70 -37
  85. data/lib/expressir/commands/validate_ascii.rb +607 -0
  86. data/lib/expressir/commands/validate_load.rb +88 -0
  87. data/lib/expressir/coverage.rb +15 -11
  88. data/lib/expressir/express/builder.rb +350 -0
  89. data/lib/expressir/express/builders/attribute_decl_builder.rb +38 -0
  90. data/lib/expressir/express/builders/built_in_builder.rb +88 -0
  91. data/lib/expressir/express/builders/constant_builder.rb +115 -0
  92. data/lib/expressir/express/builders/declaration_builder.rb +24 -0
  93. data/lib/expressir/express/builders/derive_clause_builder.rb +16 -0
  94. data/lib/expressir/express/builders/derived_attr_builder.rb +28 -0
  95. data/lib/expressir/express/builders/domain_rule_builder.rb +21 -0
  96. data/lib/expressir/express/builders/entity_decl_builder.rb +108 -0
  97. data/lib/expressir/express/builders/explicit_attr_builder.rb +52 -0
  98. data/lib/expressir/express/builders/expression_builder.rb +453 -0
  99. data/lib/expressir/express/builders/function_decl_builder.rb +84 -0
  100. data/lib/expressir/express/builders/helpers.rb +148 -0
  101. data/lib/expressir/express/builders/interface_builder.rb +171 -0
  102. data/lib/expressir/express/builders/inverse_attr_builder.rb +45 -0
  103. data/lib/expressir/express/builders/inverse_attr_type_builder.rb +36 -0
  104. data/lib/expressir/express/builders/inverse_clause_builder.rb +16 -0
  105. data/lib/expressir/express/builders/literal_builder.rb +107 -0
  106. data/lib/expressir/express/builders/procedure_decl_builder.rb +80 -0
  107. data/lib/expressir/express/builders/qualifier_builder.rb +128 -0
  108. data/lib/expressir/express/builders/reference_builder.rb +27 -0
  109. data/lib/expressir/express/builders/rule_decl_builder.rb +95 -0
  110. data/lib/expressir/express/builders/schema_body_decl_builder.rb +22 -0
  111. data/lib/expressir/express/builders/schema_decl_builder.rb +62 -0
  112. data/lib/expressir/express/builders/schema_version_builder.rb +40 -0
  113. data/lib/expressir/express/builders/simple_id_builder.rb +26 -0
  114. data/lib/expressir/express/builders/statement_builder.rb +250 -0
  115. data/lib/expressir/express/builders/subtype_constraint_builder.rb +188 -0
  116. data/lib/expressir/express/builders/syntax_builder.rb +19 -0
  117. data/lib/expressir/express/builders/token_builder.rb +15 -0
  118. data/lib/expressir/express/builders/type_builder.rb +264 -0
  119. data/lib/expressir/express/builders/type_decl_builder.rb +32 -0
  120. data/lib/expressir/express/builders/unique_clause_builder.rb +22 -0
  121. data/lib/expressir/express/builders/unique_rule_builder.rb +36 -0
  122. data/lib/expressir/express/builders/where_clause_builder.rb +22 -0
  123. data/lib/expressir/express/builders.rb +43 -0
  124. data/lib/expressir/express/error.rb +18 -2
  125. data/lib/expressir/express/formatter.rb +23 -1509
  126. data/lib/expressir/express/formatters/data_types_formatter.rb +317 -0
  127. data/lib/expressir/express/formatters/declarations_formatter.rb +689 -0
  128. data/lib/expressir/express/formatters/expressions_formatter.rb +160 -0
  129. data/lib/expressir/express/formatters/literals_formatter.rb +46 -0
  130. data/lib/expressir/express/formatters/references_formatter.rb +42 -0
  131. data/lib/expressir/express/formatters/remark_formatter.rb +296 -0
  132. data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
  133. data/lib/expressir/express/formatters/statements_formatter.rb +224 -0
  134. data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +48 -0
  135. data/lib/expressir/express/parser.rb +155 -7
  136. data/lib/expressir/express/pretty_formatter.rb +624 -0
  137. data/lib/expressir/express/remark_attacher.rb +1155 -0
  138. data/lib/expressir/express/resolve_references_model_visitor.rb +1 -0
  139. data/lib/expressir/express/streaming_builder.rb +467 -0
  140. data/lib/expressir/express/transformer/remark_handling.rb +196 -0
  141. data/lib/expressir/manifest/resolver.rb +213 -0
  142. data/lib/expressir/manifest/validator.rb +195 -0
  143. data/lib/expressir/model/declarations/entity.rb +6 -0
  144. data/lib/expressir/model/dependency_resolver.rb +270 -0
  145. data/lib/expressir/model/identifier.rb +1 -1
  146. data/lib/expressir/model/indexes/entity_index.rb +103 -0
  147. data/lib/expressir/model/indexes/reference_index.rb +148 -0
  148. data/lib/expressir/model/indexes/type_index.rb +149 -0
  149. data/lib/expressir/model/interface_validator.rb +384 -0
  150. data/lib/expressir/model/model_element.rb +30 -2
  151. data/lib/expressir/model/remark_info.rb +51 -0
  152. data/lib/expressir/model/repository.rb +400 -5
  153. data/lib/expressir/model/repository_validator.rb +295 -0
  154. data/lib/expressir/model/search_engine.rb +574 -0
  155. data/lib/expressir/model.rb +4 -94
  156. data/lib/expressir/package/builder.rb +200 -0
  157. data/lib/expressir/package/metadata.rb +81 -0
  158. data/lib/expressir/package/reader.rb +165 -0
  159. data/lib/expressir/schema_manifest.rb +11 -1
  160. data/lib/expressir/version.rb +1 -1
  161. data/lib/expressir.rb +20 -3
  162. metadata +168 -9
  163. data/docs/benchmarking.adoc +0 -107
  164. data/docs/liquid_drops.adoc +0 -1547
  165. data/lib/expressir/express/visitor.rb +0 -2815
@@ -0,0 +1,709 @@
1
+ ---
2
+ title: Parsers
3
+ nav_order: 6
4
+ ---
5
+
6
+ == Expressir Parsers
7
+
8
+ === Purpose
9
+
10
+ This page explains how Expressir parses EXPRESS schemas from various formats into its Ruby data model. Understanding the parsing architecture is essential for troubleshooting parsing issues, optimizing performance, and working with different EXPRESS formats.
11
+
12
+ === References
13
+
14
+ * link:express-language.html[EXPRESS Language] - Understanding the source language
15
+ * link:data-model.html[Data Model] - Understanding the target model
16
+ * link:../guides/cli/format-schemas.html[Guide: Format Schemas] - Using the parser via CLI
17
+ * link:../guides/ruby-api/parsing-files.html[Guide: Parsing Files] - Using the parser API
18
+
19
+ === Concepts
20
+
21
+ Parser:: Component that reads text in EXPRESS syntax and produces an Abstract Syntax Tree (AST)
22
+ AST:: Abstract Syntax Tree - intermediate tree representation of parsed code
23
+ Transform:: Conversion of AST into Expressir's Ruby data model
24
+ Reference Resolution:: Process of linking references (by name) to their target definitions
25
+ Visitor:: Design pattern for traversing and transforming the AST
26
+ Cache:: Stored parsed schemas for faster subsequent loads
27
+
28
+ === Parser Architecture
29
+
30
+ Expressir uses a multi-stage parsing pipeline:
31
+
32
+ [source]
33
+ ----
34
+ ┌─────────────────┐
35
+ │ EXPRESS Text │
36
+ │ (.exp file) │
37
+ └────────┬────────┘
38
+
39
+
40
+ ┌──────────┐
41
+ │ Parsanol │ (PEG parser)
42
+ │ Grammar │
43
+ └────┬─────┘
44
+
45
+
46
+ ┌────────────────┐
47
+ │ AST (Tree) │ (intermediate)
48
+ └────────┬───────┘
49
+
50
+
51
+ ┌──────────┐
52
+ │ Visitor │ (transform)
53
+ └────┬─────┘
54
+
55
+
56
+ ┌────────────────┐
57
+ │ Data Model │ (Ruby objects)
58
+ │ Repository │
59
+ └────────┬───────┘
60
+
61
+
62
+ ┌────────────────────┐
63
+ │ Reference │
64
+ │ Resolution │
65
+ └────────┬───────────┘
66
+
67
+
68
+ ┌────────────────────┐
69
+ │ Finalized Model │
70
+ │ (ready to use) │
71
+ └────────────────────┘
72
+ ----
73
+
74
+ ==== Backend: Parsanol
75
+
76
+ Expressir uses the **Parsanol** parser backend:
77
+
78
+ **Parsanol (High-Performance Rust)**::
79
+ * High-performance Rust backend
80
+ * 18-44x faster parsing
81
+ * 99.5% fewer allocations
82
+ * Supports source position tracking (Slice)
83
+ * Available automatically when Parsanol gem is installed
84
+
85
+ Expressir automatically uses Parsanol when available:
86
+
87
+ [source,ruby]
88
+ ----
89
+ # Automatically uses native Rust parser (Parsanol)
90
+ repo = Expressir::Express::Parser.from_file("geometry.exp")
91
+
92
+ # Check if native parser is available
93
+ if defined?(Parsanol::Native) && Parsanol::Native.available?
94
+ # Using Rust parser - 20-30x faster
95
+ end
96
+ ----
97
+
98
+ See the link:https://github.com/parsanol/parsanol-ruby[Parsanol documentation] for performance details.
99
+
100
+ ==== Stage 1: Lexical Analysis and Parsing
101
+
102
+ Expressir uses **Parsanol**, a high-performance Parsing Expression Grammar (PEG) parser:
103
+
104
+ [source,ruby]
105
+ ----
106
+ # Grammar rules defined in Parser class
107
+ rule(:entityDecl) do
108
+ (entityHead >> entityBody >> tEND_ENTITY >> op_delim).as(:entityDecl)
109
+ end
110
+
111
+ rule(:entityHead) do
112
+ (tENTITY >> entityId >> subsuper >> op_delim).as(:entityHead)
113
+ end
114
+ ----
115
+
116
+ **Parsanol advantages**:
117
+
118
+ * **Pure Ruby**: No external dependencies
119
+ * **Composable rules**: Complex grammars from simple parts
120
+ * **Error reporting**: Clear parse failure messages
121
+ * **Type-safe**: Strongly typed AST nodes
122
+
123
+ ==== Stage 2: AST Generation
124
+
125
+ Parsing produces a hierarchical tree structure:
126
+
127
+ [source,ruby]
128
+ ----
129
+ # Example AST for: ENTITY person; name : STRING; END_ENTITY;
130
+ {
131
+ entityDecl: {
132
+ entityHead: {
133
+ entityId: { str: "person" },
134
+ ...
135
+ },
136
+ entityBody: {
137
+ explicitAttr: [
138
+ {
139
+ attributeDecl: { str: "name" },
140
+ parameterType: { str: "STRING" }
141
+ }
142
+ ]
143
+ }
144
+ }
145
+ }
146
+ ----
147
+
148
+ ==== Stage 3: AST Transformation
149
+
150
+ The Visitor pattern transforms AST to data model:
151
+
152
+ [source,ruby]
153
+ ----
154
+ class Visitor
155
+ def visit_entityDecl(node)
156
+ entity = Model::Declarations::Entity.new
157
+ entity.id = node[:entityHead][:entityId]
158
+ entity.attributes = visit_attributes(node[:entityBody])
159
+ entity
160
+ end
161
+ end
162
+ ----
163
+
164
+ **Transformation responsibilities**:
165
+
166
+ * Create appropriate model objects
167
+ * Set attributes and relationships
168
+ * Attach parent links
169
+ * Preserve source text (if requested)
170
+ * Extract documentation (remarks)
171
+
172
+ ==== Stage 4: Reference Resolution
173
+
174
+ Final stage links references to definitions:
175
+
176
+ [source,ruby]
177
+ ----
178
+ # Before resolution
179
+ attribute.type # => SimpleReference(id: "length_measure")
180
+
181
+ # After resolution
182
+ attribute.type.ref # => Type(id: "length_measure")
183
+ ----
184
+
185
+ === Supported Formats
186
+
187
+ Expressir supports multiple EXPRESS formats:
188
+
189
+ ==== EXPRESS Language (ISO 10303-11)
190
+
191
+ Standard textual EXPRESS format:
192
+
193
+ [source,express]
194
+ ----
195
+ SCHEMA geometry_schema;
196
+ ENTITY point;
197
+ x : REAL;
198
+ y : REAL;
199
+ z : REAL;
200
+ END_ENTITY;
201
+ END_SCHEMA;
202
+ ----
203
+
204
+ **File extension**: `.exp`
205
+
206
+ **Characteristics**:
207
+
208
+ * Text-based, human-readable
209
+ * Supports full EXPRESS language
210
+ * Most common format
211
+
212
+ **Usage**:
213
+
214
+ [source,ruby]
215
+ ----
216
+ repo = Expressir::Express::Parser.from_file("geometry.exp")
217
+ ----
218
+
219
+ ==== STEPmod EXPRESS XML
220
+
221
+ XML representation used in STEPmod repository:
222
+
223
+ [source,xml]
224
+ ----
225
+ <express>
226
+ <schema name="geometry_schema">
227
+ <entity name="point">
228
+ <explicit name="x" type="REAL"/>
229
+ <explicit name="y" type="REAL"/>
230
+ <explicit name="z" type="REAL"/>
231
+ </entity>
232
+ </schema>
233
+ </express>
234
+ ----
235
+
236
+ **File extension**: `.xml`
237
+
238
+ **Characteristics**:
239
+
240
+ * XML format
241
+ * Modular schema organization
242
+ * Used in ISO STEP modular repository
243
+
244
+ **Note**: Future support planned
245
+
246
+ ==== EXPRESS XML (ISO 10303-28)
247
+
248
+ Standardized XML representation:
249
+
250
+ **File extension**: `.xml`
251
+
252
+ **Characteristics**:
253
+
254
+ * Follows ISO 10303-28 specification
255
+ * Designed for data exchange
256
+ * Precise mapping to EXPRESS constructs
257
+
258
+ **Note**: Future support planned
259
+
260
+ === Parsing Process
261
+
262
+ ==== Single File Parsing
263
+
264
+ Parse one EXPRESS file:
265
+
266
+ [source,ruby]
267
+ ----
268
+ # Basic parsing
269
+ repository = Expressir::Express::Parser.from_file("schema.exp")
270
+
271
+ # With options
272
+ repository = Expressir::Express::Parser.from_file(
273
+ "schema.exp",
274
+ skip_references: false, # Resolve references (default: false)
275
+ include_source: true, # Attach source text (default: nil)
276
+ root_path: "/base/path" # Base for relative paths (default: nil)
277
+ )
278
+ ----
279
+
280
+ **Process**:
281
+
282
+ 1. Read file content
283
+ 2. Parse to AST
284
+ 3. Transform to model
285
+ 4. Resolve references (unless skipped)
286
+ 5. Return Repository
287
+
288
+ ==== Multiple File Parsing
289
+
290
+ Parse several files into one repository:
291
+
292
+ [source,ruby]
293
+ ----
294
+ files = ["schema1.exp", "schema2.exp", "schema3.exp"]
295
+
296
+ # With progress tracking
297
+ repository = Expressir::Express::Parser.from_files(files) do |filename, schemas, error|
298
+ if error
299
+ puts "Error parsing #{filename}: #{error.message}"
300
+ else
301
+ puts "Loaded #{schemas.length} schemas from #{filename}"
302
+ end
303
+ end
304
+ ----
305
+
306
+ **Process**:
307
+
308
+ 1. Parse each file individually
309
+ 2. Collect all schemas
310
+ 3. Create unified Repository
311
+ 4. Resolve cross-file references
312
+ 5. Return complete Repository
313
+
314
+ ==== String Parsing
315
+
316
+ Parse EXPRESS from string:
317
+
318
+ [source,ruby]
319
+ ----
320
+ express_code = <<~EXPRESS
321
+ SCHEMA example;
322
+ ENTITY person;
323
+ name : STRING;
324
+ END_ENTITY;
325
+ END_SCHEMA;
326
+ EXPRESS
327
+
328
+ repository = Expressir::Express::Parser.from_exp(express_code)
329
+ ----
330
+
331
+ **Use cases**:
332
+
333
+ * Testing
334
+ * Dynamic schema generation
335
+ * Template processing
336
+ * Schema fragments
337
+
338
+ === Reference Resolution
339
+
340
+ ==== What is Reference Resolution?
341
+
342
+ EXPRESS uses names to reference other elements:
343
+
344
+ [source,express]
345
+ ----
346
+ TYPE length_measure = REAL;
347
+ END_TYPE;
348
+
349
+ ENTITY line;
350
+ length : length_measure; -- Reference to type above
351
+ END_ENTITY;
352
+ ----
353
+
354
+ After parsing, `length_measure` is just a string. Reference resolution finds the actual `Type` object.
355
+
356
+ ==== Resolution Process
357
+
358
+ [source,ruby]
359
+ ----
360
+ # Automatic resolution (default)
361
+ repo = Expressir::Express::Parser.from_file("schema.exp")
362
+ # References already resolved
363
+
364
+ # Manual resolution
365
+ repo = Expressir::Express::Parser.from_file("schema.exp", skip_references: true)
366
+ # References not yet resolved
367
+ repo.resolve_all_references
368
+ # Now resolved
369
+ ----
370
+
371
+ ==== Interface Resolution
372
+
373
+ USE FROM and REFERENCE FROM create cross-schema references:
374
+
375
+ [source,express]
376
+ ----
377
+ SCHEMA application_schema;
378
+ USE FROM geometry_schema; -- Import all
379
+ REFERENCE FROM support_schema (date); -- Import specific
380
+
381
+ ENTITY geometric_model;
382
+ base : point; -- From geometry_schema
383
+ created : date; -- From support_schema
384
+ END_ENTITY;
385
+ END_SCHEMA;
386
+ ----
387
+
388
+ Resolution finds `point` in `geometry_schema` and `date` in `support_schema`.
389
+
390
+ ==== Resolution Scope
391
+
392
+ Resolution searches in order:
393
+
394
+ 1. **Current entity/function** (local scope)
395
+ 2. **Current schema** (schema-level declarations)
396
+ 3. **Interfaced schemas** (USE FROM / REFERENCE FROM)
397
+ 4. **Parent scopes** (for nested contexts)
398
+
399
+ ==== Unresolved References
400
+
401
+ If a reference cannot be resolved:
402
+
403
+ [source,ruby]
404
+ ----
405
+ attribute.type.ref # => nil (not found)
406
+ ----
407
+
408
+ This typically indicates:
409
+
410
+ * Typo in reference name
411
+ * Missing interface declaration
412
+ * Missing schema in repository
413
+ * Incorrect schema order
414
+
415
+ === Error Handling
416
+
417
+ ==== Parse Failures
418
+
419
+ When parsing fails, Expressir raises detailed errors:
420
+
421
+ [source,ruby]
422
+ ----
423
+ begin
424
+ repo = Expressir::Express::Parser.from_file("invalid.exp")
425
+ rescue Expressir::Express::Error::SchemaParseFailure => e
426
+ puts "Failed to parse: #{e.filename}"
427
+ puts e.message
428
+ puts e.parse_failure_cause.ascii_tree # Detailed error location
429
+ end
430
+ ----
431
+
432
+ **Error information includes**:
433
+
434
+ * File name
435
+ * Line and column numbers
436
+ * Expected tokens
437
+ * Actual tokens found
438
+ * Parse tree context
439
+
440
+ ==== Common Parse Errors
441
+
442
+ **Missing semicolon**:
443
+
444
+ [source]
445
+ ----
446
+ Expected ';' at line 10, column 5
447
+ ----
448
+
449
+ **Invalid keyword**:
450
+
451
+ [source]
452
+ ----
453
+ Unexpected keyword 'FOO' at line 15, column 3
454
+ ----
455
+
456
+ **Mismatched END statement**:
457
+
458
+ [source]
459
+ ----
460
+ Expected 'END_ENTITY' but found 'END_TYPE' at line 20
461
+ ----
462
+
463
+ **Invalid identifier**:
464
+
465
+ [source]
466
+ ----
467
+ Expected identifier at line 8, column 12
468
+ ----
469
+
470
+ ==== Recovery Strategies
471
+
472
+ **Skip broken file**:
473
+
474
+ [source,ruby]
475
+ ----
476
+ files.each do |file|
477
+ begin
478
+ repo = Expressir::Express::Parser.from_file(file)
479
+ process(repo)
480
+ rescue Expressir::Express::Error::SchemaParseFailure => e
481
+ warn "Skipping #{file}: #{e.message}"
482
+ next
483
+ end
484
+ end
485
+ ----
486
+
487
+ **Continue parsing remaining files**:
488
+
489
+ [source,ruby]
490
+ ----
491
+ Expressir::Express::Parser.from_files(files) do |filename, schemas, error|
492
+ if error
493
+ warn "Failed: #{filename}"
494
+ else
495
+ # Process successful schemas
496
+ end
497
+ end
498
+ ----
499
+
500
+ === Performance Considerations
501
+
502
+ ==== Benchmarking
503
+
504
+ Measure parsing performance:
505
+
506
+ [source,ruby]
507
+ ----
508
+ require 'benchmark'
509
+
510
+ time = Benchmark.realtime do
511
+ repo = Expressir::Express::Parser.from_file("large_schema.exp")
512
+ end
513
+
514
+ puts "Parsed in #{time.round(2)} seconds"
515
+ ----
516
+
517
+ ==== Optimization Techniques
518
+
519
+ **Skip reference resolution for analysis**:
520
+
521
+ [source,ruby]
522
+ ----
523
+ # Faster if you don't need resolved references
524
+ repo = Expressir::Express::Parser.from_file("schema.exp", skip_references: true)
525
+ ----
526
+
527
+ **Omit source text**:
528
+
529
+ [source,ruby]
530
+ ----
531
+ # Reduces memory usage
532
+ repo = Expressir::Express::Parser.from_file("schema.exp", include_source: false)
533
+ ----
534
+
535
+ **Parse in parallel** (for multiple files):
536
+
537
+ [source,ruby]
538
+ ----
539
+ require 'parallel'
540
+
541
+ repos = Parallel.map(files) do |file|
542
+ Expressir::Express::Parser.from_file(file, skip_references: true)
543
+ end
544
+
545
+ # Combine and resolve references once
546
+ combined = combine_repositories(repos)
547
+ combined.resolve_all_references
548
+ ----
549
+
550
+ ==== Caching
551
+
552
+ Use caching for repeated parses:
553
+
554
+ [source,ruby]
555
+ ----
556
+ # Expressir has built-in cache support
557
+ Expressir::Express::Cache.enable
558
+
559
+ # First parse: slow
560
+ repo1 = Expressir::Express::Parser.from_file("schema.exp")
561
+
562
+ # Second parse: fast (from cache)
563
+ repo2 = Expressir::Express::Parser.from_file("schema.exp")
564
+ ----
565
+
566
+ See link:../guides/cli/benchmark-performance.html[Benchmark Performance] guide for details.
567
+
568
+ === Advanced Topics
569
+
570
+ ==== Custom Visitors
571
+
572
+ Extend parsing with custom transformations:
573
+
574
+ [source,ruby]
575
+ ----
576
+ class MyVisitor < Expressir::Express::Visitor
577
+ def visit_entity(node)
578
+ entity = super
579
+ # Custom processing
580
+ entity.custom_flag = true
581
+ entity
582
+ end
583
+ end
584
+ ----
585
+
586
+ ==== Incremental Parsing
587
+
588
+ Parse schemas on demand:
589
+
590
+ [source,ruby]
591
+ ----
592
+ # Parse schema headers only
593
+ repos = files.map do |file|
594
+ Expressir::Express::Parser.from_file(file, skip_references: true)
595
+ end
596
+
597
+ # Parse individual schemas fully as needed
598
+ selected_repo = repos.find { |r| r.schemas.first.id == "target_schema" }
599
+ selected_repo.resolve_all_references
600
+ ----
601
+
602
+ ==== Grammar Extension
603
+
604
+ Expressir's grammar can be extended for custom syntax:
605
+
606
+ [source,ruby]
607
+ ----
608
+ class CustomParser < Expressir::Express::Parser::Parser
609
+ rule(:custom_construct) do
610
+ # Custom grammar rules
611
+ end
612
+ end
613
+ ----
614
+
615
+ === Parsing Best Practices
616
+
617
+ **Always handle errors**::
618
+ Use begin/rescue blocks to handle parse failures gracefully
619
+
620
+ **Validate before parsing**::
621
+ Check file existence and readability first
622
+
623
+ **Use progress callbacks**::
624
+ For multiple files, track progress with callbacks
625
+
626
+ **Skip references when possible**::
627
+ If you don't need resolved references, skip for speed
628
+
629
+ **Cache for production**::
630
+ Enable caching for applications that parse repeatedly
631
+
632
+ **Profile large schemas**::
633
+ Use benchmarking to identify bottlenecks
634
+
635
+ **Process incrementally**::
636
+ For very large sets, parse and process one at a time
637
+
638
+ === Troubleshooting
639
+
640
+ ==== Parser Hangs
641
+
642
+ **Symptom**: Parser doesn't complete
643
+
644
+ **Causes**:
645
+
646
+ * Malformed file with infinite recursion
647
+ * Very large schema
648
+ * Memory exhaustion
649
+
650
+ **Solutions**:
651
+
652
+ * Validate file structure first
653
+ * Parse smaller chunks
654
+ * Increase memory limits
655
+
656
+ ==== Reference Resolution Fails
657
+
658
+ **Symptom**: Many unresolved references
659
+
660
+ **Causes**:
661
+
662
+ * Missing interface declarations
663
+ * Incorrect schema order
664
+ * Typos in names
665
+
666
+ **Solutions**:
667
+
668
+ * Check USE FROM / REFERENCE FROM
669
+ * Parse schemas in dependency order
670
+ * Validate names match
671
+
672
+ ==== Memory Issues
673
+
674
+ **Symptom**: Out of memory errors
675
+
676
+ **Causes**:
677
+
678
+ * Very large schemas
679
+ * Including source text
680
+ * Parsing many files at once
681
+
682
+ **Solutions**:
683
+
684
+ * Parse incrementally
685
+ * Skip source text inclusion
686
+ * Use streaming approaches
687
+
688
+ === Next Steps
689
+
690
+ Now that you understand parsing:
691
+
692
+ **Try parsing**::
693
+ link:../tutorials/parsing-your-first-schema.html[Parse your first schema]
694
+
695
+ **Learn the CLI**::
696
+ link:../guides/cli/format-schemas.html[Format schemas with CLI]
697
+
698
+ **Master the API**::
699
+ link:../guides/ruby-api/parsing-files.html[Parse files programmatically]
700
+
701
+ **Optimize performance**::
702
+ link:../guides/cli/benchmark-performance.html[Benchmark and optimize]
703
+
704
+ === Bibliography
705
+
706
+ * https://github.com/parsanon/parsanon-ruby[Parsanol] - High-performance PEG parser for Ruby
707
+ * https://en.wikipedia.org/wiki/Parsing_expression_grammar[PEG on Wikipedia] - Understanding PEG parsers
708
+ * link:express-language.html[EXPRESS Language] - Understanding what is being parsed
709
+ * link:data-model.html[Data Model] - Understanding the parsing result