expressir 2.4.0 → 2.4.1

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
  3. data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
  4. data/TODO.bugs/03-builder-mutable-state.md +43 -0
  5. data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
  6. data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
  7. data/TODO.bugs/06-collection-registry-single-source.md +53 -0
  8. data/TODO.bugs/07-require-relative-cleanup.md +42 -0
  9. data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
  10. data/TODO.bugs/09-parser-split.md +53 -0
  11. data/TODO.bugs/10-to-s-override.md +42 -0
  12. data/TODO.bugs/11-parser-class-variables.md +39 -0
  13. data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
  14. data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
  15. data/TODO.bugs/14-model-formatting-leak.md +30 -0
  16. data/TODO.bugs/15-expression-children-macro.md +27 -0
  17. data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
  18. data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
  19. data/TODO.bugs/18-const-get-private-constants.md +30 -0
  20. data/TODO.bugs/19-format-methods-public.md +22 -0
  21. data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
  22. data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
  23. data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
  24. data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
  25. data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
  26. data/TODO.bugs/25-debug-puts-in-production.md +21 -0
  27. data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
  28. data/TODO.bugs/27-package-build-god-method.md +19 -0
  29. data/TODO.bugs/28-package-god-class.md +30 -0
  30. data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
  31. data/TODO.bugs/30-unicode-map-extraction.md +19 -0
  32. data/TODO.bugs/README.md +43 -0
  33. data/docs/_tutorials/formatting-schemas.adoc +89 -0
  34. data/docs/_tutorials/index.adoc +10 -0
  35. data/docs/lychee.toml +3 -0
  36. data/expressir.gemspec +4 -2
  37. data/lib/expressir/commands/changes_import_eengine.rb +0 -6
  38. data/lib/expressir/commands/changes_validate.rb +0 -2
  39. data/lib/expressir/commands/clean.rb +1 -1
  40. data/lib/expressir/commands/file_violations.rb +70 -0
  41. data/lib/expressir/commands/format.rb +1 -1
  42. data/lib/expressir/commands/non_ascii_character.rb +49 -0
  43. data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
  44. data/lib/expressir/commands/package.rb +138 -185
  45. data/lib/expressir/commands/validate_ascii.rb +0 -510
  46. data/lib/expressir/commands/version.rb +1 -1
  47. data/lib/expressir/commands.rb +3 -0
  48. data/lib/expressir/coverage.rb +49 -117
  49. data/lib/expressir/express/builder.rb +74 -86
  50. data/lib/expressir/express/builder_context.rb +22 -0
  51. data/lib/expressir/express/builders/expression_builder.rb +16 -16
  52. data/lib/expressir/express/cache.rb +35 -8
  53. data/lib/expressir/express/error.rb +7 -0
  54. data/lib/expressir/express/formatter.rb +33 -18
  55. data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
  56. data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
  57. data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
  58. data/lib/expressir/express/grammar/parser.rb +705 -0
  59. data/lib/expressir/express/grammar.rb +11 -0
  60. data/lib/expressir/express/node_position_index.rb +215 -0
  61. data/lib/expressir/express/parser.rb +114 -884
  62. data/lib/expressir/express/pretty_formatter.rb +23 -5
  63. data/lib/expressir/express/remark_attacher.rb +475 -577
  64. data/lib/expressir/express/remark_scanner.rb +84 -19
  65. data/lib/expressir/express/schema_block_scanner.rb +136 -0
  66. data/lib/expressir/express/schema_source_formatter.rb +15 -0
  67. data/lib/expressir/express/scope_resolver.rb +194 -0
  68. data/lib/expressir/express/source_formatter.rb +15 -0
  69. data/lib/expressir/express/streaming_builder.rb +147 -176
  70. data/lib/expressir/express.rb +8 -6
  71. data/lib/expressir/model/concerns.rb +3 -0
  72. data/lib/expressir/model/data_types/generic_entity.rb +6 -6
  73. data/lib/expressir/model/declarations/entity.rb +5 -0
  74. data/lib/expressir/model/declarations/function.rb +5 -0
  75. data/lib/expressir/model/declarations/procedure.rb +5 -0
  76. data/lib/expressir/model/declarations/rule.rb +6 -0
  77. data/lib/expressir/model/declarations/schema.rb +21 -8
  78. data/lib/expressir/model/declarations/type.rb +3 -0
  79. data/lib/expressir/model/exp_file.rb +2 -0
  80. data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
  81. data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
  82. data/lib/expressir/model/expressions/binary_expression.rb +1 -0
  83. data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
  84. data/lib/expressir/model/expressions/function_call.rb +1 -0
  85. data/lib/expressir/model/expressions/interval.rb +1 -0
  86. data/lib/expressir/model/expressions/query_expression.rb +2 -0
  87. data/lib/expressir/model/expressions/unary_expression.rb +1 -0
  88. data/lib/expressir/model/model_element.rb +128 -14
  89. data/lib/expressir/model/remark_format.rb +17 -0
  90. data/lib/expressir/model/remark_info.rb +31 -3
  91. data/lib/expressir/model/remark_placement.rb +29 -0
  92. data/lib/expressir/model/statements/alias.rb +2 -0
  93. data/lib/expressir/model/statements/assignment.rb +4 -0
  94. data/lib/expressir/model/statements/case.rb +21 -0
  95. data/lib/expressir/model/statements/case_action.rb +1 -0
  96. data/lib/expressir/model/statements/compound.rb +4 -0
  97. data/lib/expressir/model/statements/escape.rb +3 -0
  98. data/lib/expressir/model/statements/if.rb +5 -0
  99. data/lib/expressir/model/statements/null.rb +3 -0
  100. data/lib/expressir/model/statements/procedure_call.rb +3 -0
  101. data/lib/expressir/model/statements/repeat.rb +3 -0
  102. data/lib/expressir/model/statements/return.rb +3 -0
  103. data/lib/expressir/model/statements/skip.rb +3 -0
  104. data/lib/expressir/model.rb +12 -3
  105. data/lib/expressir/package/builder.rb +2 -2
  106. data/lib/expressir/version.rb +6 -1
  107. data/lib/expressir.rb +38 -3
  108. metadata +63 -5
  109. data/lib/expressir/express/transformer/remark_handling.rb +0 -194
@@ -1,755 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "parsanol"
2
4
 
3
5
  module Expressir
4
6
  module Express
7
+ # Public-facing parser facade.
8
+ #
9
+ # Three concerns were split out (TODO.bugs/09):
10
+ # - Grammar rules → {Grammar::Parser} (~250 Parsanol rules)
11
+ # - Schema-block splitting → {SchemaBlockScanner}
12
+ # - I/O orchestration → this class (from_file, from_exp, etc.)
13
+ #
14
+ # The facade delegates grammar work to Grammar::Parser and block
15
+ # extraction to SchemaBlockScanner. All public methods remain
16
+ # backward-compatible.
5
17
  class Parser
