expressir 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +681 -78
- data/Gemfile +4 -1
- data/README.adoc +63 -26
- data/benchmark/srl_benchmark.rb +399 -0
- data/benchmark/srl_native_benchmark.rb +146 -0
- data/benchmark/srl_ruby_benchmark.rb +132 -0
- data/expressir.gemspec +3 -2
- data/lib/expressir/benchmark.rb +1 -1
- data/lib/expressir/changes/item_change.rb +0 -2
- data/lib/expressir/changes/mapping_change.rb +0 -2
- data/lib/expressir/changes/schema_change.rb +0 -3
- data/lib/expressir/changes/version_change.rb +0 -4
- data/lib/expressir/changes.rb +5 -6
- data/lib/expressir/cli.rb +10 -24
- data/lib/expressir/commands/changes.rb +0 -2
- data/lib/expressir/commands/changes_import_eengine.rb +2 -5
- data/lib/expressir/commands/changes_validate.rb +0 -2
- data/lib/expressir/commands/format.rb +1 -1
- data/lib/expressir/commands/manifest.rb +0 -7
- data/lib/expressir/commands/package.rb +16 -29
- data/lib/expressir/commands/validate.rb +0 -2
- data/lib/expressir/commands/validate_ascii.rb +0 -1
- data/lib/expressir/commands/validate_load.rb +1 -1
- 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 -5
- data/lib/expressir/eengine/changes_section.rb +1 -4
- data/lib/expressir/eengine/compare_report.rb +1 -13
- data/lib/expressir/eengine/mim_compare_report.rb +1 -5
- data/lib/expressir/eengine/modified_object.rb +1 -3
- data/lib/expressir/eengine.rb +9 -0
- data/lib/expressir/errors.rb +3 -5
- data/lib/expressir/express/builder.rb +82 -24
- 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 +5 -18
- 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 +11 -13
- data/lib/expressir/express/builders/explicit_attr_builder.rb +5 -8
- data/lib/expressir/express/builders/expression_builder.rb +25 -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 +18 -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 +174 -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 +266 -47
- data/lib/expressir/express/pretty_formatter.rb +68 -47
- data/lib/expressir/express/remark_attacher.rb +254 -162
- data/lib/expressir/express/streaming_builder.rb +0 -3
- data/lib/expressir/express/transformer/remark_handling.rb +1 -3
- 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 +39 -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 +76 -41
- data/lib/expressir/model/repository_validator.rb +0 -2
- data/lib/expressir/model/search_engine.rb +12 -35
- 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 +35 -4
- data/lib/expressir/package/metadata.rb +0 -2
- data/lib/expressir/package/reader.rb +0 -1
- data/lib/expressir/package.rb +8 -0
- data/lib/expressir/schema_manifest.rb +5 -7
- data/lib/expressir/schema_manifest_entry.rb +3 -5
- data/lib/expressir/transformer.rb +7 -0
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +23 -171
- metadata +46 -6
- data/lib/expressir/express/builders/token_builder.rb +0 -15
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
module Expressir
|
|
6
4
|
module Express
|
|
7
5
|
# Handles attaching remarks (comments) to model elements after parsing.
|
|
@@ -13,89 +11,13 @@ module Expressir
|
|
|
13
11
|
# 2. Proximity-based matching for simple tags
|
|
14
12
|
# 3. NOT creating spurious schema-level items for ambiguous tags
|
|
15
13
|
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
14
|
def initialize(source)
|
|
95
15
|
@source = source
|
|
96
16
|
@attached_spans = Set.new
|
|
97
17
|
@line_cache = {}
|
|
98
18
|
@model = nil
|
|
19
|
+
@source_lines = nil # cached @source.lines
|
|
20
|
+
@scope_map = nil # cached scope at each line number
|
|
99
21
|
end
|
|
100
22
|
|
|
101
23
|
def attach(model)
|
|
@@ -103,6 +25,15 @@ module Expressir
|
|
|
103
25
|
remarks = extract_all_remarks
|
|
104
26
|
attach_tagged_remarks(model, remarks)
|
|
105
27
|
attach_untagged_remarks(model, remarks)
|
|
28
|
+
|
|
29
|
+
# Free expensive data structures after attachment is complete.
|
|
30
|
+
# These are only needed during the attach process.
|
|
31
|
+
@source = nil
|
|
32
|
+
@source_lines = nil
|
|
33
|
+
@scope_map = nil
|
|
34
|
+
@line_cache = nil
|
|
35
|
+
@remarks_cache = nil
|
|
36
|
+
|
|
106
37
|
model
|
|
107
38
|
end
|
|
108
39
|
|
|
@@ -190,51 +121,63 @@ module Expressir
|
|
|
190
121
|
end
|
|
191
122
|
end
|
|
192
123
|
|
|
124
|
+
def source_lines
|
|
125
|
+
@source_lines ||= @source.lines
|
|
126
|
+
end
|
|
127
|
+
|
|
193
128
|
def get_line_number(position)
|
|
194
129
|
return 1 if position.nil? || position.zero?
|
|
195
130
|
|
|
196
131
|
@line_cache[position] ||= @source.byteslice(0...position).count("\n") + 1
|
|
197
132
|
end
|
|
198
133
|
|
|
134
|
+
alias get_line_number_from_offset get_line_number
|
|
135
|
+
|
|
199
136
|
def attach_tagged_remarks(model, remarks)
|
|
200
|
-
|
|
137
|
+
tagged = remarks.select { |r| r[:tag] }
|
|
138
|
+
return if tagged.empty?
|
|
139
|
+
|
|
140
|
+
@model = model
|
|
141
|
+
|
|
142
|
+
# Build scope map ONCE: O(file_lines) scan instead of O(n*file_lines) for n remarks
|
|
143
|
+
# This is the key optimization that makes scope lookup O(1) per remark
|
|
144
|
+
@scope_map ||= build_scope_map
|
|
201
145
|
|
|
202
|
-
# Collect nodes with positions for
|
|
146
|
+
# Collect nodes with positions for position-based fallback
|
|
203
147
|
nodes_with_positions = []
|
|
204
148
|
collect_nodes_with_positions(model, nodes_with_positions)
|
|
205
149
|
# Use stable sort to ensure deterministic ordering across Ruby versions
|
|
206
|
-
# When positions are equal, preserve original order using index as tie-breaker
|
|
207
150
|
nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
208
151
|
|
|
209
|
-
|
|
210
|
-
r[:tag]
|
|
211
|
-
end.sort_by { |r| r[:position] }.each do |remark|
|
|
152
|
+
tagged.sort_by { |r| r[:position] }.each do |remark|
|
|
212
153
|
next if @attached_spans.include?(remark[:position])
|
|
213
154
|
|
|
214
155
|
tag = remark[:tag]
|
|
215
156
|
target = nil
|
|
216
157
|
|
|
158
|
+
# Find containing scope using pre-computed scope map (O(1))
|
|
159
|
+
# Falls back to position-based lookup if scope map doesn't have the line
|
|
160
|
+
containing_scope = find_containing_scope_by_name(remark[:line])
|
|
161
|
+
containing_scope ||= find_containing_scope_position(remark[:line],
|
|
162
|
+
nodes_with_positions)
|
|
163
|
+
|
|
217
164
|
# Check if this is an informal proposition tag (IP\d+)
|
|
218
165
|
if tag.match?(/^IP\d+$/)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
target = create_or_find_informal_proposition(
|
|
166
|
+
scope = containing_scope
|
|
167
|
+
if scope.nil?
|
|
168
|
+
scope = find_scope_by_source_text(remark[:line])
|
|
169
|
+
end
|
|
170
|
+
if scope && supports_informal_propositions?(scope)
|
|
171
|
+
target = create_or_find_informal_proposition(scope, tag)
|
|
225
172
|
end
|
|
226
173
|
end
|
|
227
174
|
|
|
228
175
|
# Standard path-based lookup
|
|
229
176
|
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
177
|
# Handle prefixed tags like wr:WR1, ip:IP1, ur:UR1
|
|
235
178
|
if tag.include?(":") && !tag.include?(".")
|
|
236
179
|
target = handle_prefixed_tag(tag, containing_scope, model,
|
|
237
|
-
|
|
180
|
+
get_schema_ids(model))
|
|
238
181
|
end
|
|
239
182
|
|
|
240
183
|
# Strategy 1: Try exact path lookup
|
|
@@ -255,6 +198,7 @@ module Expressir
|
|
|
255
198
|
|
|
256
199
|
# Then try schema prefix
|
|
257
200
|
if target.nil?
|
|
201
|
+
schema_ids = get_schema_ids(model)
|
|
258
202
|
schema_ids.each do |schema_id|
|
|
259
203
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
260
204
|
break if target
|
|
@@ -275,8 +219,8 @@ module Expressir
|
|
|
275
219
|
end
|
|
276
220
|
|
|
277
221
|
# 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
222
|
if target.nil? && !function_rule_procedure?(containing_scope)
|
|
223
|
+
schema_ids = get_schema_ids(model)
|
|
280
224
|
schema_ids.each do |schema_id|
|
|
281
225
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
282
226
|
break if target
|
|
@@ -284,6 +228,7 @@ module Expressir
|
|
|
284
228
|
end
|
|
285
229
|
else
|
|
286
230
|
# No containing scope, try with schema prefix
|
|
231
|
+
schema_ids = get_schema_ids(model)
|
|
287
232
|
schema_ids.each do |schema_id|
|
|
288
233
|
target = find_by_exact_path(model, "#{schema_id}.#{tag}")
|
|
289
234
|
break if target
|
|
@@ -299,19 +244,23 @@ module Expressir
|
|
|
299
244
|
if scope_path
|
|
300
245
|
full_path = "#{scope_path}.#{tag}"
|
|
301
246
|
target = create_implicit_remark_item(model, full_path,
|
|
302
|
-
|
|
247
|
+
get_schema_ids(model))
|
|
303
248
|
end
|
|
304
249
|
end
|
|
305
250
|
# Fall back to schema prefix
|
|
306
251
|
if target.nil?
|
|
307
|
-
target = create_implicit_remark_item(model, tag,
|
|
252
|
+
target = create_implicit_remark_item(model, tag,
|
|
253
|
+
get_schema_ids(model))
|
|
308
254
|
end
|
|
309
255
|
end
|
|
310
256
|
|
|
311
257
|
# Strategy 4: For simple tags at schema level, create implicit item
|
|
312
|
-
if target.nil? && !tag.include?(".")
|
|
313
|
-
|
|
314
|
-
|
|
258
|
+
if target.nil? && !tag.include?(".")
|
|
259
|
+
schema_ids = get_schema_ids(model)
|
|
260
|
+
if schema_ids.any?
|
|
261
|
+
target = create_implicit_remark_item_at_schema(model, tag,
|
|
262
|
+
schema_ids.first)
|
|
263
|
+
end
|
|
315
264
|
end
|
|
316
265
|
end
|
|
317
266
|
|
|
@@ -323,6 +272,113 @@ module Expressir
|
|
|
323
272
|
end
|
|
324
273
|
end
|
|
325
274
|
|
|
275
|
+
# Position-based fallback for finding containing scope.
|
|
276
|
+
# Used when scope map lookup returns nil (e.g., for remarks at lines
|
|
277
|
+
# outside any declared scope's end_line, or for non-scope-containers).
|
|
278
|
+
def find_containing_scope_position(remark_line, nodes_with_positions)
|
|
279
|
+
containing_nodes = nodes_with_positions.select do |n|
|
|
280
|
+
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
281
|
+
!repository?(n[:node]) && !cache?(n[:node])
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
containing_nodes.reverse_each do |n|
|
|
285
|
+
node = n[:node]
|
|
286
|
+
return node if node.is_a?(Model::ScopeContainer)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
nil
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Done once per RemarkAttacher instance (O(file_lines)).
|
|
293
|
+
# Each find_containing_scope call then becomes O(1).
|
|
294
|
+
def build_scope_map
|
|
295
|
+
lines = source_lines
|
|
296
|
+
scope_map = {}
|
|
297
|
+
return scope_map if lines.empty?
|
|
298
|
+
|
|
299
|
+
# Track nested scopes by scanning all lines once
|
|
300
|
+
scope_stack = [] # array of {type:, name:, line:}
|
|
301
|
+
|
|
302
|
+
lines.each_with_index do |line, idx|
|
|
303
|
+
line_num = idx + 1
|
|
304
|
+
|
|
305
|
+
# Check for START keywords first
|
|
306
|
+
if line =~ /^\s*SCHEMA\s+(\w+)/i
|
|
307
|
+
scope_stack << { type: :schema, name: $1, line: line_num }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
if line =~ /^\s*FUNCTION\s+(\w+)/i
|
|
311
|
+
scope_stack << { type: :function, name: $1, line: line_num }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if line =~ /^\s*PROCEDURE\s+(\w+)/i
|
|
315
|
+
scope_stack << { type: :procedure, name: $1, line: line_num }
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
if line =~ /^\s*RULE\s+(\w+)/i
|
|
319
|
+
scope_stack << { type: :rule, name: $1, line: line_num }
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
if line =~ /^\s*ENTITY\s+(\w+)/i
|
|
323
|
+
scope_stack << { type: :entity, name: $1, line: line_num }
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
if line =~ /^\s*TYPE\s+(\w+)/i
|
|
327
|
+
scope_stack << { type: :type, name: $1, line: line_num }
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Check for END keywords (inline closures on same line handled here)
|
|
331
|
+
if (line =~ /END_TYPE/i) && (scope_stack.last&.dig(:type) == :type)
|
|
332
|
+
scope_stack.pop
|
|
333
|
+
end
|
|
334
|
+
if (line =~ /END_FUNCTION/i) && (scope_stack.last&.dig(:type) == :function)
|
|
335
|
+
scope_stack.pop
|
|
336
|
+
end
|
|
337
|
+
if (line =~ /END_PROCEDURE/i) && (scope_stack.last&.dig(:type) == :procedure)
|
|
338
|
+
scope_stack.pop
|
|
339
|
+
end
|
|
340
|
+
if (line =~ /END_RULE/i) && (scope_stack.last&.dig(:type) == :rule)
|
|
341
|
+
scope_stack.pop
|
|
342
|
+
end
|
|
343
|
+
if (line =~ /END_ENTITY/i) && (scope_stack.last&.dig(:type) == :entity)
|
|
344
|
+
scope_stack.pop
|
|
345
|
+
end
|
|
346
|
+
if (line =~ /END_SCHEMA/i) && (scope_stack.last&.dig(:type) == :schema)
|
|
347
|
+
scope_stack.pop
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Record the innermost scope for this line
|
|
351
|
+
scope_map[line_num] = scope_stack.last&.dig(:name)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
scope_map
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# O(1) scope lookup using pre-computed scope map
|
|
358
|
+
def find_containing_scope_by_name(remark_line)
|
|
359
|
+
return nil unless @scope_map
|
|
360
|
+
|
|
361
|
+
scope_name = @scope_map[remark_line]
|
|
362
|
+
return nil unless scope_name
|
|
363
|
+
|
|
364
|
+
# Find the model node for this scope
|
|
365
|
+
return nil unless @model
|
|
366
|
+
|
|
367
|
+
@model.schemas.each do |schema|
|
|
368
|
+
return schema if schema.id == scope_name
|
|
369
|
+
|
|
370
|
+
%i[functions procedures rules entities types].each do |decl_type|
|
|
371
|
+
collection = schema.send(decl_type)
|
|
372
|
+
next unless collection.is_a?(Array)
|
|
373
|
+
|
|
374
|
+
found = collection.find { |n| n.id == scope_name }
|
|
375
|
+
return found if found
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
nil
|
|
380
|
+
end
|
|
381
|
+
|
|
326
382
|
def find_node_in_scope(scope, tag)
|
|
327
383
|
return nil unless scope
|
|
328
384
|
|
|
@@ -465,7 +521,7 @@ module Expressir
|
|
|
465
521
|
return nil unless where_rules&.any?
|
|
466
522
|
|
|
467
523
|
# Search source text for WHERE clause containing this remark
|
|
468
|
-
lines =
|
|
524
|
+
lines = source_lines
|
|
469
525
|
|
|
470
526
|
where_rules.each do |wr|
|
|
471
527
|
next unless wr.id
|
|
@@ -502,30 +558,16 @@ module Expressir
|
|
|
502
558
|
end
|
|
503
559
|
|
|
504
560
|
def find_containing_scope(remark_line, nodes_with_positions)
|
|
505
|
-
# First try
|
|
506
|
-
|
|
507
|
-
return
|
|
561
|
+
# First try scope map (O(1) once built)
|
|
562
|
+
scope = find_containing_scope_by_name(remark_line)
|
|
563
|
+
return scope if scope
|
|
508
564
|
|
|
509
565
|
# 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
|
|
566
|
+
find_containing_scope_position(remark_line, nodes_with_positions)
|
|
525
567
|
end
|
|
526
568
|
|
|
527
569
|
def find_scope_by_text_search(remark_line)
|
|
528
|
-
lines =
|
|
570
|
+
lines = source_lines
|
|
529
571
|
return nil if remark_line < 1 || remark_line > lines.length
|
|
530
572
|
|
|
531
573
|
# Track nested scopes by searching backwards from remark_line
|
|
@@ -641,23 +683,19 @@ module Expressir
|
|
|
641
683
|
end
|
|
642
684
|
|
|
643
685
|
def find_containing_scope_for_ip(remark_line, nodes_with_positions)
|
|
644
|
-
# First try
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
return text_based_scope
|
|
686
|
+
# First try scope map (O(1))
|
|
687
|
+
scope = find_containing_scope_by_name(remark_line)
|
|
688
|
+
if scope && supports_informal_propositions?(scope)
|
|
689
|
+
return scope
|
|
649
690
|
end
|
|
650
691
|
|
|
651
692
|
# 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
693
|
containing_nodes = nodes_with_positions.select do |n|
|
|
655
694
|
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
656
695
|
!repository?(n[:node]) && !cache?(n[:node])
|
|
657
696
|
end
|
|
658
697
|
|
|
659
698
|
# Find the innermost node that supports informal propositions
|
|
660
|
-
# Priority: Entity, Rule, Type, Schema
|
|
661
699
|
if containing_nodes.any?
|
|
662
700
|
containing_nodes.reverse_each do |n|
|
|
663
701
|
node = n[:node]
|
|
@@ -676,7 +714,7 @@ module Expressir
|
|
|
676
714
|
|
|
677
715
|
def find_scope_by_source_text(remark_line)
|
|
678
716
|
# Search backwards from remark_line for containing scope
|
|
679
|
-
lines =
|
|
717
|
+
lines = source_lines
|
|
680
718
|
|
|
681
719
|
# Find the entity/type/rule that contains this line
|
|
682
720
|
entity_start = nil
|
|
@@ -756,7 +794,10 @@ module Expressir
|
|
|
756
794
|
end
|
|
757
795
|
|
|
758
796
|
def find_by_exact_path(model, path)
|
|
759
|
-
return nil unless path
|
|
797
|
+
return nil unless path
|
|
798
|
+
|
|
799
|
+
# Only Repository and ExpFile support path-based find
|
|
800
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
760
801
|
|
|
761
802
|
# Try original path
|
|
762
803
|
result = safe_find(model, path)
|
|
@@ -768,7 +809,8 @@ module Expressir
|
|
|
768
809
|
end
|
|
769
810
|
|
|
770
811
|
def create_implicit_remark_item_at_schema(model, item_id, schema_id)
|
|
771
|
-
|
|
812
|
+
# Only Repository and ExpFile support schema lookup
|
|
813
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
772
814
|
|
|
773
815
|
schema = safe_find(model, schema_id)
|
|
774
816
|
return nil unless schema.is_a?(Model::Declarations::Schema)
|
|
@@ -789,7 +831,7 @@ module Expressir
|
|
|
789
831
|
end
|
|
790
832
|
|
|
791
833
|
def create_implicit_remark_item(model, path, schema_ids = [])
|
|
792
|
-
return nil unless repository?(model)
|
|
834
|
+
return nil unless repository?(model) || exp_file?(model)
|
|
793
835
|
|
|
794
836
|
# Normalize path (handle "ip:IP1" format)
|
|
795
837
|
clean_path = normalize_path(path)
|
|
@@ -913,7 +955,7 @@ module Expressir
|
|
|
913
955
|
end
|
|
914
956
|
|
|
915
957
|
def get_line_content(line_num)
|
|
916
|
-
lines =
|
|
958
|
+
lines = source_lines
|
|
917
959
|
return "" if line_num < 1 || line_num > lines.length
|
|
918
960
|
|
|
919
961
|
lines[line_num - 1]
|
|
@@ -952,20 +994,29 @@ module Expressir
|
|
|
952
994
|
|
|
953
995
|
# Only add remarks to nodes that support them
|
|
954
996
|
if supports_remarks?(node)
|
|
955
|
-
|
|
956
|
-
node.remarks
|
|
997
|
+
# Always add to remarks attribute (for types that have it)
|
|
998
|
+
if node.respond_to?(:remarks) && node.respond_to?(:remarks=)
|
|
999
|
+
node.remarks ||= []
|
|
1000
|
+
node.remarks << text
|
|
1001
|
+
end
|
|
957
1002
|
|
|
958
1003
|
if tag.nil?
|
|
959
|
-
|
|
960
|
-
|
|
1004
|
+
# Untagged remark: store in untagged_remarks
|
|
1005
|
+
remark_info = Model::RemarkInfo.new(text: text, format: format)
|
|
961
1006
|
node.untagged_remarks ||= []
|
|
962
1007
|
node.untagged_remarks << remark_info
|
|
963
1008
|
end
|
|
964
1009
|
end
|
|
965
1010
|
end
|
|
966
1011
|
|
|
1012
|
+
# All ModelElement subclasses have untagged_remarks from ModelElement
|
|
967
1013
|
def supports_remarks?(obj)
|
|
968
|
-
|
|
1014
|
+
obj.is_a?(Model::ModelElement)
|
|
1015
|
+
end
|
|
1016
|
+
|
|
1017
|
+
# Types that include HasRemarkItems can have remark_items
|
|
1018
|
+
def supports_remark_items?(obj)
|
|
1019
|
+
obj.is_a?(Model::HasRemarkItems)
|
|
969
1020
|
end
|
|
970
1021
|
|
|
971
1022
|
def collect_nodes_with_positions(node, result, visited = Set.new)
|
|
@@ -979,20 +1030,41 @@ module Expressir
|
|
|
979
1030
|
# The parser always provides this via Slice#offset
|
|
980
1031
|
if node.source_offset
|
|
981
1032
|
pos = node.source_offset
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
#
|
|
986
|
-
#
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1033
|
+
# Validate offset: native parser returns 0 for leaf nodes (WhereRule)
|
|
1034
|
+
# where it can't determine the actual position. These have short
|
|
1035
|
+
# expression-like source ("TRUE;") that doesn't appear at file start.
|
|
1036
|
+
# Container nodes (Schema, Entity, Type) have declaration-like source
|
|
1037
|
+
# that either starts at position 0 legitimately or is clearly valid.
|
|
1038
|
+
valid = pos.positive?
|
|
1039
|
+
if !valid && pos.zero? && node.source
|
|
1040
|
+
src = node.source.to_s
|
|
1041
|
+
# Accept position=0 if source is a declaration keyword line
|
|
1042
|
+
valid = src.start_with?("SCHEMA", "ENTITY", "TYPE", "FUNCTION",
|
|
1043
|
+
"PROCEDURE", "RULE", "CONSTANT", "VARIABLE",
|
|
1044
|
+
"USE", "REFERENCE", "END_SCHEMA", "END_ENTITY",
|
|
1045
|
+
"END_TYPE", "END_FUNCTION", "END_PROCEDURE",
|
|
1046
|
+
"END_RULE", "END_CONSTANT", "END_VARIABLE")
|
|
1047
|
+
end
|
|
1048
|
+
if valid
|
|
1049
|
+
line = get_line_number(pos)
|
|
1050
|
+
source_end_line = get_line_number(pos + node.source.length)
|
|
1051
|
+
|
|
1052
|
+
# For container nodes, use the maximum end_line from children
|
|
1053
|
+
# This is needed because source.length only covers the declaration, not the body
|
|
1054
|
+
children_end_line = calculate_children_end_line(node)
|
|
1055
|
+
end_line = [source_end_line,
|
|
1056
|
+
children_end_line].compact.max || source_end_line
|
|
1057
|
+
|
|
1058
|
+
result << {
|
|
1059
|
+
node: node,
|
|
1060
|
+
position: pos,
|
|
1061
|
+
line: line,
|
|
1062
|
+
end_line: end_line,
|
|
1063
|
+
}
|
|
1064
|
+
else
|
|
1065
|
+
# Invalid offset — treat as unknown position
|
|
1066
|
+
result << { node: node, position: nil, line: nil, end_line: nil }
|
|
1067
|
+
end
|
|
996
1068
|
else
|
|
997
1069
|
# No source_offset available - should not happen if parser provides Slice
|
|
998
1070
|
result << { node: node, position: nil, line: nil, end_line: nil }
|
|
@@ -1075,13 +1147,23 @@ module Expressir
|
|
|
1075
1147
|
# Find the node that CONTAINS this remark line
|
|
1076
1148
|
# This handles preamble remarks and embedded remarks
|
|
1077
1149
|
# Exclude Repository and Cache as they are not semantic scopes
|
|
1150
|
+
# But include ExpFile for file-level preamble remarks
|
|
1078
1151
|
containing = nodes.select do |n|
|
|
1079
1152
|
n[:line] && n[:end_line] && n[:line] <= remark_line && n[:end_line] >= remark_line &&
|
|
1080
1153
|
!repository?(n[:node]) && !cache?(n[:node])
|
|
1081
1154
|
end
|
|
1082
1155
|
|
|
1083
1156
|
if containing.any?
|
|
1084
|
-
#
|
|
1157
|
+
# Prefer ExpFile for preamble remarks (before first schema)
|
|
1158
|
+
# Otherwise return the most specific (smallest) containing node
|
|
1159
|
+
exp_file_node = containing.find { |n| exp_file?(n[:node]) }
|
|
1160
|
+
# If this is a preamble remark (before first schema line), use ExpFile
|
|
1161
|
+
if exp_file_node
|
|
1162
|
+
first_schema_line = exp_file_node[:node].schemas&.first&.source_offset
|
|
1163
|
+
if first_schema_line && remark_line < get_line_number(first_schema_line)
|
|
1164
|
+
return exp_file_node[:node]
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1085
1167
|
# Sort by span size and return the smallest
|
|
1086
1168
|
containing.min_by { |n| n[:end_line] - n[:line] }[:node]
|
|
1087
1169
|
else
|
|
@@ -1096,24 +1178,34 @@ module Expressir
|
|
|
1096
1178
|
|
|
1097
1179
|
# Type checking helper methods
|
|
1098
1180
|
|
|
1181
|
+
def get_schema_ids(model)
|
|
1182
|
+
if repository?(model)
|
|
1183
|
+
model.schemas.filter_map(&:id)
|
|
1184
|
+
elsif exp_file?(model)
|
|
1185
|
+
model.schemas.filter_map(&:id)
|
|
1186
|
+
else
|
|
1187
|
+
[]
|
|
1188
|
+
end
|
|
1189
|
+
end
|
|
1190
|
+
|
|
1099
1191
|
def repository?(obj)
|
|
1100
1192
|
obj.is_a?(Model::Repository)
|
|
1101
1193
|
end
|
|
1102
1194
|
|
|
1195
|
+
def exp_file?(obj)
|
|
1196
|
+
obj.is_a?(Model::ExpFile)
|
|
1197
|
+
end
|
|
1198
|
+
|
|
1103
1199
|
def cache?(obj)
|
|
1104
1200
|
obj.is_a?(Model::Cache)
|
|
1105
1201
|
end
|
|
1106
1202
|
|
|
1107
1203
|
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) }
|
|
1204
|
+
obj.is_a?(Model::HasInformalPropositions)
|
|
1113
1205
|
end
|
|
1114
1206
|
|
|
1115
1207
|
def supports_where_rules?(obj)
|
|
1116
|
-
|
|
1208
|
+
obj.is_a?(Model::HasWhereRules)
|
|
1117
1209
|
end
|
|
1118
1210
|
|
|
1119
1211
|
# Safe accessor methods that return nil instead of NoMethodError
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "parsanol"
|
|
4
|
-
require "set"
|
|
5
4
|
|
|
6
5
|
module Expressir
|
|
7
6
|
module Express
|
|
@@ -31,8 +30,7 @@ module Expressir
|
|
|
31
30
|
# @param remarks [Array<Hash>] The extracted remarks
|
|
32
31
|
def attach_remarks(model, remarks)
|
|
33
32
|
# Group remarks by their tag
|
|
34
|
-
tagged_remarks = remarks.
|
|
35
|
-
untagged_remarks = remarks.reject { |r| r[:tag] }
|
|
33
|
+
tagged_remarks, untagged_remarks = remarks.partition { |r| r[:tag] }
|
|
36
34
|
|
|
37
35
|
# Process tagged remarks
|
|
38
36
|
tagged_remarks.each do |remark|
|