expressir 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +254 -77
- data/Gemfile +4 -1
- data/README.adoc +63 -26
- data/benchmark/srl_benchmark.rb +386 -0
- data/benchmark/srl_native_benchmark.rb +142 -0
- data/benchmark/srl_ruby_benchmark.rb +130 -0
- data/expressir.gemspec +4 -2
- data/lib/expressir/benchmark.rb +1 -1
- data/lib/expressir/changes/item_change.rb +0 -1
- data/lib/expressir/changes/mapping_change.rb +0 -1
- data/lib/expressir/changes/schema_change.rb +0 -2
- data/lib/expressir/changes/version_change.rb +0 -3
- data/lib/expressir/changes.rb +5 -6
- data/lib/expressir/cli.rb +10 -24
- data/lib/expressir/commands/base.rb +2 -9
- data/lib/expressir/commands/changes.rb +0 -2
- data/lib/expressir/commands/changes_import_eengine.rb +0 -3
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/format.rb +5 -3
- data/lib/expressir/commands/manifest.rb +0 -7
- data/lib/expressir/commands/package.rb +93 -101
- data/lib/expressir/commands/validate.rb +0 -2
- data/lib/expressir/commands/validate_ascii.rb +2 -4
- data/lib/expressir/commands/validate_load.rb +8 -5
- data/lib/expressir/commands.rb +20 -0
- data/lib/expressir/config.rb +0 -2
- data/lib/expressir/coverage.rb +11 -4
- data/lib/expressir/eengine/arm_compare_report.rb +1 -4
- data/lib/expressir/eengine/changes_section.rb +1 -3
- data/lib/expressir/eengine/compare_report.rb +1 -13
- data/lib/expressir/eengine/mim_compare_report.rb +1 -4
- data/lib/expressir/eengine/modified_object.rb +1 -2
- data/lib/expressir/eengine.rb +9 -0
- data/lib/expressir/errors.rb +113 -0
- data/lib/expressir/express/builder.rb +22 -7
- data/lib/expressir/express/builder_registry.rb +411 -0
- data/lib/expressir/express/builders/attribute_decl_builder.rb +0 -6
- data/lib/expressir/express/builders/built_in_builder.rb +1 -16
- data/lib/expressir/express/builders/constant_builder.rb +4 -19
- data/lib/expressir/express/builders/declaration_builder.rb +0 -4
- data/lib/expressir/express/builders/derive_clause_builder.rb +0 -2
- data/lib/expressir/express/builders/derived_attr_builder.rb +0 -2
- data/lib/expressir/express/builders/domain_rule_builder.rb +0 -2
- data/lib/expressir/express/builders/entity_decl_builder.rb +7 -13
- data/lib/expressir/express/builders/explicit_attr_builder.rb +5 -8
- data/lib/expressir/express/builders/expression_builder.rb +31 -67
- data/lib/expressir/express/builders/function_decl_builder.rb +20 -18
- data/lib/expressir/express/builders/interface_builder.rb +0 -20
- data/lib/expressir/express/builders/inverse_attr_builder.rb +0 -2
- data/lib/expressir/express/builders/inverse_attr_type_builder.rb +0 -6
- data/lib/expressir/express/builders/inverse_clause_builder.rb +0 -2
- data/lib/expressir/express/builders/literal_builder.rb +1 -15
- data/lib/expressir/express/builders/procedure_decl_builder.rb +20 -19
- data/lib/expressir/express/builders/qualifier_builder.rb +0 -27
- data/lib/expressir/express/builders/reference_builder.rb +1 -10
- data/lib/expressir/express/builders/rule_decl_builder.rb +21 -19
- data/lib/expressir/express/builders/schema_body_decl_builder.rb +0 -4
- data/lib/expressir/express/builders/schema_decl_builder.rb +7 -13
- data/lib/expressir/express/builders/schema_version_builder.rb +0 -6
- data/lib/expressir/express/builders/simple_id_builder.rb +1 -10
- data/lib/expressir/express/builders/statement_builder.rb +4 -32
- data/lib/expressir/express/builders/subtype_constraint_builder.rb +6 -30
- data/lib/expressir/express/builders/syntax_builder.rb +60 -7
- data/lib/expressir/express/builders/type_builder.rb +3 -45
- data/lib/expressir/express/builders/type_decl_builder.rb +1 -7
- data/lib/expressir/express/builders/unique_clause_builder.rb +1 -3
- data/lib/expressir/express/builders/unique_rule_builder.rb +0 -2
- data/lib/expressir/express/builders/where_clause_builder.rb +1 -3
- data/lib/expressir/express/builders.rb +47 -35
- data/lib/expressir/express/error.rb +0 -3
- data/lib/expressir/express/formatter.rb +17 -19
- data/lib/expressir/express/formatters/data_types_formatter.rb +295 -293
- data/lib/expressir/express/formatters/declarations_formatter.rb +617 -615
- data/lib/expressir/express/formatters/expressions_formatter.rb +146 -144
- data/lib/expressir/express/formatters/literals_formatter.rb +35 -33
- data/lib/expressir/express/formatters/references_formatter.rb +34 -32
- data/lib/expressir/express/formatters/remark_formatter.rb +176 -209
- data/lib/expressir/express/formatters/remark_item_formatter.rb +18 -16
- data/lib/expressir/express/formatters/statements_formatter.rb +190 -188
- data/lib/expressir/express/formatters/supertype_expressions_formatter.rb +41 -39
- data/lib/expressir/express/formatters.rb +22 -0
- data/lib/expressir/express/parser.rb +40 -41
- data/lib/expressir/express/pretty_formatter.rb +68 -47
- data/lib/expressir/express/remark_attacher.rb +210 -147
- data/lib/expressir/express/streaming_builder.rb +0 -3
- data/lib/expressir/express/transformer/remark_handling.rb +1 -2
- data/lib/expressir/express.rb +29 -0
- data/lib/expressir/manifest/resolver.rb +0 -3
- data/lib/expressir/manifest/validator.rb +0 -3
- data/lib/expressir/manifest.rb +6 -0
- data/lib/expressir/model/cache.rb +1 -1
- data/lib/expressir/model/concerns.rb +19 -0
- data/lib/expressir/model/data_types/aggregate.rb +1 -1
- data/lib/expressir/model/data_types/array.rb +1 -1
- data/lib/expressir/model/data_types/bag.rb +1 -1
- data/lib/expressir/model/data_types/binary.rb +1 -1
- data/lib/expressir/model/data_types/boolean.rb +1 -1
- data/lib/expressir/model/data_types/enumeration.rb +1 -1
- data/lib/expressir/model/data_types/enumeration_item.rb +1 -1
- data/lib/expressir/model/data_types/generic.rb +1 -1
- data/lib/expressir/model/data_types/generic_entity.rb +1 -1
- data/lib/expressir/model/data_types/integer.rb +1 -1
- data/lib/expressir/model/data_types/list.rb +1 -1
- data/lib/expressir/model/data_types/logical.rb +1 -1
- data/lib/expressir/model/data_types/number.rb +1 -1
- data/lib/expressir/model/data_types/real.rb +1 -1
- data/lib/expressir/model/data_types/select.rb +1 -1
- data/lib/expressir/model/data_types/set.rb +1 -1
- data/lib/expressir/model/data_types/string.rb +1 -1
- data/lib/expressir/model/data_types.rb +25 -0
- data/lib/expressir/model/declarations/attribute.rb +1 -1
- data/lib/expressir/model/declarations/constant.rb +1 -1
- data/lib/expressir/model/declarations/derived_attribute.rb +1 -1
- data/lib/expressir/model/declarations/entity.rb +4 -1
- data/lib/expressir/model/declarations/function.rb +3 -1
- data/lib/expressir/model/declarations/informal_proposition_rule.rb +2 -1
- data/lib/expressir/model/declarations/interface.rb +1 -1
- data/lib/expressir/model/declarations/interface_item.rb +1 -1
- data/lib/expressir/model/declarations/interfaced_item.rb +1 -1
- data/lib/expressir/model/declarations/inverse_attribute.rb +1 -1
- data/lib/expressir/model/declarations/parameter.rb +1 -1
- data/lib/expressir/model/declarations/procedure.rb +3 -1
- data/lib/expressir/model/declarations/remark_item.rb +1 -1
- data/lib/expressir/model/declarations/rule.rb +4 -1
- data/lib/expressir/model/declarations/schema.rb +2 -1
- data/lib/expressir/model/declarations/schema_version.rb +1 -1
- data/lib/expressir/model/declarations/schema_version_item.rb +1 -1
- data/lib/expressir/model/declarations/subtype_constraint.rb +1 -1
- data/lib/expressir/model/declarations/type.rb +4 -1
- data/lib/expressir/model/declarations/unique_rule.rb +1 -1
- data/lib/expressir/model/declarations/variable.rb +1 -1
- data/lib/expressir/model/declarations/where_rule.rb +1 -1
- data/lib/expressir/model/declarations.rb +31 -0
- data/lib/expressir/model/dependency_resolver.rb +0 -2
- data/lib/expressir/model/exp_file.rb +38 -0
- data/lib/expressir/model/expressions/aggregate_initializer.rb +1 -1
- data/lib/expressir/model/expressions/aggregate_initializer_item.rb +1 -1
- data/lib/expressir/model/expressions/binary_expression.rb +1 -1
- data/lib/expressir/model/expressions/entity_constructor.rb +1 -1
- data/lib/expressir/model/expressions/function_call.rb +1 -1
- data/lib/expressir/model/expressions/interval.rb +1 -1
- data/lib/expressir/model/expressions/query_expression.rb +1 -1
- data/lib/expressir/model/expressions/unary_expression.rb +1 -1
- data/lib/expressir/model/expressions.rb +18 -0
- data/lib/expressir/model/identifier.rb +5 -1
- data/lib/expressir/model/indexes.rb +11 -0
- data/lib/expressir/model/literals/binary.rb +1 -1
- data/lib/expressir/model/literals/integer.rb +1 -1
- data/lib/expressir/model/literals/logical.rb +1 -1
- data/lib/expressir/model/literals/real.rb +1 -1
- data/lib/expressir/model/literals/string.rb +1 -1
- data/lib/expressir/model/literals.rb +13 -0
- data/lib/expressir/model/model_element.rb +7 -15
- data/lib/expressir/model/references/attribute_reference.rb +1 -1
- data/lib/expressir/model/references/group_reference.rb +1 -1
- data/lib/expressir/model/references/index_reference.rb +1 -1
- data/lib/expressir/model/references/simple_reference.rb +1 -1
- data/lib/expressir/model/references.rb +12 -0
- data/lib/expressir/model/remark_info.rb +1 -7
- data/lib/expressir/model/repository.rb +72 -36
- data/lib/expressir/model/repository_validator.rb +0 -2
- data/lib/expressir/model/search_engine.rb +6 -30
- data/lib/expressir/model/statements/alias.rb +1 -1
- data/lib/expressir/model/statements/assignment.rb +1 -1
- data/lib/expressir/model/statements/case.rb +1 -1
- data/lib/expressir/model/statements/case_action.rb +1 -1
- data/lib/expressir/model/statements/compound.rb +1 -1
- data/lib/expressir/model/statements/escape.rb +1 -1
- data/lib/expressir/model/statements/if.rb +1 -1
- data/lib/expressir/model/statements/null.rb +1 -1
- data/lib/expressir/model/statements/procedure_call.rb +1 -1
- data/lib/expressir/model/statements/repeat.rb +1 -1
- data/lib/expressir/model/statements/return.rb +1 -1
- data/lib/expressir/model/statements/skip.rb +1 -1
- data/lib/expressir/model/statements.rb +20 -0
- data/lib/expressir/model/supertype_expressions/binary_supertype_expression.rb +1 -1
- data/lib/expressir/model/supertype_expressions/oneof_supertype_expression.rb +1 -1
- data/lib/expressir/model/supertype_expressions.rb +12 -0
- data/lib/expressir/model.rb +28 -4
- data/lib/expressir/package/builder.rb +33 -4
- data/lib/expressir/package/metadata.rb +0 -1
- data/lib/expressir/package/reader.rb +0 -1
- data/lib/expressir/package.rb +8 -0
- data/lib/expressir/schema_manifest.rb +5 -6
- data/lib/expressir/schema_manifest_entry.rb +3 -4
- data/lib/expressir/transformer.rb +7 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +23 -173
- metadata +64 -9
- data/lib/expressir/express/builders/token_builder.rb +0 -15
|
@@ -13,89 +13,13 @@ module Expressir
|
|
|
13
13
|
# 2. Proximity-based matching for simple tags
|
|
14
14
|
# 3. NOT creating spurious schema-level items for ambiguous tags
|
|
15
15
|
class RemarkAttacher
|
|
16
|
-
# Types that support informal propositions
|
|
17
|
-
INFORMAL_PROPOSITION_TYPES = [
|
|
18
|
-
Model::Declarations::Entity,
|
|
19
|
-
Model::Declarations::Rule,
|
|
20
|
-
Model::Declarations::Type,
|
|
21
|
-
Model::Declarations::InformalPropositionRule,
|
|
22
|
-
].freeze
|
|
23
|
-
|
|
24
|
-
# Types that support remark items (have remark_items attribute)
|
|
25
|
-
# These are types where we can create RemarkItem children
|
|
26
|
-
REMARK_ITEM_TYPES = [
|
|
27
|
-
Model::Declarations::Schema,
|
|
28
|
-
Model::Declarations::Entity,
|
|
29
|
-
Model::Declarations::Type,
|
|
30
|
-
Model::Declarations::Rule,
|
|
31
|
-
Model::Declarations::Function,
|
|
32
|
-
Model::Declarations::Procedure,
|
|
33
|
-
Model::Declarations::InformalPropositionRule,
|
|
34
|
-
Model::Declarations::WhereRule,
|
|
35
|
-
Model::Declarations::UniqueRule,
|
|
36
|
-
# Attribute types (all include Identifier which provides remark_items)
|
|
37
|
-
Model::Declarations::Attribute,
|
|
38
|
-
Model::Declarations::DerivedAttribute,
|
|
39
|
-
Model::Declarations::InverseAttribute,
|
|
40
|
-
].freeze
|
|
41
|
-
|
|
42
|
-
# Types that support where rules
|
|
43
|
-
WHERE_RULE_TYPES = [
|
|
44
|
-
Model::Declarations::Entity,
|
|
45
|
-
Model::Declarations::Type,
|
|
46
|
-
Model::Declarations::Rule,
|
|
47
|
-
Model::Declarations::Function,
|
|
48
|
-
Model::Declarations::Procedure,
|
|
49
|
-
].freeze
|
|
50
|
-
|
|
51
|
-
# Scope container types (can contain other declarations)
|
|
52
|
-
SCOPE_CONTAINER_TYPES = [
|
|
53
|
-
Model::Declarations::Schema,
|
|
54
|
-
Model::Declarations::Function,
|
|
55
|
-
Model::Declarations::Procedure,
|
|
56
|
-
Model::Declarations::Rule,
|
|
57
|
-
Model::Declarations::Entity,
|
|
58
|
-
Model::Declarations::Type,
|
|
59
|
-
].freeze
|
|
60
|
-
|
|
61
|
-
# Types that support remarks (have Identifier module or define remarks directly)
|
|
62
|
-
REMARKS_SUPPORT_TYPES = [
|
|
63
|
-
# Declaration types with Identifier module
|
|
64
|
-
Model::Declarations::Schema,
|
|
65
|
-
Model::Declarations::Entity,
|
|
66
|
-
Model::Declarations::Type,
|
|
67
|
-
Model::Declarations::Function,
|
|
68
|
-
Model::Declarations::Procedure,
|
|
69
|
-
Model::Declarations::Rule,
|
|
70
|
-
Model::Declarations::Constant,
|
|
71
|
-
Model::Declarations::Attribute,
|
|
72
|
-
Model::Declarations::InverseAttribute,
|
|
73
|
-
Model::Declarations::DerivedAttribute,
|
|
74
|
-
Model::Declarations::WhereRule,
|
|
75
|
-
Model::Declarations::UniqueRule,
|
|
76
|
-
Model::Declarations::InformalPropositionRule,
|
|
77
|
-
Model::Declarations::SubtypeConstraint,
|
|
78
|
-
Model::Declarations::Parameter,
|
|
79
|
-
Model::Declarations::Variable,
|
|
80
|
-
# Statement types with Identifier module
|
|
81
|
-
Model::Statements::Alias,
|
|
82
|
-
Model::Statements::Repeat,
|
|
83
|
-
# Expression types with Identifier module
|
|
84
|
-
Model::Expressions::QueryExpression,
|
|
85
|
-
# Data types with Identifier module
|
|
86
|
-
Model::DataTypes::Aggregate,
|
|
87
|
-
Model::DataTypes::EnumerationItem,
|
|
88
|
-
Model::DataTypes::Generic,
|
|
89
|
-
Model::DataTypes::GenericEntity,
|
|
90
|
-
# Types with remarks attribute defined directly (not via Identifier)
|
|
91
|
-
Model::Declarations::RemarkItem,
|
|
92
|
-
].freeze
|
|
93
|
-
|
|
94
16
|
def initialize(source)
|
|
95
17
|
@source = source
|
|
96
18
|
@attached_spans = Set.new
|
|
97
19
|
@line_cache = {}
|
|
98
20
|
@model = nil
|
|
21
|
+
@source_lines = nil # cached @source.lines
|
|
22
|
+
@scope_map = nil # cached scope at each line number
|
|
99
23
|
end
|
|
100
24
|
|
|
101
25
|
def attach(model)
|
|
@@ -190,51 +114,63 @@ module Expressir
|
|
|
190
114
|
end
|
|
191
115
|
end
|
|
192
116
|
|
|
117
|
+
def source_lines
|
|
118
|
+
@source_lines ||= @source.lines
|
|
119
|
+
end
|
|
120
|
+
|
|
193
121
|
def get_line_number(position)
|
|
194
122
|
return 1 if position.nil? || position.zero?
|
|
195
123
|
|
|
196
124
|
@line_cache[position] ||= @source.byteslice(0...position).count("\n") + 1
|
|
197
125
|
end
|
|
198
126
|
|
|
127
|
+
alias get_line_number_from_offset get_line_number
|
|
128
|
+
|
|
199
129
|
def attach_tagged_remarks(model, remarks)
|
|
200
|
-
|
|
130
|
+
tagged = remarks.select { |r| r[:tag] }
|
|
131
|
+
return if tagged.empty?
|
|
201
132
|
|
|
202
|
-
|
|
133
|
+
@model = model
|
|
134
|
+
|
|
135
|
+
# Build scope map ONCE: O(file_lines) scan instead of O(n*file_lines) for n remarks
|
|
136
|
+
# This is the key optimization that makes scope lookup O(1) per remark
|
|
137
|
+
@scope_map ||= build_scope_map
|
|
138
|
+
|
|
139
|
+
# Collect nodes with positions for position-based fallback
|
|
203
140
|
nodes_with_positions = []
|
|
204
141
|
collect_nodes_with_positions(model, nodes_with_positions)
|
|
205
142
|
# Use stable sort to ensure deterministic ordering across Ruby versions
|
|
206
|
-
# When positions are equal, preserve original order using index as tie-breaker
|
|
207
143
|
nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
208
144
|
|
|
209
|
-
|
|
210
|
-
r[:tag]
|
|
211
|
-
end.sort_by { |r| r[:position] }.each do |remark|
|
|
145
|
+
tagged.sort_by { |r| r[:position] }.each do |remark|
|
|
212
146
|
next if @attached_spans.include?(remark[:position])
|
|
213
147
|
|
|
214
148
|
tag = remark[:tag]
|
|
215
149
|
target = nil
|
|
216
150
|
|
|
151
|
+
# Find containing scope using pre-computed scope map (O(1))
|
|
152
|
+
# Falls back to position-based lookup if scope map doesn't have the line
|
|
153
|
+
containing_scope = find_containing_scope_by_name(remark[:line])
|
|
154
|
+
containing_scope ||= find_containing_scope_position(remark[:line],
|
|
155
|
+
nodes_with_positions)
|
|
156
|
+
|
|
217
157
|
# Check if this is an informal proposition tag (IP\d+)
|
|
218
158
|
if tag.match?(/^IP\d+$/)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
target = create_or_find_informal_proposition(
|
|
159
|
+
scope = containing_scope
|
|
160
|
+
if scope.nil?
|
|
161
|
+
scope = find_scope_by_source_text(remark[:line])
|
|
162
|
+
end
|
|
163
|
+
if scope && supports_informal_propositions?(scope)
|
|
164
|
+
target = create_or_find_informal_proposition(scope, tag)
|
|
225
165
|
end
|
|
226
166
|
end
|
|
227
167
|
|
|
228
168
|
# Standard path-based lookup
|
|
229
169
|
if target.nil?
|
|
230
|
-
# Find containing scope for scope-aware path resolution
|
|
231
|
-
containing_scope = find_containing_scope(remark[:line],
|
|
232
|
-
nodes_with_positions)
|
|
233
|
-
|
|
234
170
|
# Handle prefixed tags like wr:WR1, ip:IP1, ur:UR1
|
|
235
171
|
if tag.include?(":") && !tag.include?(".")
|
|
236
172
|
target = handle_prefixed_tag(tag, containing_scope, model,
|
|
237
|
-
|
|
173
|
+
get_schema_ids(model))
|
|
238
174
|
end
|
|
239
175
|
|
|
240
176
|
# Strategy 1: Try exact path lookup
|
|
@@ -255,6 +191,7 @@ module Expressir
|
|
|
255
191
|
|
|
256
192
|
# Then try schema prefix
|
|
257
193
|
if target.nil?
|
|
194
|
+
schema_ids = get_schema_ids(model)
|
|
258
195
|
schema_ids.each do |schema_id|
|
|
259
196
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
260
197
|
break if target
|
|
@@ -275,8 +212,8 @@ module Expressir
|
|
|
275
212
|
end
|
|
276
213
|
|
|
277
214
|
# Only fall back to schema prefix if NOT inside a function/rule/procedure
|
|
278
|
-
# This prevents remarks inside scopes from attaching to schema-level items
|
|
279
215
|
if target.nil? && !function_rule_procedure?(containing_scope)
|
|
216
|
+
schema_ids = get_schema_ids(model)
|
|
280
217
|
schema_ids.each do |schema_id|
|
|
281
218
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
282
219
|
break if target
|
|
@@ -284,6 +221,7 @@ module Expressir
|
|
|
284
221
|
end
|
|
285
222
|
else
|
|
286
223
|
# No containing scope, try with schema prefix
|
|
224
|
+
schema_ids = get_schema_ids(model)
|
|
287
225
|
schema_ids.each do |schema_id|
|
|
288
226
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
289
227
|
break if target
|
|
@@ -299,19 +237,22 @@ module Expressir
|
|
|
299
237
|
if scope_path
|
|
300
238
|
full_path = "#{scope_path}.#{tag}"
|
|
301
239
|
target = create_implicit_remark_item(model, full_path,
|
|
302
|
-
|
|
240
|
+
get_schema_ids(model))
|
|
303
241
|
end
|
|
304
242
|
end
|
|
305
243
|
# Fall back to schema prefix
|
|
306
244
|
if target.nil?
|
|
307
|
-
target = create_implicit_remark_item(model, tag,
|
|
245
|
+
target = create_implicit_remark_item(model, tag, get_schema_ids(model))
|
|
308
246
|
end
|
|
309
247
|
end
|
|
310
248
|
|
|
311
249
|
# Strategy 4: For simple tags at schema level, create implicit item
|
|
312
|
-
if target.nil? && !tag.include?(".")
|
|
313
|
-
|
|
314
|
-
|
|
250
|
+
if target.nil? && !tag.include?(".")
|
|
251
|
+
schema_ids = get_schema_ids(model)
|
|
252
|
+
if schema_ids.any?
|
|
253
|
+
target = create_implicit_remark_item_at_schema(model, tag,
|
|
254
|
+
schema_ids.first)
|
|
255
|
+
end
|
|
315
256
|
end
|
|
316
257
|
end
|
|
317
258
|
|
|
@@ -323,6 +264,112 @@ module Expressir
|
|
|
323
264
|
end
|
|
324
265
|
end
|
|
325
266
|
|
|
267
|
+
# Position-based fallback for finding containing scope.
|
|
268
|
+
# Used when scope map lookup returns nil (e.g., for remarks at lines
|
|
269
|
+
# outside any declared scope's end_line, or for non-scope-containers).
|
|
270
|
+
def find_containing_scope_position(remark_line, nodes_with_positions)
|
|
271
|
+
containing_nodes = nodes_with_positions.select do |n|
|
|
272
|
+
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
273
|
+
!repository?(n[:node]) && !cache?(n[:node])
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
containing_nodes.reverse_each do |n|
|
|
277
|
+
node = n[:node]
|
|
278
|
+
return node if node.is_a?(Model::ScopeContainer)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
nil
|
|
282
|
+
end
|
|
283
|
+
# Done once per RemarkAttacher instance (O(file_lines)).
|
|
284
|
+
# Each find_containing_scope call then becomes O(1).
|
|
285
|
+
def build_scope_map
|
|
286
|
+
lines = source_lines
|
|
287
|
+
scope_map = {}
|
|
288
|
+
return scope_map if lines.empty?
|
|
289
|
+
|
|
290
|
+
# Track nested scopes by scanning all lines once
|
|
291
|
+
scope_stack = [] # array of {type:, name:, line:}
|
|
292
|
+
|
|
293
|
+
lines.each_with_index do |line, idx|
|
|
294
|
+
line_num = idx + 1
|
|
295
|
+
|
|
296
|
+
# Check for START keywords first
|
|
297
|
+
if line =~ /^\s*SCHEMA\s+(\w+)/i
|
|
298
|
+
scope_stack << { type: :schema, name: $1, line: line_num }
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
if line =~ /^\s*FUNCTION\s+(\w+)/i
|
|
302
|
+
scope_stack << { type: :function, name: $1, line: line_num }
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
if line =~ /^\s*PROCEDURE\s+(\w+)/i
|
|
306
|
+
scope_stack << { type: :procedure, name: $1, line: line_num }
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if line =~ /^\s*RULE\s+(\w+)/i
|
|
310
|
+
scope_stack << { type: :rule, name: $1, line: line_num }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
if line =~ /^\s*ENTITY\s+(\w+)/i
|
|
314
|
+
scope_stack << { type: :entity, name: $1, line: line_num }
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if line =~ /^\s*TYPE\s+(\w+)/i
|
|
318
|
+
scope_stack << { type: :type, name: $1, line: line_num }
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Check for END keywords (inline closures on same line handled here)
|
|
322
|
+
if (line =~ /END_TYPE/i) && (scope_stack.last&.dig(:type) == :type)
|
|
323
|
+
scope_stack.pop
|
|
324
|
+
end
|
|
325
|
+
if (line =~ /END_FUNCTION/i) && (scope_stack.last&.dig(:type) == :function)
|
|
326
|
+
scope_stack.pop
|
|
327
|
+
end
|
|
328
|
+
if (line =~ /END_PROCEDURE/i) && (scope_stack.last&.dig(:type) == :procedure)
|
|
329
|
+
scope_stack.pop
|
|
330
|
+
end
|
|
331
|
+
if (line =~ /END_RULE/i) && (scope_stack.last&.dig(:type) == :rule)
|
|
332
|
+
scope_stack.pop
|
|
333
|
+
end
|
|
334
|
+
if (line =~ /END_ENTITY/i) && (scope_stack.last&.dig(:type) == :entity)
|
|
335
|
+
scope_stack.pop
|
|
336
|
+
end
|
|
337
|
+
if (line =~ /END_SCHEMA/i) && (scope_stack.last&.dig(:type) == :schema)
|
|
338
|
+
scope_stack.pop
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Record the innermost scope for this line
|
|
342
|
+
scope_map[line_num] = scope_stack.last&.dig(:name)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
scope_map
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# O(1) scope lookup using pre-computed scope map
|
|
349
|
+
def find_containing_scope_by_name(remark_line)
|
|
350
|
+
return nil unless @scope_map
|
|
351
|
+
|
|
352
|
+
scope_name = @scope_map[remark_line]
|
|
353
|
+
return nil unless scope_name
|
|
354
|
+
|
|
355
|
+
# Find the model node for this scope
|
|
356
|
+
return nil unless @model
|
|
357
|
+
|
|
358
|
+
@model.schemas.each do |schema|
|
|
359
|
+
return schema if schema.id == scope_name
|
|
360
|
+
|
|
361
|
+
%i[functions procedures rules entities types].each do |decl_type|
|
|
362
|
+
collection = schema.send(decl_type)
|
|
363
|
+
next unless collection.is_a?(Array)
|
|
364
|
+
|
|
365
|
+
found = collection.find { |n| n.id == scope_name }
|
|
366
|
+
return found if found
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
nil
|
|
371
|
+
end
|
|
372
|
+
|
|
326
373
|
def find_node_in_scope(scope, tag)
|
|
327
374
|
return nil unless scope
|
|
328
375
|
|
|
@@ -465,7 +512,7 @@ module Expressir
|
|
|
465
512
|
return nil unless where_rules&.any?
|
|
466
513
|
|
|
467
514
|
# Search source text for WHERE clause containing this remark
|
|
468
|
-
lines =
|
|
515
|
+
lines = source_lines
|
|
469
516
|
|
|
470
517
|
where_rules.each do |wr|
|
|
471
518
|
next unless wr.id
|
|
@@ -502,30 +549,16 @@ module Expressir
|
|
|
502
549
|
end
|
|
503
550
|
|
|
504
551
|
def find_containing_scope(remark_line, nodes_with_positions)
|
|
505
|
-
# First try
|
|
506
|
-
|
|
507
|
-
return
|
|
552
|
+
# First try scope map (O(1) once built)
|
|
553
|
+
scope = find_containing_scope_by_name(remark_line)
|
|
554
|
+
return scope if scope
|
|
508
555
|
|
|
509
556
|
# Fallback to position-based detection
|
|
510
|
-
|
|
511
|
-
containing_nodes = nodes_with_positions.select do |n|
|
|
512
|
-
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
513
|
-
!repository?(n[:node]) && !cache?(n[:node])
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Return the innermost scope container (function, procedure, rule, entity, type)
|
|
517
|
-
containing_nodes.reverse_each do |n|
|
|
518
|
-
node = n[:node]
|
|
519
|
-
SCOPE_CONTAINER_TYPES.each do |scope_class|
|
|
520
|
-
return node if node.is_a?(scope_class)
|
|
521
|
-
end
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
nil
|
|
557
|
+
find_containing_scope_position(remark_line, nodes_with_positions)
|
|
525
558
|
end
|
|
526
559
|
|
|
527
560
|
def find_scope_by_text_search(remark_line)
|
|
528
|
-
lines =
|
|
561
|
+
lines = source_lines
|
|
529
562
|
return nil if remark_line < 1 || remark_line > lines.length
|
|
530
563
|
|
|
531
564
|
# Track nested scopes by searching backwards from remark_line
|
|
@@ -641,23 +674,19 @@ module Expressir
|
|
|
641
674
|
end
|
|
642
675
|
|
|
643
676
|
def find_containing_scope_for_ip(remark_line, nodes_with_positions)
|
|
644
|
-
# First try
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return text_based_scope
|
|
677
|
+
# First try scope map (O(1))
|
|
678
|
+
scope = find_containing_scope_by_name(remark_line)
|
|
679
|
+
if scope && supports_informal_propositions?(scope)
|
|
680
|
+
return scope
|
|
649
681
|
end
|
|
650
682
|
|
|
651
683
|
# Fallback to position-based detection
|
|
652
|
-
# Find nodes that contain this remark line
|
|
653
|
-
# Exclude Repository and Cache as they are not semantic scopes
|
|
654
684
|
containing_nodes = nodes_with_positions.select do |n|
|
|
655
685
|
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
656
686
|
!repository?(n[:node]) && !cache?(n[:node])
|
|
657
687
|
end
|
|
658
688
|
|
|
659
689
|
# Find the innermost node that supports informal propositions
|
|
660
|
-
# Priority: Entity, Rule, Type, Schema
|
|
661
690
|
if containing_nodes.any?
|
|
662
691
|
containing_nodes.reverse_each do |n|
|
|
663
692
|
node = n[:node]
|
|
@@ -676,7 +705,7 @@ module Expressir
|
|
|
676
705
|
|
|
677
706
|
def find_scope_by_source_text(remark_line)
|
|
678
707
|
# Search backwards from remark_line for containing scope
|
|
679
|
-
lines =
|
|
708
|
+
lines = source_lines
|
|
680
709
|
|
|
681
710
|
# Find the entity/type/rule that contains this line
|
|
682
711
|
entity_start = nil
|
|
@@ -756,7 +785,10 @@ module Expressir
|
|
|
756
785
|
end
|
|
757
786
|
|
|
758
787
|
def find_by_exact_path(model, path)
|
|
759
|
-
return nil unless path
|
|
788
|
+
return nil unless path
|
|
789
|
+
|
|
790
|
+
# Only Repository and ExpFile support path-based find
|
|
791
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
760
792
|
|
|
761
793
|
# Try original path
|
|
762
794
|
result = safe_find(model, path)
|
|
@@ -768,7 +800,8 @@ module Expressir
|
|
|
768
800
|
end
|
|
769
801
|
|
|
770
802
|
def create_implicit_remark_item_at_schema(model, item_id, schema_id)
|
|
771
|
-
|
|
803
|
+
# Only Repository and ExpFile support schema lookup
|
|
804
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
772
805
|
|
|
773
806
|
schema = safe_find(model, schema_id)
|
|
774
807
|
return nil unless schema.is_a?(Model::Declarations::Schema)
|
|
@@ -789,7 +822,7 @@ module Expressir
|
|
|
789
822
|
end
|
|
790
823
|
|
|
791
824
|
def create_implicit_remark_item(model, path, schema_ids = [])
|
|
792
|
-
return nil unless repository?(model)
|
|
825
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
793
826
|
|
|
794
827
|
# Normalize path (handle "ip:IP1" format)
|
|
795
828
|
clean_path = normalize_path(path)
|
|
@@ -913,7 +946,7 @@ module Expressir
|
|
|
913
946
|
end
|
|
914
947
|
|
|
915
948
|
def get_line_content(line_num)
|
|
916
|
-
lines =
|
|
949
|
+
lines = source_lines
|
|
917
950
|
return "" if line_num < 1 || line_num > lines.length
|
|
918
951
|
|
|
919
952
|
lines[line_num - 1]
|
|
@@ -952,20 +985,29 @@ module Expressir
|
|
|
952
985
|
|
|
953
986
|
# Only add remarks to nodes that support them
|
|
954
987
|
if supports_remarks?(node)
|
|
955
|
-
|
|
956
|
-
node.remarks
|
|
988
|
+
# Always add to remarks attribute (for types that have it)
|
|
989
|
+
if node.respond_to?(:remarks) && node.respond_to?(:remarks=)
|
|
990
|
+
node.remarks ||= []
|
|
991
|
+
node.remarks << text
|
|
992
|
+
end
|
|
957
993
|
|
|
958
994
|
if tag.nil?
|
|
959
|
-
|
|
960
|
-
|
|
995
|
+
# Untagged remark: store in untagged_remarks
|
|
996
|
+
remark_info = Model::RemarkInfo.new(text: text, format: format)
|
|
961
997
|
node.untagged_remarks ||= []
|
|
962
998
|
node.untagged_remarks << remark_info
|
|
963
999
|
end
|
|
964
1000
|
end
|
|
965
1001
|
end
|
|
966
1002
|
|
|
1003
|
+
# All ModelElement subclasses have untagged_remarks from ModelElement
|
|
967
1004
|
def supports_remarks?(obj)
|
|
968
|
-
|
|
1005
|
+
obj.is_a?(Model::ModelElement)
|
|
1006
|
+
end
|
|
1007
|
+
|
|
1008
|
+
# Types that include HasRemarkItems can have remark_items
|
|
1009
|
+
def supports_remark_items?(obj)
|
|
1010
|
+
obj.is_a?(Model::HasRemarkItems)
|
|
969
1011
|
end
|
|
970
1012
|
|
|
971
1013
|
def collect_nodes_with_positions(node, result, visited = Set.new)
|
|
@@ -985,7 +1027,8 @@ module Expressir
|
|
|
985
1027
|
# For container nodes, use the maximum end_line from children
|
|
986
1028
|
# This is needed because source.length only covers the declaration, not the body
|
|
987
1029
|
children_end_line = calculate_children_end_line(node)
|
|
988
|
-
end_line = [source_end_line,
|
|
1030
|
+
end_line = [source_end_line,
|
|
1031
|
+
children_end_line].compact.max || source_end_line
|
|
989
1032
|
|
|
990
1033
|
result << {
|
|
991
1034
|
node: node,
|
|
@@ -1075,13 +1118,23 @@ module Expressir
|
|
|
1075
1118
|
# Find the node that CONTAINS this remark line
|
|
1076
1119
|
# This handles preamble remarks and embedded remarks
|
|
1077
1120
|
# Exclude Repository and Cache as they are not semantic scopes
|
|
1121
|
+
# But include ExpFile for file-level preamble remarks
|
|
1078
1122
|
containing = nodes.select do |n|
|
|
1079
1123
|
n[:line] && n[:end_line] && n[:line] <= remark_line && n[:end_line] >= remark_line &&
|
|
1080
1124
|
!repository?(n[:node]) && !cache?(n[:node])
|
|
1081
1125
|
end
|
|
1082
1126
|
|
|
1083
1127
|
if containing.any?
|
|
1084
|
-
#
|
|
1128
|
+
# Prefer ExpFile for preamble remarks (before first schema)
|
|
1129
|
+
# Otherwise return the most specific (smallest) containing node
|
|
1130
|
+
exp_file_node = containing.find { |n| exp_file?(n[:node]) }
|
|
1131
|
+
# If this is a preamble remark (before first schema line), use ExpFile
|
|
1132
|
+
if exp_file_node
|
|
1133
|
+
first_schema_line = exp_file_node[:node].schemas&.first&.source_offset
|
|
1134
|
+
if first_schema_line && remark_line < get_line_number(first_schema_line)
|
|
1135
|
+
return exp_file_node[:node]
|
|
1136
|
+
end
|
|
1137
|
+
end
|
|
1085
1138
|
# Sort by span size and return the smallest
|
|
1086
1139
|
containing.min_by { |n| n[:end_line] - n[:line] }[:node]
|
|
1087
1140
|
else
|
|
@@ -1096,24 +1149,34 @@ module Expressir
|
|
|
1096
1149
|
|
|
1097
1150
|
# Type checking helper methods
|
|
1098
1151
|
|
|
1152
|
+
def get_schema_ids(model)
|
|
1153
|
+
if repository?(model)
|
|
1154
|
+
model.schemas.filter_map(&:id)
|
|
1155
|
+
elsif exp_file?(model)
|
|
1156
|
+
model.schemas.filter_map(&:id)
|
|
1157
|
+
else
|
|
1158
|
+
[]
|
|
1159
|
+
end
|
|
1160
|
+
end
|
|
1161
|
+
|
|
1099
1162
|
def repository?(obj)
|
|
1100
1163
|
obj.is_a?(Model::Repository)
|
|
1101
1164
|
end
|
|
1102
1165
|
|
|
1166
|
+
def exp_file?(obj)
|
|
1167
|
+
obj.is_a?(Model::ExpFile)
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1103
1170
|
def cache?(obj)
|
|
1104
1171
|
obj.is_a?(Model::Cache)
|
|
1105
1172
|
end
|
|
1106
1173
|
|
|
1107
1174
|
def supports_informal_propositions?(obj)
|
|
1108
|
-
|
|
1109
|
-
end
|
|
1110
|
-
|
|
1111
|
-
def supports_remark_items?(obj)
|
|
1112
|
-
REMARK_ITEM_TYPES.any? { |t| obj.is_a?(t) }
|
|
1175
|
+
obj.is_a?(Model::HasInformalPropositions)
|
|
1113
1176
|
end
|
|
1114
1177
|
|
|
1115
1178
|
def supports_where_rules?(obj)
|
|
1116
|
-
|
|
1179
|
+
obj.is_a?(Model::HasWhereRules)
|
|
1117
1180
|
end
|
|
1118
1181
|
|
|
1119
1182
|
# Safe accessor methods that return nil instead of NoMethodError
|
|
@@ -31,8 +31,7 @@ module Expressir
|
|
|
31
31
|
# @param remarks [Array<Hash>] The extracted remarks
|
|
32
32
|
def attach_remarks(model, remarks)
|
|
33
33
|
# Group remarks by their tag
|
|
34
|
-
tagged_remarks = remarks.
|
|
35
|
-
untagged_remarks = remarks.reject { |r| r[:tag] }
|
|
34
|
+
tagged_remarks, untagged_remarks = remarks.partition { |r| r[:tag] }
|
|
36
35
|
|
|
37
36
|
# Process tagged remarks
|
|
38
37
|
tagged_remarks.each do |remark|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
autoload :Builder, "#{__dir__}/express/builder"
|
|
6
|
+
autoload :Builders, "#{__dir__}/express/builders"
|
|
7
|
+
# autoload :BuilderRegistry, "#{__dir__}/express/builder_registry"
|
|
8
|
+
|
|
9
|
+
# Core classes (autoloaded for lazy loading)
|
|
10
|
+
autoload :Cache, "#{__dir__}/express/cache"
|
|
11
|
+
autoload :Error, "#{__dir__}/express/error"
|
|
12
|
+
autoload :Formatter, "#{__dir__}/express/formatter"
|
|
13
|
+
autoload :Formatters, "#{__dir__}/express/formatters"
|
|
14
|
+
autoload :HyperlinkFormatter, "#{__dir__}/express/hyperlink_formatter"
|
|
15
|
+
autoload :ModelVisitor, "#{__dir__}/express/model_visitor"
|
|
16
|
+
autoload :Parser, "#{__dir__}/express/parser"
|
|
17
|
+
autoload :PrettyFormatter, "#{__dir__}/express/pretty_formatter"
|
|
18
|
+
autoload :RemarkAttacher, "#{__dir__}/express/remark_attacher"
|
|
19
|
+
autoload :ResolveReferencesModelVisitor,
|
|
20
|
+
"#{__dir__}/express/resolve_references_model_visitor"
|
|
21
|
+
autoload :SchemaHeadFormatter, "#{__dir__}/express/schema_head_formatter"
|
|
22
|
+
autoload :StreamingBuilder, "#{__dir__}/express/streaming_builder"
|
|
23
|
+
|
|
24
|
+
autoload :Transformer, "#{__dir__}/express/transformer"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Eagerly load builders (they register themselves at load time)
|
|
29
|
+
require_relative "express/builder_registry"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../model/dependency_resolver"
|
|
4
|
-
|
|
5
3
|
module Expressir
|
|
6
4
|
module Manifest
|
|
7
5
|
# Resolves schema paths in manifests using pattern-based path discovery
|
|
@@ -131,7 +129,6 @@ module Expressir
|
|
|
131
129
|
# @param schema_path [String] Path to the schema file
|
|
132
130
|
# @return [String] The schema name declared in the file
|
|
133
131
|
def extract_schema_name(schema_path)
|
|
134
|
-
require_relative "../express/parser"
|
|
135
132
|
repo = Expressir::Express::Parser.from_file(schema_path)
|
|
136
133
|
schema = repo.schemas.first
|
|
137
134
|
schema&.id || File.basename(schema_path, ".*")
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../model/dependency_resolver"
|
|
4
|
-
|
|
5
3
|
module Expressir
|
|
6
4
|
module Manifest
|
|
7
5
|
# Validates schema manifests for completeness and integrity
|
|
@@ -159,7 +157,6 @@ module Expressir
|
|
|
159
157
|
# @param schema_path [String] Path to the schema file
|
|
160
158
|
# @return [String] The schema name declared in the file
|
|
161
159
|
def extract_schema_name(schema_path)
|
|
162
|
-
require_relative "../express/parser"
|
|
163
160
|
repo = Expressir::Express::Parser.from_file(schema_path)
|
|
164
161
|
schema = repo.schemas.first
|
|
165
162
|
schema&.id || File.basename(schema_path, ".*")
|
|
@@ -5,7 +5,7 @@ module Expressir
|
|
|
5
5
|
attribute :version, :string
|
|
6
6
|
attribute :root_path, :string
|
|
7
7
|
attribute :content, ModelElement
|
|
8
|
-
attribute :_class, :string, default: -> {
|
|
8
|
+
attribute :_class, :string, default: -> { self.class.name }
|
|
9
9
|
|
|
10
10
|
key_value do
|
|
11
11
|
map "_class", to: :_class, render_default: true
|