6
- class Parser < Parsanol::Parser
7
- root(:syntax)
8
-
9
- # Cache for native parser grammar (class-level)
10
- @@cached_grammar_json = nil
11
- @@cached_grammar_atom_id = nil
12
-
13
- # Cache for parser instance (avoids ~7ms overhead of Parser.new)
14
- @@cached_parser = nil
15
- @@parser_mutex = Mutex.new
16
-
17
- # Cache for schemaDecl grammar JSON (used for streaming parse)
18
- @@cached_schema_grammar_json = nil
19
-
20
- # Threshold for using memory-bounded fresh parse (bytes)
21
- # Files above this use parse_fresh which has no packrat cache
22
- LARGE_FILE_THRESHOLD = 1024 * 1024 # 1 MB
23
-
24
- # Get cached parser instance (thread-safe)
25
- # Reusing the parser avoids the overhead of reinitializing all rule definitions
26
- def self.cached_parser
27
- return @@cached_parser if @@cached_parser
28
-
29
- @@parser_mutex.synchronize do
30
- @@cached_parser ||= new
31
- end
32
- end
33
-
34
- # Clear the cached parser (useful for testing or after grammar changes)
35
- def self.clear_parser_cache
36
- @@parser_mutex.synchronize do
37
- @@cached_parser = nil
38
- end
39
- end
40
-
41
- # Check if native parsing is available
42
- def self.native_available?
43
- return @native_available unless @native_available.nil?
44
-
45
- @native_available = begin
46
- require "parsanol/native"
47
- Parsanol::Native.available?
48
- rescue LoadError
49
- false
50
- end
51
- end
52
-
53
- # Get cached grammar JSON for native parsing
54
- def self.cached_grammar_json
55
- return @@cached_grammar_json if @@cached_grammar_json
56
-
57
- parser = new
58
- atom = parser.syntax
59
-
60
- # Cache by atom object_id
61
- if atom.object_id != @@cached_grammar_atom_id
62
- @@cached_grammar_atom_id = atom.object_id
63
- @@cached_grammar_json = Parsanol::Native.serialize_grammar(atom)
64
- end
65
-
66
- @@cached_grammar_json
67
- end
68
-
69
- # Get cached grammar JSON for schemaDecl (used for streaming parse)
70
- def self.cached_schema_grammar_json
71
- return @@cached_schema_grammar_json if @@cached_schema_grammar_json
72
-
73
- schema_atom = cached_parser.schemaDecl
74
- @@cached_schema_grammar_json = Parsanol::Native.serialize_grammar(schema_atom)
75
- @@cached_schema_grammar_json
76
- end
77
-
78
- # Parse using native engine with Rust-side transformation (fastest)
79
- #
80
- # This method provides ~17x speedup over pure Ruby parsing.
81
- # The transformation happens in Rust using to_parslet_compatible,
82
- # producing Parslet-compatible output that Builder.build can consume directly.
83
- #
84
- # @param source [String] EXPRESS source code to parse
85
- # @return [Hash, Array] Transformed AST in Parslet-compatible format
86
- # @raise [LoadError] If native parser is not available
87
- def self.parse_native(source)
88
- unless native_available?
89
- raise LoadError, "Native parser not available"
90
- end
91
-
92
- grammar_atom = cached_parser.syntax
93
- # Use fresh-parse (no cache) for large files to bound memory
94
- if source.bytesize > LARGE_FILE_THRESHOLD
95
- Parsanol::Native.parse_fresh(grammar_atom, source)
96
- else
97
- Parsanol::Native.parse(grammar_atom, source)
98
- end
99
- end
100
-
101
- def cts(atom)
102
- spaces >> atom
103
- end
104
-
105
- def cstr(atom)
106
- cts(str(atom).as(:str))
107
- end
108
-
109
- KEYWORDS = %i[
110
- ABS ABSTRACT ACOS AGGREGATE ALIAS AND ANDOR ARRAY AS ASIN ATAN BAG BASED_ON
111
- BEGIN BINARY BLENGTH BOOLEAN BY CASE CONSTANT CONST_E COS DERIVE DIV ELSE
112
- END END_ALIAS END_CASE END_CONSTANT END_ENTITY END_FUNCTION END_IF
113
- END_LOCAL END_PROCEDURE END_REPEAT END_RULE END_SCHEMA
114
- END_SUBTYPE_CONSTRAINT END_TYPE ENTITY ENUMERATION ESCAPE EXISTS EXP
115
- EXTENSIBLE FALSE FIXED FOR FORMAT FROM FUNCTION GENERIC GENERIC_ENTITY
116
- HIBOUND HIINDEX IF IN INSERT INTEGER INVERSE LENGTH LIKE LIST LOBOUND LOCAL
117
- LOG LOG10 LOG2 LOGICAL LOINDEX MOD NOT NUMBER NVL ODD OF ONEOF OPTIONAL OR
118
- OTHERWISE PI PROCEDURE QUERY REAL REFERENCE REMOVE RENAMED REPEAT RETURN
119
- ROLESOF RULE SCHEMA SELECT SELF SET SIN SIZEOF SKIP SQRT STRING SUBTYPE
120
- SUBTYPE_CONSTRAINT SUPERTYPE TAN THEN TO TRUE TYPE TYPEOF TOTAL_OVER UNIQUE
121
- UNKNOWN UNTIL USE USEDIN VALUE VALUE_IN VALUE_UNIQUE VAR WITH WHERE WHILE
122
- XOR
123
- ].freeze
18
+ # Threshold for using memory-bounded fresh parse (bytes)
19
+ LARGE_FILE_THRESHOLD = 1024 * 1024 # 1 MB
20
+
21
+ class << self
22
+ # Delegate grammar-level queries to Grammar::Parser.
23
+ def native_available? = Grammar::Parser.native_available?
24
+ def cached_parser = Grammar::Parser.cached_parser
25
+ def cached_grammar_json = Grammar::Parser.cached_grammar_json
26
+ def cached_schema_grammar_json = Grammar::Parser.cached_schema_grammar_json
27
+ def parse_native(source) = Grammar::Parser.parse_native(source)
28
+ def clear_parser_cache = Grammar::Parser.clear_parser_cache
29
+ end
124
30
 
125
- def keyword_rule(str)
126
- key_chars = str.to_s.chars
127
- key_chars
128
- .collect! { |char| match("[#{char}#{char.downcase}]") }
129
- .reduce(:>>) >> match["a-zA-Z0-9_"].absent?
130
- end
31
+ # UTF-8 BOM byte sequence (EF BB BF). Some editors prepend this
32
+ # to EXPRESS files; the parser fails because SCHEMA isn't at byte 0.
33
+ def self.strip_bom(source)
34
+ bytes = source.b
35
+ return source unless bytes.bytesize >= 3 &&
36
+ bytes.getbyte(0) == 0xEF &&
37
+ bytes.getbyte(1) == 0xBB &&
38
+ bytes.getbyte(2) == 0xBF
131
39
 
