expressir 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/TODO.bugs/01-stale-transformer-autoload.md +39 -0
- data/TODO.bugs/02-parser-class-instance-vars.md +36 -0
- data/TODO.bugs/03-builder-mutable-state.md +43 -0
- data/TODO.bugs/04-formatter-public-send-dispatch.md +53 -0
- data/TODO.bugs/05-anonymous-formatter-subclass.md +45 -0
- data/TODO.bugs/06-collection-registry-single-source.md +53 -0
- data/TODO.bugs/07-require-relative-cleanup.md +42 -0
- data/TODO.bugs/08-require-expressir-in-commands.md +34 -0
- data/TODO.bugs/09-parser-split.md +53 -0
- data/TODO.bugs/10-to-s-override.md +42 -0
- data/TODO.bugs/11-parser-class-variables.md +39 -0
- data/TODO.bugs/12-marker-modules-vs-registry.md +64 -0
- data/TODO.bugs/13-string-literal-scanner-limitation.md +52 -0
- data/TODO.bugs/14-model-formatting-leak.md +30 -0
- data/TODO.bugs/15-expression-children-macro.md +27 -0
- data/TODO.bugs/16-pretty-formatter-duplication.md +28 -0
- data/TODO.bugs/17-snake-case-cache-mutable-constant.md +28 -0
- data/TODO.bugs/18-const-get-private-constants.md +30 -0
- data/TODO.bugs/19-format-methods-public.md +22 -0
- data/TODO.bugs/20-coverage-nested-entities-dedup.md +20 -0
- data/TODO.bugs/21-operator-tokens-secondary-dispatch.md +21 -0
- data/TODO.bugs/22-builder-fast-path-wrappers.md +32 -0
- data/TODO.bugs/23-coverage-inverse-maps.md +21 -0
- data/TODO.bugs/24-streaming-builder-complexity.md +19 -0
- data/TODO.bugs/25-debug-puts-in-production.md +21 -0
- data/TODO.bugs/26-generic-entity-children-misplaced.md +21 -0
- data/TODO.bugs/27-package-build-god-method.md +19 -0
- data/TODO.bugs/28-package-god-class.md +30 -0
- data/TODO.bugs/29-validate-ascii-god-class.md +24 -0
- data/TODO.bugs/30-unicode-map-extraction.md +19 -0
- data/TODO.bugs/README.md +43 -0
- data/TODO.max-perf/01-restore-ci-green.md +29 -0
- data/TODO.max-perf/02-streaming-parse-path.md +31 -0
- data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
- data/TODO.max-perf/04-benchmark-harness.md +28 -0
- data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
- data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
- data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
- data/TODO.max-perf/08-builder-build-perf.md +45 -0
- data/TODO.max-perf/09-grammar-cold-start.md +25 -0
- data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
- data/TODO.max-perf/11-ci-green-closeout.md +25 -0
- data/TODO.max-perf/12-require-boot-profile.md +25 -0
- data/TODO.max-perf/13-key-conversion-specs.md +26 -0
- data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
- data/benchmark/srl_benchmark.rb +76 -17
- data/docs/_tutorials/formatting-schemas.adoc +89 -0
- data/docs/_tutorials/index.adoc +10 -0
- data/docs/lychee.toml +3 -0
- data/expressir.gemspec +4 -2
- data/lib/expressir/cli.rb +3 -0
- data/lib/expressir/commands/changes_import_eengine.rb +0 -6
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/clean.rb +1 -1
- data/lib/expressir/commands/coverage.rb +6 -2
- data/lib/expressir/commands/file_violations.rb +70 -0
- data/lib/expressir/commands/format.rb +1 -1
- data/lib/expressir/commands/non_ascii_character.rb +49 -0
- data/lib/expressir/commands/non_ascii_violation_collection.rb +301 -0
- data/lib/expressir/commands/package.rb +143 -187
- data/lib/expressir/commands/validate_ascii.rb +0 -510
- data/lib/expressir/commands/version.rb +1 -1
- data/lib/expressir/commands.rb +3 -0
- data/lib/expressir/coverage.rb +49 -117
- data/lib/expressir/express/ast_key_converter.rb +114 -0
- data/lib/expressir/express/builder.rb +71 -194
- data/lib/expressir/express/builder_context.rb +22 -0
- data/lib/expressir/express/builders/expression_builder.rb +16 -16
- data/lib/expressir/express/cache.rb +35 -8
- data/lib/expressir/express/error.rb +24 -0
- data/lib/expressir/express/formatter.rb +33 -18
- data/lib/expressir/express/formatters/declarations_formatter.rb +24 -2
- data/lib/expressir/express/formatters/remark_formatter.rb +75 -2
- data/lib/expressir/express/formatters/statements_formatter.rb +20 -3
- data/lib/expressir/express/grammar/parser.rb +705 -0
- data/lib/expressir/express/grammar.rb +11 -0
- data/lib/expressir/express/node_position_index.rb +215 -0
- data/lib/expressir/express/parallel_files.rb +229 -0
- data/lib/expressir/express/parser.rb +138 -950
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +497 -581
- data/lib/expressir/express/remark_scanner.rb +84 -19
- data/lib/expressir/express/schema_block_scanner.rb +137 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +223 -0
- data/lib/expressir/express/source_formatter.rb +15 -0
- data/lib/expressir/express/streaming_builder.rb +147 -176
- data/lib/expressir/express.rb +10 -6
- data/lib/expressir/model/concerns.rb +3 -0
- data/lib/expressir/model/data_types/generic_entity.rb +6 -6
- data/lib/expressir/model/declarations/entity.rb +5 -0
- data/lib/expressir/model/declarations/function.rb +5 -0
- data/lib/expressir/model/declarations/procedure.rb +5 -0
- data/lib/expressir/model/declarations/rule.rb +6 -0
- data/lib/expressir/model/declarations/schema.rb +21 -8
- data/lib/expressir/model/declarations/type.rb +3 -0
- data/lib/expressir/model/exp_file.rb +2 -0
- data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -0
- data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -0
- data/lib/expressir/model/expressions/binary_expression.rb +1 -0
- data/lib/expressir/model/expressions/entity_constructor.rb +1 -0
- data/lib/expressir/model/expressions/function_call.rb +1 -0
- data/lib/expressir/model/expressions/interval.rb +1 -0
- data/lib/expressir/model/expressions/query_expression.rb +2 -0
- data/lib/expressir/model/expressions/unary_expression.rb +1 -0
- data/lib/expressir/model/model_element.rb +133 -14
- data/lib/expressir/model/remark_format.rb +17 -0
- data/lib/expressir/model/remark_info.rb +31 -3
- data/lib/expressir/model/remark_placement.rb +29 -0
- data/lib/expressir/model/repository.rb +18 -5
- data/lib/expressir/model/statements/alias.rb +2 -0
- data/lib/expressir/model/statements/assignment.rb +4 -0
- data/lib/expressir/model/statements/case.rb +21 -0
- data/lib/expressir/model/statements/case_action.rb +1 -0
- data/lib/expressir/model/statements/compound.rb +4 -0
- data/lib/expressir/model/statements/escape.rb +3 -0
- data/lib/expressir/model/statements/if.rb +5 -0
- data/lib/expressir/model/statements/null.rb +3 -0
- data/lib/expressir/model/statements/procedure_call.rb +3 -0
- data/lib/expressir/model/statements/repeat.rb +3 -0
- data/lib/expressir/model/statements/return.rb +3 -0
- data/lib/expressir/model/statements/skip.rb +3 -0
- data/lib/expressir/model.rb +12 -3
- data/lib/expressir/package/builder.rb +2 -2
- data/lib/expressir/version.rb +6 -1
- data/lib/expressir.rb +56 -3
- metadata +81 -7
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -1,758 +1,131 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
124
|
-
|
|
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
|
|
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
|
|
131
30
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
39
|
|
|
137
|
-
|
|
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]
|
|
692
|
-
# @param [Boolean]
|
|
693
|
-
# @param [Boolean]
|
|
694
|
-
# @param [Boolean]
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
724
|
-
|
|
74
|
+
exp_file = ::Expressir::Express::Builder.build_with_remarks(ast, source: source,
|
|
75
|
+
include_source: include_source)
|
|
725
76
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
77
|
+
transfer_header_to_schema(exp_file, source)
|
|
78
|
+
|
|
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
|
-
|
|
736
|
-
@resolve_references_model_visitor.visit(@exp_file)
|
|
87
|
+
ResolveReferencesModelVisitor.new.visit(exp_file)
|
|
737
88
|
end
|
|
738
89
|
end
|
|
739
90
|
|
|
740
|
-
|
|
91
|
+
exp_file
|
|
741
92
|
end
|
|
742
93
|
end
|
|
743
94
|
|
|
744
|
-
# Parses Express files into an Express model
|
|
745
|
-
# @param [Array<String>]
|
|
746
|
-
# @param [Boolean]
|
|
747
|
-
# @param [Boolean]
|
|
748
|
-
# @param [Boolean]
|
|
749
|
-
# @yield [filename, schemas, error] Optional block called for each file
|
|
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
|
-
root_path: nil, use_native: nil)
|
|
103
|
+
root_path: nil, use_native: nil, max_processes: nil, &progress)
|
|
104
|
+
all_exp_files = if ParallelFiles.sequential?(files, max_processes)
|
|
105
|
+
parse_files_sequentially(
|
|
106
|
+
files, skip_references: skip_references, include_source: include_source,
|
|
107
|
+
root_path: root_path, use_native: use_native
|
|
108
|
+
) do |file, exp_file, error|
|
|
109
|
+
progress&.call(file, exp_file&.schemas, error)
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
ParallelFiles.run(
|
|
113
|
+
files,
|
|
114
|
+
max_processes: max_processes,
|
|
115
|
+
parse: lambda do |file|
|
|
116
|
+
from_file(file, skip_references: true, root_path: root_path,
|
|
117
|
+
use_native: use_native)
|
|
118
|
+
end,
|
|
119
|
+
) do |file, exp_file, error|
|
|
120
|
+
progress&.call(file, exp_file&.schemas, error)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
build_repository(all_exp_files, skip_references: skip_references)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.parse_files_sequentially(files, skip_references: nil,
|
|
128
|
+
include_source: nil, root_path: nil, use_native: nil, &block)
|
|
756
129
|
all_exp_files = []
|
|
757
130
|
|
|
758
131
|
files.each do |file|
|
|
@@ -760,53 +133,56 @@ root_path: nil, use_native: nil)
|
|
|
760
133
|
root_path: root_path, use_native: use_native)
|
|
761
134
|
all_exp_files << exp_file
|
|
762
135
|
|
|
763
|
-
|
|
764
|
-
yield(file, exp_file&.schemas, nil) if block_given?
|
|
136
|
+
yield(file, exp_file, nil) if block
|
|
765
137
|
rescue StandardError => e
|
|
766
|
-
#
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
# This allows handling of specific schema parse failures while still propagating other errors
|
|
138
|
+
# Nil-pad so results align with files by index, exactly like the
|
|
139
|
+
# parallel path does.
|
|
140
|
+
all_exp_files << nil if e.is_a?(Error::SchemaParseFailure)
|
|
141
|
+
yield(file, nil, e) if block
|
|
771
142
|
raise unless e.is_a?(Error::SchemaParseFailure)
|
|
772
143
|
end
|
|
773
144
|
|
|
774
|
-
|
|
145
|
+
all_exp_files
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def self.build_repository(all_exp_files, skip_references: nil)
|
|
149
|
+
repository = Model::Repository.new(files: all_exp_files)
|
|
775
150
|
|
|
776
151
|
unless skip_references
|
|
777
152
|
Expressir::Benchmark.measure_references do
|
|
778
|
-
|
|
779
|
-
@resolve_references_model_visitor.visit(@repository)
|
|
153
|
+
ResolveReferencesModelVisitor.new.visit(repository)
|
|
780
154
|
end
|
|
781
155
|
end
|
|
782
156
|
|
|
783
|
-
|
|
157
|
+
repository
|
|
784
158
|
end
|
|
785
159
|
|
|
786
|
-
# Parses Express content string into an Express model
|
|
787
|
-
# @param [String]
|
|
788
|
-
# @param [Boolean]
|
|
789
|
-
# @param [Boolean]
|
|
790
|
-
# @param [Boolean]
|
|
791
|
-
# @param [Boolean]
|
|
160
|
+
# Parses Express content string into an Express model.
|
|
161
|
+
# @param content [String] EXPRESS source code
|
|
162
|
+
# @param skip_references [Boolean] skip resolving references
|
|
163
|
+
# @param include_source [Boolean] attach original source code to model elements
|
|
164
|
+
# @param use_native [Boolean] use native parser (default: true when available)
|
|
165
|
+
# @param use_streaming [Boolean] unsupported on current parsanol;
|
|
166
|
+
# passing true raises {Error::StreamingUnsupportedError}. The
|
|
167
|
+
# streaming paths return when parsanol exposes a stable
|
|
168
|
+
# parse_with_builder (see parsanol-ruby#52 and the TODO.max-perf/02
|
|
169
|
+
# notes).
|
|
792
170
|
# @return [Model::ExpFile] Parsed ExpFile
|
|
793
|
-
# @raise [SchemaParseFailure] if the content fails to parse
|
|
171
|
+
# @raise [Error::SchemaParseFailure] if the content fails to parse
|
|
794
172
|
def self.from_exp(content, skip_references: nil, include_source: nil,
|
|
795
173
|
use_native: nil, use_streaming: false)
|
|
796
|
-
|
|
797
|
-
if use_streaming
|
|
798
|
-
|
|
799
|
-
include_source: include_source)
|
|
174
|
+
content = strip_bom(content)
|
|
175
|
+
if use_streaming
|
|
176
|
+
raise Error::StreamingUnsupportedError
|
|
800
177
|
end
|
|
801
178
|
|
|
802
|
-
use_native = Parser.native_available? if use_native.nil?
|
|
179
|
+
use_native = Grammar::Parser.native_available? if use_native.nil?
|
|
803
180
|
|
|
804
181
|
begin
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
Parser.parse_native(content)
|
|
182
|
+
ast = if use_native && Grammar::Parser.native_available?
|
|
183
|
+
Grammar::Parser.parse_native(content)
|
|
808
184
|
else
|
|
809
|
-
Parser.cached_parser.parse(content)
|
|
185
|
+
Grammar::Parser.cached_parser.parse(content)
|
|
810
186
|
end
|
|
811
187
|
rescue Parsanol::ParseFailed, StandardError => e
|
|
812
188
|
raise Error::SchemaParseFailure.new("(from string)", e)
|
|
@@ -816,39 +192,7 @@ root_path: nil, use_native: nil)
|
|
|
816
192
|
source: content,
|
|
817
193
|
include_source: include_source)
|
|
818
194
|
|
|
819
|
-
exp_file
|
|
820
|
-
schema.file = nil
|
|
821
|
-
schema.file_basename = nil
|
|
822
|
-
end
|
|
823
|
-
|
|
824
|
-
unless skip_references
|
|
825
|
-
Expressir::Benchmark.measure_references do
|
|
826
|
-
resolve_references_model_visitor = ResolveReferencesModelVisitor.new
|
|
827
|
-
resolve_references_model_visitor.visit(exp_file)
|
|
828
|
-
end
|
|
829
|
-
end
|
|
830
|
-
|
|
831
|
-
exp_file
|
|
832
|
-
end
|
|
833
|
-
|
|
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
|
|
838
|
-
# @return [Model::ExpFile] Parsed ExpFile
|
|
839
|
-
# @raise [SchemaParseFailure] if the content fails to parse
|
|
840
|
-
def self.from_exp_streaming_builder(content, skip_references: nil,
|
|
841
|
-
include_source: nil)
|
|
842
|
-
grammar_json = Parser.cached_grammar_json
|
|
843
|
-
builder = ::Expressir::Express::StreamingBuilder.new(source: content,
|
|
844
|
-
include_source: include_source)
|
|
845
|
-
|
|
846
|
-
begin
|
|
847
|
-
exp_file = Parsanol::Native.parse_with_builder(grammar_json,
|
|
848
|
-
content, builder)
|
|
849
|
-
rescue StandardError => e
|
|
850
|
-
raise Error::SchemaParseFailure.new("(streaming)", e)
|
|
851
|
-
end
|
|
195
|
+
transfer_header_to_schema(exp_file, content)
|
|
852
196
|
|
|
853
197
|
exp_file.schemas.each do |schema|
|
|
854
198
|
schema.file = nil
|
|
@@ -857,192 +201,36 @@ include_source: nil)
|
|
|
857
201
|
|
|
858
202
|
unless skip_references
|
|
859
203
|
Expressir::Benchmark.measure_references do
|
|
860
|
-
|
|
861
|
-
resolve_references_model_visitor.visit(exp_file)
|
|
204
|
+
ResolveReferencesModelVisitor.new.visit(exp_file)
|
|
862
205
|
end
|
|
863
206
|
end
|
|
864
207
|
|
|
865
208
|
exp_file
|
|
866
209
|
end
|
|
867
210
|
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
#
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
# @param include_source [Boolean] Whether to include source in model
|
|
876
|
-
# @return [Expressir::Model::ExpFile] Parsed EXPRESS file
|
|
877
|
-
def self.from_exp_streaming(content, skip_references: nil,
|
|
878
|
-
include_source: nil)
|
|
879
|
-
grammar_json = Parser.cached_schema_grammar_json
|
|
880
|
-
|
|
881
|
-
# Extract schema blocks from source
|
|
882
|
-
schema_blocks = extract_schema_blocks(content)
|
|
211
|
+
# Transfer file-level untagged remarks that appear before the first
|
|
212
|
+
# SCHEMA keyword to the first schema's +header+ attribute so they are
|
|
213
|
+
# accessible via +schema.header+ and through Liquid drops.
|
|
214
|
+
def self.transfer_header_to_schema(exp_file, source = nil)
|
|
215
|
+
return unless exp_file.untagged_remarks&.any?
|
|
216
|
+
return unless exp_file.schemas&.any?
|
|
217
|
+
return unless source
|
|
883
218
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
ast = Parsanol::Native.parse_fresh(grammar_json, block[:source])
|
|
887
|
-
schema_model = Builder.build(ast)
|
|
888
|
-
schema_model.source = block[:source]
|
|
889
|
-
schema_model
|
|
890
|
-
rescue StandardError => e
|
|
891
|
-
raise Error::SchemaParseFailure.new(
|
|
892
|
-
"(schema #{block[:name] || 'unknown'})", e
|
|
893
|
-
)
|
|
894
|
-
end
|
|
895
|
-
|
|
896
|
-
# Build the file model
|
|
897
|
-
exp_file = Expressir::Model::ExpFile.new
|
|
898
|
-
exp_file.schemas = schemas
|
|
899
|
-
|
|
900
|
-
exp_file.schemas.each do |schema|
|
|
901
|
-
schema.file = nil
|
|
902
|
-
schema.file_basename = nil
|
|
903
|
-
end
|
|
219
|
+
schema_pos = source.b.index(/\bSCHEMA\b/)
|
|
220
|
+
return unless schema_pos
|
|
904
221
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
resolve_references_model_visitor = ResolveReferencesModelVisitor.new
|
|
908
|
-
resolve_references_model_visitor.visit(exp_file)
|
|
909
|
-
end
|
|
222
|
+
header_remarks = exp_file.untagged_remarks.select do |r|
|
|
223
|
+
r.source_offset && r.source_offset < schema_pos
|
|
910
224
|
end
|
|
225
|
+
return unless header_remarks.any?
|
|
911
226
|
|
|
912
|
-
|
|
227
|
+
header_text = header_remarks.map(&:text).join("\n")
|
|
228
|
+
exp_file.schemas.first.header = header_text
|
|
229
|
+
exp_file.untagged_remarks -= header_remarks
|
|
913
230
|
end
|
|
914
|
-
|
|
915
|
-
|
|
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)
|
|
930
|
-
|
|
931
|
-
break if pos >= len
|
|
932
|
-
|
|
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
|
|
1001
|
-
end
|
|
1002
|
-
|
|
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
|
|
1042
|
-
end
|
|
1043
|
-
private_class_method :skip_ws_and_comments
|
|
1044
|
-
|
|
1045
|
-
private_class_method :from_exp_streaming
|
|
231
|
+
private_class_method :transfer_header_to_schema
|
|
232
|
+
private_class_method :parse_files_sequentially
|
|
233
|
+
private_class_method :build_repository
|
|
1046
234
|
end
|
|
1047
235
|
end
|
|
1048
236
|
end
|