expressir 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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/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 +138 -185
- 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/builder.rb +74 -86
- 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 +7 -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/parser.rb +114 -884
- data/lib/expressir/express/pretty_formatter.rb +23 -5
- data/lib/expressir/express/remark_attacher.rb +475 -577
- data/lib/expressir/express/remark_scanner.rb +84 -19
- data/lib/expressir/express/schema_block_scanner.rb +136 -0
- data/lib/expressir/express/schema_source_formatter.rb +15 -0
- data/lib/expressir/express/scope_resolver.rb +194 -0
- data/lib/expressir/express/source_formatter.rb +15 -0
- data/lib/expressir/express/streaming_builder.rb +147 -176
- data/lib/expressir/express.rb +8 -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 +128 -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/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 +38 -3
- metadata +63 -5
- data/lib/expressir/express/transformer/remark_handling.rb +0 -194
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# EXPRESS grammar namespace. Houses the Parsanol grammar definition
|
|
6
|
+
# separate from the I/O orchestration on the outer Parser facade.
|
|
7
|
+
module Grammar
|
|
8
|
+
autoload :Parser, "#{__dir__}/grammar/parser"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# Builds and queries an index of model nodes by source position.
|
|
6
|
+
#
|
|
7
|
+
# One interface (`nearest_node_to`, `node_for_end_scope_at`, `each_node`),
|
|
8
|
+
# one implementation. Extracted from RemarkAttacher so the tree-walk +
|
|
9
|
+
# byte→line translation + nearest-node heuristics live in one place,
|
|
10
|
+
# tested independently of any remark logic.
|
|
11
|
+
#
|
|
12
|
+
# Reference: ISO 10303-11 — the parser yields source spans on ModelElement
|
|
13
|
+
# instances; this module turns those spans into a line-keyed index that
|
|
14
|
+
# remark attachment can query.
|
|
15
|
+
class NodePositionIndex
|
|
16
|
+
# Single source of truth for "what collections does this node have"
|
|
17
|
+
# lives on the model — each class declares its own via the
|
|
18
|
+
# `collection_attributes` macro. Referenced by both RemarkAttacher
|
|
19
|
+
# and NodePositionIndex via this alias for backward compatibility.
|
|
20
|
+
COLLECTION_REGISTRY = Model::ModelElement.collection_registry
|
|
21
|
+
|
|
22
|
+
attr_reader :nodes
|
|
23
|
+
|
|
24
|
+
def initialize(model, line_map)
|
|
25
|
+
@model = model
|
|
26
|
+
@line_map = line_map
|
|
27
|
+
@nodes = build_sorted_nodes
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the most-specific node whose span contains `remark_line`,
|
|
31
|
+
# preferring same-line starts/ends, then smallest containing span.
|
|
32
|
+
# Excludes Repository and Cache (not semantic scopes for remarks).
|
|
33
|
+
def nearest_node_to(remark_line)
|
|
34
|
+
same_start = nodes.select do |n|
|
|
35
|
+
n[:line] == remark_line && semantic?(n[:node])
|
|
36
|
+
end
|
|
37
|
+
return same_start.last[:node] if same_start.any?
|
|
38
|
+
|
|
39
|
+
same_end = nodes.select do |n|
|
|
40
|
+
n[:end_line] == remark_line && semantic?(n[:node])
|
|
41
|
+
end
|
|
42
|
+
return same_end.last[:node] if same_end.any?
|
|
43
|
+
|
|
44
|
+
containing = nodes.select do |n|
|
|
45
|
+
n[:line] && n[:end_line] &&
|
|
46
|
+
n[:line] <= remark_line && n[:end_line] >= remark_line &&
|
|
47
|
+
semantic?(n[:node])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if containing.any?
|
|
51
|
+
exp_file_node = containing.find { |n| n[:node].is_a?(Model::ExpFile) }
|
|
52
|
+
if exp_file_node
|
|
53
|
+
first_schema_offset = exp_file_node[:node].schemas&.first&.source_offset
|
|
54
|
+
if first_schema_offset && remark_line < @line_map.line_number(first_schema_offset)
|
|
55
|
+
return exp_file_node[:node]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
# Prefer non-ExpFile nodes (Schema, Entity, etc.) over ExpFile
|
|
59
|
+
# when both span the same range, since ExpFile is a file-level
|
|
60
|
+
# container and remarks inside a schema belong to the schema.
|
|
61
|
+
candidates = containing.reject { |n| n[:node].is_a?(Model::ExpFile) }
|
|
62
|
+
candidates = containing if candidates.empty?
|
|
63
|
+
candidates.min_by { |n| n[:end_line] - n[:line] }[:node]
|
|
64
|
+
else
|
|
65
|
+
before = nodes.select do |n|
|
|
66
|
+
n[:end_line] && n[:end_line] < remark_line && semantic?(n[:node])
|
|
67
|
+
end
|
|
68
|
+
if before.any?
|
|
69
|
+
before.max_by { |n| n[:end_line] }[:node]
|
|
70
|
+
else
|
|
71
|
+
# Remark is before all nodes (e.g., preamble comment before SCHEMA).
|
|
72
|
+
# Attach to the first semantic node.
|
|
73
|
+
after = nodes.select { |n| n[:line] && semantic?(n[:node]) }
|
|
74
|
+
after.min_by { |n| n[:line] }[:node] if after.any?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Returns the node whose END_XXX declaration is on `remark_line`, when
|
|
80
|
+
# the line text matches one of the END_XXX keywords. Used to attach
|
|
81
|
+
# remarks that appear immediately after a scope closes.
|
|
82
|
+
def node_for_end_scope_at(remark_line, line_content)
|
|
83
|
+
node_type = case line_content
|
|
84
|
+
when /END_SCHEMA/i then Model::Declarations::Schema
|
|
85
|
+
when /END_ENTITY/i then Model::Declarations::Entity
|
|
86
|
+
when /END_TYPE/i then Model::Declarations::Type
|
|
87
|
+
when /END_FUNCTION/i then Model::Declarations::Function
|
|
88
|
+
when /END_PROCEDURE/i then Model::Declarations::Procedure
|
|
89
|
+
when /END_RULE/i then Model::Declarations::Rule
|
|
90
|
+
end
|
|
91
|
+
return nil unless node_type
|
|
92
|
+
|
|
93
|
+
matching = nodes.select do |n|
|
|
94
|
+
n[:node].is_a?(node_type) &&
|
|
95
|
+
(n[:end_line] == remark_line ||
|
|
96
|
+
(n[:end_line] && n[:end_line] <= remark_line && n[:end_line] >= remark_line - 2))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
matching.first&.dig(:node) || nodes.find { |n| n[:node].is_a?(node_type) }&.dig(:node)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def semantic?(node)
|
|
105
|
+
!node.is_a?(Model::Repository) && !node.is_a?(Model::Cache)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def build_sorted_nodes
|
|
109
|
+
result = []
|
|
110
|
+
collect_nodes(@model, result, Set.new)
|
|
111
|
+
result.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
112
|
+
result
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def collect_nodes(node, result, visited, owner: nil, collection: nil)
|
|
116
|
+
return unless node
|
|
117
|
+
return if visited.include?(node.object_id)
|
|
118
|
+
|
|
119
|
+
visited.add(node.object_id)
|
|
120
|
+
|
|
121
|
+
if node.is_a?(Model::ModelElement) && node.source && node.source_offset
|
|
122
|
+
record_node_position(node, result, owner, collection)
|
|
123
|
+
else
|
|
124
|
+
result << { node: node, position: nil, line: nil, end_line: nil,
|
|
125
|
+
owner: owner, collection: collection }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
collect_children(node, result, visited)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def record_node_position(node, result, owner, collection)
|
|
132
|
+
pos = node.source_offset
|
|
133
|
+
valid = position_valid?(pos, node)
|
|
134
|
+
unless valid
|
|
135
|
+
result << { node: node, position: nil, line: nil, end_line: nil,
|
|
136
|
+
owner: owner, collection: collection }
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
line = @line_map.line_number(pos)
|
|
141
|
+
source_end_line = @line_map.line_number(pos + node.source.length)
|
|
142
|
+
children_end_line = children_end_line_for(node)
|
|
143
|
+
end_line = [source_end_line, children_end_line].compact.max || source_end_line
|
|
144
|
+
|
|
145
|
+
result << { node: node, position: pos, line: line, end_line: end_line,
|
|
146
|
+
owner: owner, collection: collection }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The parser returns source_offset=0 for leaf nodes (WhereRule) where
|
|
150
|
+
# it cannot determine the actual position. Accept position=0 only when
|
|
151
|
+
# the source is a declaration keyword line — those legitimately start
|
|
152
|
+
# at the beginning of the file.
|
|
153
|
+
def position_valid?(pos, node)
|
|
154
|
+
return true if pos.positive?
|
|
155
|
+
return false unless pos.zero? && node.source
|
|
156
|
+
|
|
157
|
+
node.source.to_s.start_with?(
|
|
158
|
+
"SCHEMA", "ENTITY", "TYPE", "FUNCTION",
|
|
159
|
+
"PROCEDURE", "RULE", "CONSTANT", "VARIABLE",
|
|
160
|
+
"USE", "REFERENCE", "END_SCHEMA", "END_ENTITY",
|
|
161
|
+
"END_TYPE", "END_FUNCTION", "END_PROCEDURE",
|
|
162
|
+
"END_RULE", "END_CONSTANT", "END_VARIABLE"
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def children_end_line_for(node)
|
|
167
|
+
end_lines = []
|
|
168
|
+
|
|
169
|
+
if node.is_a?(Model::Declarations::Schema)
|
|
170
|
+
Array(node.children).each do |child|
|
|
171
|
+
next unless child.is_a?(Model::ModelElement) && child.source_offset && child.source
|
|
172
|
+
|
|
173
|
+
end_lines << @line_map.line_number(child.source_offset + child.source.length)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
each_collection_on(node) do |item|
|
|
178
|
+
next unless item.is_a?(Model::ModelElement) && item.source_offset && item.source
|
|
179
|
+
|
|
180
|
+
end_lines << @line_map.line_number(item.source_offset + item.source.length)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end_lines.max
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def collect_children(node, result, visited)
|
|
187
|
+
if node.is_a?(Model::Declarations::Schema)
|
|
188
|
+
Array(node.children).each do |c|
|
|
189
|
+
collect_nodes(c, result, visited, owner: node, collection: :children)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
each_collection_on(node) do |item, attr|
|
|
194
|
+
collect_nodes(item, result, visited, owner: node, collection: attr)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Yields each child in any declared collection on the node (with the
|
|
199
|
+
# collection attribute name), based on the type-driven
|
|
200
|
+
# COLLECTION_REGISTRY. Returns nothing for nodes whose class is not
|
|
201
|
+
# registered.
|
|
202
|
+
def each_collection_on(node)
|
|
203
|
+
attrs = COLLECTION_REGISTRY[node.class]
|
|
204
|
+
return unless attrs
|
|
205
|
+
|
|
206
|
+
attrs.each do |attr|
|
|
207
|
+
collection = node.public_send(attr)
|
|
208
|
+
next unless collection.is_a?(Array)
|
|
209
|
+
|
|
210
|
+
collection.each { |item| yield(item, attr) }
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|