132
- KEYWORDS.each do |keyword|
133
- sym = :"t#{keyword}"
134
- rule(sym) { cts(keyword_rule(keyword).as(:str)).as(sym) }
135
- end
136
-
137
- rule(:abstractEntityDeclaration) do
138
- tABSTRACT.as(:abstractEntityDeclaration)
139
- end
140
- rule(:abstractSupertypeDeclaration) do
141
- (tABSTRACT >> tSUPERTYPE >> subtypeConstraint.maybe).as(:abstractSupertypeDeclaration)
142
- end
143
- rule(:abstractSupertype) do
144
- (tABSTRACT >> tSUPERTYPE >> op_delim).as(:abstractSupertype)
145
- end
146
- rule(:actualParameterList) do
147
- (op_leftparen >> (parameter >> (op_comma >> parameter).repeat).as(:listOf_parameter).maybe >> op_rightparen)
148
- .as(:actualParameterList)
149
- end
150
- rule(:addLikeOp) { (op_plus | op_minus | tOR | tXOR).as(:addLikeOp) }
151
- rule(:aggregateInitializer) do
152
- (op_leftbracket >> (element >> (op_comma >> element).repeat).as(:listOf_element).maybe >> op_rightbracket)
153
- .as(:aggregateInitializer)
154
- end
155
- rule(:aggregateSource) { simpleExpression.as(:aggregateSource) }
156
- rule(:aggregateType) do
157
- (tAGGREGATE >> (op_colon >> typeLabel).maybe >> tOF >> parameterType).as(:aggregateType)
158
- end
159
- rule(:aggregationTypes) do
160
- (arrayType | bagType | listType | setType).as(:aggregationTypes)
161
- end
162
- rule(:algorithmHead) do
163
- (declaration.repeat.as(:declaration) >> constantDecl.maybe >> localDecl.maybe).as(:algorithmHead)
164
- end
165
- rule(:aliasStmt) do
166
- (tALIAS >> variableId >> tFOR >> generalRef >> qualifier.repeat.as(:qualifier) >>
167
- op_delim >> stmt.repeat(1).as(:stmt) >> tEND_ALIAS >> op_delim.as(:op_delim2)).as(:aliasStmt)
168
- end
169
- rule(:anyKeyword) { KEYWORDS.map { |kw| send("t#{kw}") }.inject(:|) }
170
- rule(:arrayType) do
171
- (tARRAY >> boundSpec >> tOF >> tOPTIONAL.maybe >> tUNIQUE.maybe >> instantiableType).as(:arrayType)
172
- end
173
- rule(:assignmentStmt) do
174
- (generalRef >> qualifier.repeat.as(:qualifier) >> op_decl >> expression >> op_delim).as(:assignmentStmt)
175
- end
176
- rule(:attributeDecl) do
177
- (attributeId | redeclaredAttribute).as(:attributeDecl)
178
- end
179
- rule(:attributeId) { simpleId.as(:attributeId) }
180
- rule(:attributeQualifier) do
181
- (op_period >> attributeRef).as(:attributeQualifier)
182
- end
183
- rule(:attributeRef) { attributeId.as(:attributeRef) }
184
- rule(:bagType) do
185
- (tBAG >> boundSpec.maybe >> tOF >> instantiableType).as(:bagType)
186
- end
187
- rule(:binaryLiteral) do
188
- cts((str("%") >> bit.repeat(1)).as(:str)).as(:binaryLiteral)
189
- end
190
- rule(:binaryType) { (tBINARY >> widthSpec.maybe).as(:binaryType) }
191
- rule(:bit) { match["0-1"] }
192
- rule(:booleanType) { tBOOLEAN.as(:booleanType) }
193
- rule(:bound1) { numericExpression.as(:bound1) }
194
- rule(:bound2) { numericExpression.as(:bound2) }
195
- rule(:boundSpec) do
196
- (op_leftbracket >> bound1 >> op_colon >> bound2 >> op_rightbracket).as(:boundSpec)
197
- end
198
- rule(:builtInConstant) do
199
- (tCONST_E | tPI | tSELF | op_question_mark).as(:builtInConstant)
200
- end
201
- rule(:builtInFunction) do
202
- (tABS | tACOS | tASIN | tATAN | tBLENGTH | tCOS | tEXISTS | tEXP | tFORMAT | tHIBOUND | tHIINDEX | tLENGTH |
203
- tLOBOUND | tLOINDEX | tLOG2 | tLOG10 | tLOG | tNVL | tODD | tROLESOF | tSIN | tSIZEOF | tSQRT | tTAN |
204
- tTYPEOF | tUSEDIN | tVALUE_IN | tVALUE_UNIQUE | tVALUE).as(:builtInFunction)
205
- end
206
- rule(:builtInProcedure) { (tINSERT | tREMOVE).as(:builtInProcedure) }
207
- rule(:caseAction) do
208
- ((caseLabel >> (op_comma >> caseLabel).repeat).as(:listOf_caseLabel) >> op_colon >> stmt).as(:caseAction)
209
- end
210
- rule(:caseLabel) { expression.as(:caseLabel) }
211
- rule(:caseStmt) do
212
- (tCASE >> selector >> tOF >> caseAction.repeat.as(:caseAction) >> (tOTHERWISE >> op_colon >> stmt).maybe >> tEND_CASE >> op_delim).as(:caseStmt)
213
- end
214
- rule(:compoundStmt) do
215
- (tBEGIN >> stmt.repeat(1).as(:stmt) >> tEND >> op_delim).as(:compoundStmt)
216
- end
217
- rule(:concreteTypes) do
218
- (aggregationTypes | simpleTypes | typeRef).as(:concreteTypes)
219
- end
220
- rule(:constantBody) do
221
- (constantId >> op_colon >> instantiableType >> op_decl >> expression >> op_delim).as(:constantBody)
222
- end
223
- rule(:constantDecl) do
224
- (tCONSTANT >> constantBody.repeat(1).as(:constantBody) >> tEND_CONSTANT >> op_delim).as(:constantDecl)
225
- end
226
- rule(:constantFactor) do
227
- (builtInConstant | constantRef).as(:constantFactor)
228
- end
229
- rule(:constantId) { simpleId.as(:constantId) }
230
- rule(:constantRef) { constantId.as(:constantRef) }
231
- rule(:constructedTypes) do
232
- (enumerationType | selectType).as(:constructedTypes)
233
- end
234
- rule(:declaration) do
235
- (entityDecl | functionDecl | procedureDecl | subtypeConstraintDecl | typeDecl).as(:declaration)
236
- end
237
- rule(:delim) { cts(str(";").as(:delim)) }
238
- rule(:deriveClause) do
239
- (tDERIVE >> derivedAttr.repeat(1).as(:derivedAttr)).as(:deriveClause)
240
- end
241
- rule(:derivedAttr) do
242
- (attributeDecl >> op_colon >> parameterType >> op_decl >> expression >> op_delim).as(:derivedAttr)
243
- end
244
- rule(:digit) { match["0-9"] }
245
- rule(:digits) { digit >> digit.repeat }
246
- rule(:domainRule) do
247
- ((ruleLabelId >> op_colon).maybe >> expression).as(:domainRule)
248
- end
249
- rule(:element) do
250
- (expression >> (op_colon >> repetition).maybe).as(:element)
251
- end
252
- rule(:embeddedRemark) do
253
- (str("(*") >> (str("*)").absent? >> (embeddedRemark | any)).repeat >> str("*)")).as(:embeddedRemark)
254
- end
255
- rule(:encodedCharacter) { octet >> octet >> octet >> octet }
256
- rule(:encodedStringLiteral) do
257
- cts((str('"') >> encodedCharacter.repeat(1) >> str('"')).as(:str)).as(:encodedStringLiteral)
258
- end
259
- rule(:entityBody) do
260
- (explicitAttr.repeat.as(:explicitAttr) >> deriveClause.maybe >> inverseClause.maybe >> uniqueClause.maybe >> whereClause.maybe).as(:entityBody)
261
- end
262
- rule(:entityConstructor) do
263
- (entityRef >> op_leftparen >> (expression >> (op_comma >> expression).repeat).as(:listOf_expression).maybe >> op_rightparen).as(:entityConstructor)
264
- end
265
- rule(:entityDecl) do
266
- (entityHead >> entityBody >> tEND_ENTITY >> op_delim).as(:entityDecl)
267
- end
268
- rule(:entityHead) do
269
- (tENTITY >> entityId >> subsuper >> op_delim).as(:entityHead)
270
- end
271
- rule(:entityId) { simpleId.as(:entityId) }
272
- rule(:entityRef) { entityId.as(:entityRef) }
273
- rule(:enumerationExtension) do
274
- (tBASED_ON >> typeRef >> (tWITH >> enumerationItems).maybe).as(:enumerationExtension)
275
- end
276
- rule(:enumerationId) { simpleId.as(:enumerationId) }
277
- rule(:enumerationItem) { enumerationId.as(:enumerationItem) }
278
- rule(:enumerationItems) do
279
- (op_leftparen >> (enumerationItem >> (op_comma >> enumerationItem).repeat).as(:listOf_enumerationItem) >> op_rightparen).as(:enumerationItems)
280
- end
281
- rule(:enumerationRef) { enumerationId.as(:enumerationRef) }
282
- rule(:enumerationReference) do
283
- ((typeRef >> op_period).maybe >> enumerationRef).as(:enumerationReference)
284
- end
285
- rule(:enumerationType) do
286
- (tEXTENSIBLE.maybe >> tENUMERATION >> ((tOF >> enumerationItems) | enumerationExtension).maybe).as(:enumerationType)
287
- end
288
- rule(:escapeStmt) { (tESCAPE >> op_delim).as(:escapeStmt) }
289
- rule(:explicitAttr) do
290
- ((attributeDecl >> (op_comma >> attributeDecl).repeat).as(:listOf_attributeDecl) >> op_colon >>
291
- tOPTIONAL.maybe >> parameterType >> op_delim).as(:explicitAttr)
292
- end
293
- rule(:expression) do
294
- (simpleExpression >> (relOpExtended >> simpleExpression.as(:rhs)).maybe).as(:expression)
295
- end
296
- rule(:factor) do
297
- (simpleFactor >> (op_double_asterisk >> simpleFactor.as(:rhs)).maybe).as(:factor)
298
- end
299
- rule(:formalParameter) do
300
- ((parameterId >> (op_comma >> parameterId).repeat).as(:listOf_parameterId) >> op_colon >> parameterType).as(:formalParameter)
301
- end
302
- rule(:functionCall) do
303
- ((builtInFunction | functionRef) >> actualParameterList.maybe).as(:functionCall)
304
- end
305
- rule(:functionDecl) do
306
- (functionHead >> algorithmHead >> stmt.repeat(1).as(:stmt) >> tEND_FUNCTION >> op_delim).as(:functionDecl)
307
- end
308
- rule(:functionHead) do
309
- (tFUNCTION >> functionId >>
310
- (op_leftparen >> (formalParameter >> (op_delim >> formalParameter).repeat).as(:listOf_formalParameter) >> op_rightparen).maybe >>
311
- op_colon >> parameterType >> op_delim.as(:op_delim2)).as(:functionHead)
312
- end
313
- rule(:functionId) { simpleId.as(:functionId) }
314
- rule(:functionRef) { functionId.as(:functionRef) }
315
- rule(:generalAggregationTypes) do
316
- (generalArrayType | generalBagType | generalListType | generalSetType).as(:generalAggregationTypes)
317
- end
318
- rule(:generalArrayType) do
319
- (tARRAY >> boundSpec.maybe >> tOF >> tOPTIONAL.maybe >> tUNIQUE.maybe >> parameterType).as(:generalArrayType)
320
- end
321
- rule(:generalBagType) do
322
- (tBAG >> boundSpec.maybe >> tOF >> parameterType).as(:generalBagType)
323
- end
324
- rule(:generalizedTypes) do
325
- (aggregateType | generalAggregationTypes | genericEntityType | genericType).as(:generalizedTypes)
326
- end
327
- rule(:generalListType) do
328
- (tLIST >> boundSpec.maybe >> tOF >> tUNIQUE.maybe >> parameterType).as(:generalListType)
329
- end
330
- rule(:generalRef) { (parameterRef | variableId).as(:generalRef) }
331
- rule(:generalSetType) do
332
- (tSET >> boundSpec.maybe >> tOF >> parameterType).as(:generalSetType)
333
- end
334
- rule(:genericEntityType) do
335
- (tGENERIC_ENTITY >> (op_colon >> typeLabel).maybe).as(:genericEntityType)
336
- end
337
- rule(:genericType) do
338
- (tGENERIC >> (op_colon >> typeLabel).maybe).as(:genericType)
339
- end
340
- rule(:groupQualifier) do
341
- (op_double_backslash >> entityRef).as(:groupQualifier)
342
- end
343
- rule(:hexDigit) { match["0-9a-fA-F"] }
344
- rule(:ifStmtElseStatements) do
345
- stmt.repeat(1).as(:stmt).as(:ifStmtElseStatements)
346
- end
347
- rule(:ifStmtStatements) do
348
- stmt.repeat(1).as(:stmt).as(:ifStmtStatements)
349
- end
350
- rule(:ifStmt) do
351
- (tIF >> logicalExpression >> tTHEN >> ifStmtStatements >> (tELSE >> ifStmtElseStatements).maybe >> tEND_IF >> op_delim).as(:ifStmt)
352
- end
353
- rule(:incrementControl) do
354
- (variableId >> op_decl >> bound1 >> tTO >> bound2 >> (tBY >> increment).maybe).as(:incrementControl)
355
- end
356
- rule(:increment) { numericExpression.as(:increment) }
357
- rule(:index1) { index.as(:index1) }
358
- rule(:index2) { index.as(:index2) }
359
- rule(:index) { numericExpression.as(:index) }
360
- rule(:indexQualifier) do
361
- (op_leftbracket >> index1 >> (op_colon >> index2).maybe >> op_rightbracket).as(:indexQualifier)
362
- end
363
- rule(:instantiableType) do
364
- (concreteTypes | entityRef).as(:instantiableType)
365
- end
366
- rule(:integerLiteral) { cts(digits.as(:str)).as(:integerLiteral) }
367
- rule(:integerType) { tINTEGER.as(:integerType) }
368
- rule(:interfaceSpecification) do
369
- (referenceClause | useClause).as(:interfaceSpecification)
370
- end
371
- rule(:intervalHigh) { simpleExpression.as(:intervalHigh) }
372
- rule(:intervalItem) { simpleExpression.as(:intervalItem) }
373
- rule(:intervalLow) { simpleExpression.as(:intervalLow) }
374
- rule(:interval) do
375
- (op_left_curly_brace >> intervalLow >> intervalOp >> intervalItem >> intervalOp.as(:intervalOp2) >>
376
- intervalHigh >> op_right_curly_brace).as(:interval)
377
- end
378
- rule(:intervalOp) { (op_less_equal | op_less_than).as(:intervalOp) }
379
- rule(:inverseAttr) do
380
- (attributeDecl >> op_colon >> inverseAttrType >> tFOR >> (entityRef >> op_period).maybe >> attributeRef >> op_delim).as(:inverseAttr)
381
- end
382
- rule(:inverseAttrType) do
383
- (((tSET | tBAG) >> boundSpec.maybe >> tOF).maybe >> entityRef).as(:inverseAttrType)
384
- end
385
- rule(:inverseClause) do
386
- (tINVERSE >> inverseAttr.repeat(1).as(:inverseAttr)).as(:inverseClause)
387
- end
388
- rule(:letter) { match["a-zA-Z"] }
389
- rule(:listType) do
390
- (tLIST >> boundSpec.maybe >> tOF >> tUNIQUE.maybe >> instantiableType).as(:listType)
391
- end
392
- rule(:literal) do
393
- (binaryLiteral | logicalLiteral | realLiteral | integerLiteral | stringLiteral).as(:literal)
394
- end
395
- rule(:localDecl) do
396
- (tLOCAL >> localVariable.repeat(1).as(:localVariable) >> tEND_LOCAL >> op_delim).as(:localDecl)
397
- end
398
- rule(:localVariable) do
399
- ((variableId >> (op_comma >> variableId).repeat).as(:listOf_variableId) >>
400
- op_colon >> parameterType >>
401
- (op_decl >> expression).maybe >> op_delim).as(:localVariable)
402
- end
403
- rule(:logicalExpression) { expression.as(:logicalExpression) }
404
- rule(:logicalLiteral) do
405
- (tFALSE | tTRUE | tUNKNOWN).as(:logicalLiteral)
406
- end
407
- rule(:logicalType) { tLOGICAL.as(:logicalType) }
408
- rule(:multiplicationLikeOp) do
409
- (op_asterisk | op_slash | tDIV | tMOD | tAND | op_double_pipe).as(:multiplicationLikeOp)
410
- end
411
- rule(:namedTypeOrRename) do
412
- (namedTypes >> (tAS >> (entityId | typeId)).maybe).as(:namedTypeOrRename)
413
- end
414
- rule(:namedTypes) { (entityRef | typeRef).as(:namedTypes) }
415
- rule(:nullStmt) { op_delim.as(:nullStmt) }
416
- rule(:numberType) { tNUMBER.as(:numberType) }
417
- rule(:numericExpression) { simpleExpression.as(:numericExpression) }
418
- rule(:octet) { hexDigit >> hexDigit }
419
- rule(:oneOf) do
420
- (tONEOF >> op_leftparen >> (supertypeExpression >>
421
- (op_comma >> supertypeExpression).repeat).as(:listOf_supertypeExpression) >> op_rightparen).as(:oneOf)
422
- end
423
- rule(:op_asterisk) { cstr("*").as(:op_asterisk) }
424
- rule(:op_colon) { cstr(":").as(:op_colon) }
425
- rule(:op_colon_equals_colon) { cstr(":=:").as(:op_colon_equals_colon) }
426
- rule(:op_colon_less_greater_colon) do
427
- cstr(":<>:").as(:op_colon_less_greater_colon)
428
- end
429
- rule(:op_comma) { cstr(",").as(:op_comma) }
430
- rule(:op_decl) { cstr(":=").as(:op_decl) }
431
- rule(:op_delim) { cstr(";").as(:op_delim) }
432
- rule(:op_double_asterisk) { cstr("**").as(:op_double_asterisk) }
433
- rule(:op_double_backslash) { cstr("\\").as(:op_double_backslash) }
434
- rule(:op_double_pipe) { cstr("||").as(:op_double_pipe) }
435
- rule(:op_equals) { cstr("=").as(:op_equals) }
436
- rule(:op_greater_equal) { cstr(">=").as(:op_greater_equal) }
437
- rule(:op_greater_than) { cstr(">").as(:op_greater_than) }
438
- rule(:op_leftbracket) { cstr("[").as(:op_leftbracket) }
439
- rule(:op_left_curly_brace) { cstr("{").as(:op_left_curly_brace) }
440
- rule(:op_leftparen) { cstr("(").as(:op_leftparen) }
441
- rule(:op_less_equal) { cstr("<=").as(:op_less_equal) }
442
- rule(:op_less_greater) { cstr("<>").as(:op_less_greater) }
443
- rule(:op_less_than) { cstr("<").as(:op_less_than) }
444
- rule(:op_minus) { cstr("-").as(:op_minus) }
445
- rule(:op_period) { cstr(".").as(:op_period) }
446
- rule(:op_pipe) { cstr("|").as(:op_pipe) }
447
- rule(:op_plus) { cstr("+").as(:op_plus) }
448
- rule(:op_query_begin) { cstr("<*").as(:op_query_begin) }
449
- rule(:op_query_end) { cstr("*>").as(:op_query_end) }
450
- rule(:op_question_mark) { cstr("?").as(:op_question_mark) }
451
- rule(:op_rightbracket) { cstr("]").as(:op_rightbracket) }
452
- rule(:op_right_curly_brace) { cstr("}").as(:op_right_curly_brace) }
453
- rule(:op_rightparen) { cstr(")").as(:op_rightparen) }
454
- rule(:op_slash) { cstr("/").as(:op_slash) }
455
- rule(:parameter) { expression.as(:parameter) }
456
- rule(:parameterId) { simpleId.as(:parameterId) }
457
- rule(:parameterRef) { parameterId.as(:parameterRef) }
458
- rule(:parameterType) do
459
- (generalizedTypes | namedTypes | simpleTypes).as(:parameterType)
460
- end
461
- rule(:population) { entityRef.as(:population) }
462
- rule(:precisionSpec) { numericExpression.as(:precisionSpec) }
463
- rule(:primary) do
464
- (literal | (qualifiableFactor >> qualifier.repeat.as(:qualifier))).as(:primary)
465
- end
466
- rule(:procedureCallStmt) do
467
- ((builtInProcedure | procedureRef) >> actualParameterList.maybe >> op_delim).as(:procedureCallStmt)
468
- end
469
- rule(:procedureDecl) do
470
- (procedureHead >> algorithmHead >> stmt.repeat.as(:stmt) >> tEND_PROCEDURE >> op_delim).as(:procedureDecl)
471
- end
472
- rule(:procedureHeadParameter) do
473
- (tVAR.maybe >> formalParameter).as(:procedureHeadParameter)
474
- end
475
- rule(:procedureHead) do
476
- (tPROCEDURE >> procedureId >>
477
- (op_leftparen >> (procedureHeadParameter >>
478
- (op_delim >> procedureHeadParameter).repeat).as(:listOf_procedureHeadParameter) >> op_rightparen).maybe >> op_delim.as(:op_delim2)).as(:procedureHead)
479
- end
480
- rule(:procedureId) { simpleId.as(:procedureId) }
481
- rule(:procedureRef) { procedureId.as(:procedureRef) }
482
- rule(:qualifiableFactor) do
483
- (functionCall | attributeRef | constantFactor | generalRef | population).as(:qualifiableFactor)
484
- end
485
- rule(:qualifiedAttribute) do
486
- (tSELF >> groupQualifier >> attributeQualifier).as(:qualifiedAttribute)
487
- end
488
- rule(:qualifier) do
489
- (attributeQualifier | groupQualifier | indexQualifier).as(:qualifier)
490
- end
491
- rule(:queryExpression) do
492
- (tQUERY >> op_leftparen >> variableId >> op_query_begin >> aggregateSource >> op_pipe >> logicalExpression >> op_rightparen).as(:queryExpression)
493
- end
494
- rule(:realLiteral) do
495
- cts((digits >> str(".") >> digits.maybe >> (match["eE"] >> sign.maybe >> digits).maybe).as(:str)).as(:realLiteral)
496
- end
497
- rule(:realType) do
498
- (tREAL >> (op_leftparen >> precisionSpec >> op_rightparen).maybe).as(:realType)
499
- end
500
- rule(:redeclaredAttribute) do
501
- (qualifiedAttribute >> (tRENAMED >> attributeId).maybe).as(:redeclaredAttribute)
502
- end
503
- rule(:referenceClause) do
504
- (tREFERENCE >> tFROM >> schemaRef >>
505
- (op_leftparen >> (resourceOrRename >> (op_comma >> resourceOrRename).repeat).as(:listOf_resourceOrRename) >> op_rightparen).maybe >> op_delim).as(:referenceClause)
506
- end
507
- rule(:referencedAttribute) do
508
- (attributeRef | qualifiedAttribute).as(:referencedAttribute)
509
- end
510
- rule(:relOpExtended) { (relOp | tIN | tLIKE).as(:relOpExtended) }
511
- rule(:relOp) do
512
- (op_less_equal | op_greater_equal | op_less_greater | op_less_than | op_greater_than | op_equals |
513
- op_colon_less_greater_colon | op_colon_equals_colon).as(:relOp)
514
- end
515
- rule(:renameId) do
516
- (constantId | entityId | functionId | procedureId | typeId).as(:renameId)
517
- end
518
- rule(:repeatControl) do
519
- (incrementControl.maybe >> whileControl.maybe >> untilControl.maybe).as(:repeatControl)
520
- end
521
- rule(:repeatStmt) do
522
- (tREPEAT >> repeatControl >> op_delim >> stmt.repeat(1).as(:stmt) >> tEND_REPEAT >> op_delim.as(:op_delim2)).as(:repeatStmt)
523
- end
524
- rule(:repetition) { numericExpression.as(:repetition) }
525
- rule(:resourceOrRename) do
526
- (resourceRef >> (tAS >> renameId).maybe).as(:resourceOrRename)
527
- end
528
- rule(:resourceRef) do
529
- (constantRef | entityRef | functionRef | procedureRef | typeRef).as(:resourceRef)
530
- end
531
- rule(:returnStmt) do
532
- (tRETURN >> (op_leftparen >> expression >> op_rightparen).maybe >> op_delim).as(:returnStmt)
533
- end
534
- rule(:ruleDecl) do
535
- (ruleHead >> algorithmHead >> stmt.repeat.as(:stmt) >> whereClause >> tEND_RULE >> op_delim).as(:ruleDecl)
536
- end
537
- rule(:ruleHead) do
538
- (tRULE >> ruleId >> tFOR >> op_leftparen >>
539
- (entityRef >> (op_comma >> entityRef).repeat).as(:listOf_entityRef) >> op_rightparen >> op_delim).as(:ruleHead)
540
- end
541
- rule(:ruleId) { simpleId.as(:ruleId) }
542
- rule(:ruleLabelId) { simpleId.as(:ruleLabelId) }
543
- rule(:ruleLabelRef) { ruleLabelId.as(:ruleLabelRef) }
544
- rule(:ruleRef) { ruleId.as(:ruleRef) }
545
- rule(:schemaBodyDeclaration) do
546
- (declaration | ruleDecl).as(:schemaBodyDeclaration)
547
- end
548
- rule(:schemaBody) do
549
- (interfaceSpecification.repeat.as(:interfaceSpecification) >> constantDecl.maybe >>
550
- schemaBodyDeclaration.repeat.as(:schemaBodyDeclaration)).as(:schemaBody)
551
- end
552
- rule(:schemaDecl) do
553
- (tSCHEMA >> schemaId >> schemaVersionId.maybe >> op_delim >> schemaBody >> tEND_SCHEMA >> op_delim.as(:op_delim2)).as(:schemaDecl)
554
- end
555
- rule(:schemaId) { simpleId.as(:schemaId) }
556
- rule(:schemaRef) { schemaId.as(:schemaRef) }
557
- rule(:schemaVersionId) { stringLiteral.as(:schemaVersionId) }
558
- rule(:selectExtension) do
559
- (tBASED_ON >> typeRef >> (tWITH >> selectList).maybe).as(:selectExtension)
560
- end
561
- rule(:selectList) do
562
- (op_leftparen >> (namedTypes >> (op_comma >> namedTypes).repeat).as(:listOf_namedTypes) >> op_rightparen).as(:selectList)
563
- end
564
- rule(:selector) { expression.as(:selector) }
565
- rule(:selectType) do
566
- ((tEXTENSIBLE >> tGENERIC_ENTITY.maybe).maybe >> tSELECT >> (selectList | selectExtension).maybe).as(:selectType)
567
- end
568
- rule(:setType) do
569
- (tSET >> boundSpec.maybe >> tOF >> instantiableType).as(:setType)
570
- end
571
- rule(:sign) { match["+-"] }
572
- rule(:simpleExpression) do
573
- (term >> (addLikeOp.as(:operator) >> term).as(:item).repeat.as(:rhs)).as(:simpleExpression)
574
- end
575
- rule(:simpleFactor) do
576
- (simpleFactorExpression | aggregateInitializer | entityConstructor | interval | queryExpression | stringLiteral |
577
- simpleFactorUnaryExpression | enumerationReference).as(:simpleFactor)
578
- end
579
- rule(:simpleFactorExpression) do
580
- ((op_leftparen >> expression >> op_rightparen) | primary).as(:simpleFactorExpression)
581
- end
582
- rule(:simpleFactorUnaryExpression) do
583
- (unaryOp >> simpleFactorExpression).as(:simpleFactorUnaryExpression)
584
- end
585
- rule(:simpleId) do
586
- anyKeyword.absent? >> cts((match["a-zA-Z_"] >> match["a-zA-Z0-9_"].repeat).as(:str)).as(:simpleId)
587
- end
588
- rule(:simpleStringLiteral) do
589
- cts((str("'") >> (str("'").absent? >> any).repeat >> str("'")).as(:str)).as(:simpleStringLiteral)
590
- end
591
- rule(:simpleTypes) do
592
- (binaryType | booleanType | integerType | logicalType | numberType | realType | stringType).as(:simpleTypes)
593
- end
594
- rule(:skipStmt) { (tSKIP >> op_delim).as(:skipStmt) }
595
- rule(:space) { match[" \r\n\t\f"] | embeddedRemark | tailRemark }
596
- rule(:spaces) { space.repeat.as(:spaces) }
597
- rule(:stmt) do
598
- (aliasStmt | assignmentStmt | caseStmt | compoundStmt | escapeStmt | ifStmt |
599
- nullStmt | procedureCallStmt | repeatStmt | returnStmt | skipStmt).as(:stmt)
600
- end
601
- rule(:stringLiteral) do
602
- (simpleStringLiteral | encodedStringLiteral).as(:stringLiteral)
603
- end
604
- rule(:stringType) { (tSTRING >> widthSpec.maybe).as(:stringType) }
605
- rule(:subsuper) do
606
- (supertypeConstraint.maybe >> subtypeDeclaration.maybe).as(:subsuper)
607
- end
608
- rule(:subtypeConstraintBody) do
609
- (abstractSupertype.maybe >> totalOver.maybe >> (supertypeExpression >> op_delim).maybe).as(:subtypeConstraintBody)
610
- end
611
- rule(:subtypeConstraintDecl) do
612
- (subtypeConstraintHead >> subtypeConstraintBody >> tEND_SUBTYPE_CONSTRAINT >> op_delim).as(:subtypeConstraintDecl)
613
- end
614
- rule(:subtypeConstraintHead) do
615
- (tSUBTYPE_CONSTRAINT >> subtypeConstraintId >> tFOR >> entityRef >> op_delim).as(:subtypeConstraintHead)
616
- end
617
- rule(:subtypeConstraintId) { simpleId.as(:subtypeConstraintId) }
618
- rule(:subtypeConstraintRef) do
619
- subtypeConstraintId.as(:subtypeConstraintRef)
620
- end
621
- rule(:subtypeConstraint) do
622
- (tOF >> op_leftparen >> supertypeExpression >> op_rightparen).as(:subtypeConstraint)
623
- end
624
- rule(:subtypeDeclaration) do
625
- (tSUBTYPE >> tOF >> op_leftparen >> entityRef >> (op_comma >> entityRef).repeat >> op_rightparen).as(:listOf_entityRef).as(:subtypeDeclaration)
626
- end
627
- rule(:supertypeConstraint) do
628
- (abstractSupertypeDeclaration | abstractEntityDeclaration | supertypeRule).as(:supertypeConstraint)
629
- end
630
- rule(:supertypeExpression) do
631
- (supertypeFactor >> (tANDOR.as(:operator) >> supertypeFactor).as(:item).repeat.as(:rhs)).as(:supertypeExpression)
632
- end
633
- rule(:supertypeFactor) do
634
- (supertypeTerm >> (tAND.as(:operator) >> supertypeTerm).as(:item).repeat.as(:rhs)).as(:supertypeFactor)
635
- end
636
- rule(:supertypeRule) do
637
- (tSUPERTYPE >> subtypeConstraint).as(:supertypeRule)
638
- end
639
- rule(:supertypeTerm) do
640
- (entityRef | oneOf | (op_leftparen >> supertypeExpression >> op_rightparen)).as(:supertypeTerm)
641
- end
642
- rule(:syntax) do
643
- (spaces.as(:spaces) >> schemaDecl.repeat(1).as(:schemaDecl) >> spaces.as(:trailer)).as(:syntax)
644
- end
645
- rule(:tailRemark) do
646
- (str("--") >> (str("\n").absent? >> any).repeat >> str("\n").maybe).as(:tailRemark)
647
- end
648
- rule(:term) do
649
- (factor >> (multiplicationLikeOp >> factor).as(:item).repeat.as(:rhs)).as(:term)
650
- end
651
- rule(:totalOver) do
652
- (tTOTAL_OVER >> op_leftparen >> (entityRef >> (op_comma >> entityRef).repeat).as(:listOf_entityRef) >> op_rightparen >> op_delim).as(:totalOver)
653
- end
654
- rule(:typeDecl) do
655
- (tTYPE >> typeId >> op_equals >> underlyingType >> op_delim >> whereClause.maybe >> tEND_TYPE >> op_delim.as(:op_delim2)).as(:typeDecl)
656
- end
657
- rule(:typeId) { simpleId.as(:typeId) }
658
- rule(:typeLabelId) { simpleId.as(:typeLabelId) }
659
- rule(:typeLabelRef) { typeLabelId.as(:typeLabelRef) }
660
- rule(:typeLabel) { (typeLabelId | typeLabelRef).as(:typeLabel) }
661
- rule(:typeRef) { typeId.as(:typeRef) }
662
- rule(:unaryOp) { (op_plus | op_minus | tNOT).as(:unaryOp) }
663
- rule(:underlyingType) do
664
- (concreteTypes | constructedTypes).as(:underlyingType)
665
- end
666
- rule(:uniqueClause) do
667
- (tUNIQUE >> (uniqueRule >> op_delim >> (uniqueRule >> op_delim.as(:op_delim2)).repeat).as(:listOf_uniqueRule)).as(:uniqueClause)
668
- end
669
- rule(:uniqueRule) do
670
- ((ruleLabelId >> op_colon).maybe >> (referencedAttribute >> (op_comma >> referencedAttribute).repeat).as(:listOf_referencedAttribute)).as(:uniqueRule)
671
- end
672
- rule(:untilControl) { (tUNTIL >> logicalExpression).as(:untilControl) }
673
- rule(:useClause) do
674
- (tUSE >> tFROM >> schemaRef >>
675
- (op_leftparen >> (namedTypeOrRename >> (op_comma >>
676
- namedTypeOrRename).repeat).as(:listOf_namedTypeOrRename) >> op_rightparen).maybe >> op_delim).as(:useClause)
677
- end
678
- rule(:variableId) { simpleId.as(:variableId) }
679
- rule(:variableRef) { variableId.as(:variableRef) }
680
- rule(:whereClause) do
681
- (tWHERE >> (domainRule >> op_delim).repeat.as(:listOf_domainRule)).as(:whereClause)
682
- end
683
- rule(:whileControl) { (tWHILE >> logicalExpression).as(:whileControl) }
684
- rule(:width) { numericExpression.as(:width) }
685
- rule(:widthSpec) do
686
- (op_leftparen >> width >> op_rightparen >> tFIXED.maybe).as(:widthSpec)
687
- end
40
+ bytes.byteslice(3..).force_encoding(source.encoding)
688
41
  end
