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