42
+ private_class_method :strip_bom
689
43
 
690
- # Parses Express file into an Express model
691
- # @param [String] file Express file path
692
- # @param [Boolean] skip_references skip resolving references
693
- # @param [Boolean] include_source attach original source code to model elements
694
- # @param [Boolean] use_native use native parser (default: true when available)
44
+ # Parses Express file into an Express model.
45
+ # @param file [String] Express file path
46
+ # @param skip_references [Boolean] skip resolving references
47
+ # @param include_source [Boolean] attach original source code to model elements
48
+ # @param use_native [Boolean] use native parser (default: true when available)
695
49
  # @return [Model::ExpFile] ExpFile containing parsed schemas
696
- # @raise [SchemaParseFailure] if the schema file fails to parse
697
- def self.from_file(file, skip_references: nil, include_source: nil, root_path: nil, use_native: nil) # rubocop:disable Metrics/AbcSize
50
+ # @raise [Error::SchemaParseFailure] if the schema file fails to parse
51
+ def self.from_file(file, skip_references: nil, include_source: nil,
52
+ root_path: nil, use_native: nil) # rubocop:disable Metrics/AbcSize
698
53
  Expressir::Benchmark.measure_file(file) do
699
- source = File.read file
54
+ source = strip_bom(File.read(file))
700
55
 
701
- # remove root path from file path
702
56
  schema_file = root_path ? Pathname.new(file.to_s).relative_path_from(root_path).to_s : file.to_s
703
57
 
704
- use_native = Parser.native_available? if use_native.nil?
58
+ use_native = Grammar::Parser.native_available? if use_native.nil?
705
59
 
706
60
  begin
707
- ast = if use_native && Parser.native_available?
61
+ ast = if use_native && Grammar::Parser.native_available?
708
62
  begin
709
- Parser.parse_native(source)
63
+ Grammar::Parser.parse_native(source)
710
64
  rescue StandardError
711
- # Native parser may fail on non-ASCII or edge cases;
712
- # fall back to Ruby parser
713
- Parser.cached_parser.parse source
65
+ Grammar::Parser.cached_parser.parse source
714
66
  end
715
67
  else
716
- Parser.cached_parser.parse source
68
+ Grammar::Parser.cached_parser.parse source
717
69
  end
718
70
  rescue Parsanol::ParseFailed => e
719
- # Instead of just printing, raise a proper error with file context
720
71
  raise Error::SchemaParseFailure.new(schema_file, e)
721
72
  end
722
73
 
723
- @exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: source,
724
- include_source: include_source)
74
+ exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: source,
75
+ include_source: include_source)
76
+
77
+ transfer_header_to_schema(exp_file, source)
725
78
 
726
- # Set file path on the ExpFile and propagate to schemas
727
- @exp_file.path = schema_file
728
- @exp_file.schemas.each do |schema|
79
+ exp_file.path = schema_file
80
+ exp_file.schemas.each do |schema|
729
81
  schema.file = schema_file
730
82
  schema.file_basename = File.basename(schema_file, ".exp")
731
83
  end
732
84
 
733
85
  unless skip_references
734
86
  Expressir::Benchmark.measure_references do
735
- @resolve_references_model_visitor = ResolveReferencesModelVisitor.new
736
- @resolve_references_model_visitor.visit(@exp_file)
87
+ ResolveReferencesModelVisitor.new.visit(exp_file)
737
88
  end
738
89
  end
739
90
 
740
- @exp_file
91
+ exp_file
741
92
  end
742
93
  end
743
94
 
744
- # Parses Express files into an Express model
745
- # @param [Array<String>] files Express file paths
746
- # @param [Boolean] skip_references skip resolving references
747
- # @param [Boolean] include_source attach original source code to model elements
748
- # @param [Boolean] use_native use native parser (default: true when available)
749
- # @yield [filename, schemas, error] Optional block called for each file processed
750
- # @yieldparam filename [String] Name of the file being processed
751
- # @yieldparam schemas [Array, nil] Array of parsed schemas (nil if parsing failed)
752
- # @yieldparam error [Exception, nil] Error that occurred (nil if parsing succeeded)
95
+ # Parses Express files into an Express model.
96
+ # @param files [Array<String>] Express file paths
97
+ # @param skip_references [Boolean] skip resolving references
98
+ # @param include_source [Boolean] attach original source code to model elements
99
+ # @param use_native [Boolean] use native parser (default: true when available)
100
+ # @yield [filename, schemas, error] Optional block called for each file
753
101
  # @return [Model::Repository] Repository containing all parsed ExpFiles
754
102
  def self.from_files(files, skip_references: nil, include_source: nil,
755
103
  root_path: nil, use_native: nil)
@@ -760,53 +108,46 @@ root_path: nil, use_native: nil)
760
108
  root_path: root_path, use_native: use_native)
761
109
  all_exp_files << exp_file
762
110
 
763
- # Call the progress block if provided
764
111
  yield(file, exp_file&.schemas, nil) if block_given?
765
112
  rescue StandardError => e
766
- # Call the progress block with the error if provided
767
113
  yield(file, nil, e) if block_given?
768
-
769
- # Re-raise the error if it's not a schema parse failure
770
- # This allows handling of specific schema parse failures while still propagating other errors
771
114
  raise unless e.is_a?(Error::SchemaParseFailure)
772
115
  end
773
116
 
774
- @repository = Model::Repository.new(files: all_exp_files)
117
+ repository = Model::Repository.new(files: all_exp_files)
775
118
 
776
119
  unless skip_references
777
120
  Expressir::Benchmark.measure_references do
778
- @resolve_references_model_visitor = ResolveReferencesModelVisitor.new
779
- @resolve_references_model_visitor.visit(@repository)
121
+ ResolveReferencesModelVisitor.new.visit(repository)
780
122
  end
781
123
  end
782
124
 
783
- @repository
125
+ repository
784
126
  end
785
127
 
786
- # Parses Express content string into an Express model
787
- # @param [String] content Express content as string
788
- # @param [Boolean] skip_references skip resolving references
789
- # @param [Boolean] include_source attach original source code to model elements
790
- # @param [Boolean] use_native use native parser (default: true when available)
791
- # @param [Boolean] use_streaming use streaming builder for maximum performance (default: false)
128
+ # Parses Express content string into an Express model.
129
+ # @param content [String] EXPRESS source code
130
+ # @param skip_references [Boolean] skip resolving references
131
+ # @param include_source [Boolean] attach original source code to model elements
132
+ # @param use_native [Boolean] use native parser (default: true when available)
133
+ # @param use_streaming [Boolean] use streaming builder for maximum performance
792
134
  # @return [Model::ExpFile] Parsed ExpFile
793
- # @raise [SchemaParseFailure] if the content fails to parse
135
+ # @raise [Error::SchemaParseFailure] if the content fails to parse
794
136
  def self.from_exp(content, skip_references: nil, include_source: nil,
795
137
  use_native: nil, use_streaming: false)
796
- # Streaming builder mode - uses Parsanol streaming callbacks
797
- if use_streaming && Parser.native_available? && defined?(Parsanol::Native.parse_with_builder)
138
+ content = strip_bom(content)
139
+ if use_streaming && Grammar::Parser.native_available? && defined?(Parsanol::Native.parse_with_builder)
798
140
  return from_exp_streaming(content, skip_references: skip_references,
799
141
  include_source: include_source)
800
142
  end
801
143
 
802
- use_native = Parser.native_available? if use_native.nil?
144
+ use_native = Grammar::Parser.native_available? if use_native.nil?
803
145
 
804
146
  begin
805
- # Use cached parser instance for performance (avoids ~7ms Parser.new overhead)
806
- ast = if use_native && Parser.native_available?
807
- Parser.parse_native(content)
147
+ ast = if use_native && Grammar::Parser.native_available?
148
+ Grammar::Parser.parse_native(content)
808
149
  else
809
- Parser.cached_parser.parse(content)
150
+ Grammar::Parser.cached_parser.parse(content)
810
151
  end
811
152
  rescue Parsanol::ParseFailed, StandardError => e
812
153
  raise Error::SchemaParseFailure.new("(from string)", e)
@@ -816,6 +157,8 @@ root_path: nil, use_native: nil)
816
157
  source: content,
817
158
  include_source: include_source)
818
159
 
160
+ transfer_header_to_schema(exp_file, content)
161
+
819
162
  exp_file.schemas.each do |schema|
820
163
  schema.file = nil
821
164
  schema.file_basename = nil
@@ -823,23 +166,22 @@ root_path: nil, use_native: nil)
823
166
 
824
167
  unless skip_references
825
168
  Expressir::Benchmark.measure_references do
826
- resolve_references_model_visitor = ResolveReferencesModelVisitor.new
827
- resolve_references_model_visitor.visit(exp_file)
169
+ ResolveReferencesModelVisitor.new.visit(exp_file)
828
170
  end
829
171
  end
830
172
 
831
173
  exp_file
832
174
  end
833
175
 
834
- # Parse using streaming builder (construct-by-construct)
835
- # @param [String] content Express content as string
836
- # @param [Boolean] skip_references skip resolving references
837
- # @param [Boolean] include_source attach original source code to model elements
176
+ # Parse using streaming builder (construct-by-construct).
177
+ # @param content [String] EXPRESS source code
178
+ # @param skip_references [Boolean] skip resolving references
179
+ # @param include_source [Boolean] attach original source code to model elements
838
180
  # @return [Model::ExpFile] Parsed ExpFile
839
- # @raise [SchemaParseFailure] if the content fails to parse
181
+ # @raise [Error::SchemaParseFailure] if the content fails to parse
840
182
  def self.from_exp_streaming_builder(content, skip_references: nil,
841
183
  include_source: nil)
842
- grammar_json = Parser.cached_grammar_json
184
+ grammar_json = Grammar::Parser.cached_grammar_json
843
185
  builder = ::Expressir::Express::StreamingBuilder.new(source: content,
844
186
  include_source: include_source)
845
187
 
@@ -857,31 +199,29 @@ include_source: nil)
857
199
 
858
200
  unless skip_references
859
201
  Expressir::Benchmark.measure_references do
860
- resolve_references_model_visitor = ResolveReferencesModelVisitor.new
861
- resolve_references_model_visitor.visit(exp_file)
202
+ ResolveReferencesModelVisitor.new.visit(exp_file)
862
203
  end
863
204
  end
864
205
 
865
206
  exp_file
866
207
  end
867
208
 
868
- # Parse each schema separately with fresh arena (memory-bounded)
209
+ # Parse each schema separately with fresh arena (memory-bounded).
869
210
  #
870
- # This splits the source into schema blocks and parses each independently.
871
- # Memory is bounded by the largest schema, not the entire file.
211
+ # Splits source into schema blocks via {SchemaBlockScanner} and parses
212
+ # each independently. Memory is bounded by the largest schema, not the
213
+ # entire file.
872
214
  #
873
215
  # @param content [String] EXPRESS source code
874
- # @param skip_references [Boolean] Whether to skip reference resolution
875
- # @param include_source [Boolean] Whether to include source in model
876
- # @return [Expressir::Model::ExpFile] Parsed EXPRESS file
216
+ # @param skip_references [Boolean] skip resolving references
217
+ # @param include_source [Boolean] attach original source code to model elements
218
+ # @return [Model::ExpFile] Parsed ExpFile
877
219
  def self.from_exp_streaming(content, skip_references: nil,
878
220
  include_source: nil)
879
- grammar_json = Parser.cached_schema_grammar_json
221
+ grammar_json = Grammar::Parser.cached_schema_grammar_json
880
222
 
881
- # Extract schema blocks from source
882
- schema_blocks = extract_schema_blocks(content)
223
+ schema_blocks = SchemaBlockScanner.extract_schema_blocks(content)
883
224
 
884
- # Parse each schema with fresh arena
885
225
  schemas = schema_blocks.map do |block|
886
226
  ast = Parsanol::Native.parse_fresh(grammar_json, block[:source])
887
227
  schema_model = Builder.build(ast)
@@ -893,7 +233,6 @@ include_source: nil)
893
233
  )
894
234
  end
895
235
 
896
- # Build the file model
897
236
  exp_file = Expressir::Model::ExpFile.new
898
237
  exp_file.schemas = schemas
899
238
 
@@ -904,143 +243,34 @@ include_source: nil)
904
243
 
905
244
  unless skip_references
906
245
  Expressir::Benchmark.measure_references do
907
- resolve_references_model_visitor = ResolveReferencesModelVisitor.new
908
- resolve_references_model_visitor.visit(exp_file)
246
+ ResolveReferencesModelVisitor.new.visit(exp_file)
909
247
  end
910
248
  end
911
249
 
912
250
  exp_file
913
251
  end
914
252
 
915
- # Extract individual schema blocks from EXPRESS source
916
- #
917
- # This uses a state machine to properly handle nested comments and strings.
918
- #
919
- # @param source [String] EXPRESS source
920
- # @return [Array<Hash>] Array of {name: String, source: String} for each schema
921
- def self.extract_schema_blocks(source)
922
- blocks = []
923
- pos = 0
924
- len = source.length
925
-
926
- while pos < len
927
- # Skip whitespace and find SCHEMA keyword
928
- skip_ws_and_comments(source, pos)
929
- pos = skip_ws_and_comments(source, pos)
253
+ # Transfer file-level untagged remarks that appear before the first
254
+ # SCHEMA keyword to the first schema's +header+ attribute so they are
255
+ # accessible via +schema.header+ and through Liquid drops.
256
+ def self.transfer_header_to_schema(exp_file, source = nil)
257
+ return unless exp_file.untagged_remarks&.any?
258
+ return unless exp_file.schemas&.any?
259
+ return unless source
930
260
 
931
- break if pos >= len
261
+ schema_pos = source.b.index(/\bSCHEMA\b/)
262
+ return unless schema_pos
932
263
 
933
- # Check for SCHEMA
934
- if source[pos..].start_with?("SCHEMA")
935
- result = parse_schema_block(source, pos)
936
- if result
937
- blocks << result
938
- pos = result[:end_pos]
939
- next
940
- end
941
- end
942
-
943
- pos += 1
944
- end
945
-
946
- blocks
947
- end
948
-
949
- private_class_method :extract_schema_blocks
950
-
951
- def self.parse_schema_block(source, start_pos)
952
- # Must be at SCHEMA keyword
953
- return nil unless source[start_pos..].start_with?("SCHEMA")
954
-
955
- pos = start_pos + "SCHEMA".length
956
- pos = skip_spaces(source, pos)
957
-
958
- # Extract schema name (identifier)
959
- name_start = pos
960
- while pos < source.length && (source[pos] =~ /[a-zA-Z0-9_]/ || source[pos] == "_")
961
- pos += 1
962
- end
963
- schema_name = source[name_start...pos]
964
-
965
- return nil if schema_name.empty?
966
-
967
- # Skip to END_SCHEMA
968
- depth = 1
969
- search_pos = pos
970
- end_pos = nil
971
-
972
- while search_pos < source.length
973
- if source[search_pos] == '"'
974
- # String literal - skip to end
975
- search_pos += 1
976
- while search_pos < source.length && source[search_pos] != '"'
977
- search_pos += 1
978
- end
979
- search_pos += 1
980
- elsif source[search_pos] == "(" && source[search_pos + 1] == "*"
981
- # Comment - skip to end
982
- search_pos += 2
983
- while search_pos < source.length && !(source[search_pos] == "*" && source[search_pos + 1] == ")")
984
- search_pos += 1
985
- end
986
- search_pos += 2
987
- elsif source[search_pos..].start_with?("END_SCHEMA")
988
- depth -= 1
989
- if depth.zero?
990
- end_pos = search_pos + "END_SCHEMA".length
991
- # Skip trailing whitespace and semicolon
992
- pos = end_pos
993
- pos = skip_spaces(source, pos)
994
- pos += 1 if source[pos] == ";" # semicolon
995
- break
996
- end
997
- search_pos += "END_SCHEMA".length
998
- else
999
- search_pos += 1
1000
- end
264
+ header_remarks = exp_file.untagged_remarks.select do |r|
265
+ r.source_offset && r.source_offset < schema_pos
1001
266
  end
267
+ return unless header_remarks.any?
1002
268
 
1003
- return nil unless end_pos
1004
-
1005
- schema_source = source[start_pos...end_pos]
1006
- {
1007
- name: schema_name,
1008
- source: schema_source,
1009
- start_pos: start_pos,
1010
- end_pos: pos,
1011
- }
1012
- end
1013
- private_class_method :parse_schema_block
1014
-
1015
- def self.skip_spaces(source, pos)
1016
- while pos < source.length && [" ", "\t", "\n",
1017
- "\r"].include?(source[pos])
1018
- pos += 1
1019
- end
1020
- pos
1021
- end
1022
- private_class_method :skip_spaces
1023
-
1024
- def self.skip_ws_and_comments(source, pos)
1025
- len = source.length
1026
- while pos < len
1027
- c = source[pos]
1028
- if [" ", "\t", "\n", "\r"].include?(c)
1029
- pos += 1
1030
- elsif c == "(" && source[pos + 1] == "*"
1031
- # Comment - skip to end
1032
- pos += 2
1033
- while pos < len - 1 && !(source[pos] == "*" && source[pos + 1] == ")")
1034
- pos += 1
1035
- end
1036
- pos += 2
1037
- else
1038
- break
1039
- end
1040
- end
1041
- pos
269
+ header_text = header_remarks.map(&:text).join("\n")
270
+ exp_file.schemas.first.header = header_text
271
+ exp_file.untagged_remarks -= header_remarks
1042
272
  end
1043
- private_class_method :skip_ws_and_comments
273
+ private_class_method :transfer_header_to_schema
1044
274
 
1045
275
  private_class_method :from_exp_streaming
1046
276
  end