tediparse 2.0.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 +7 -0
- data/CHANGELOG.md +319 -0
- data/LICENSE +26 -0
- data/README.md +308 -0
- data/Rakefile +33 -0
- data/bin/tediparse +102 -0
- data/doc/Defining.md +97 -0
- data/doc/Generating-Grammars.md +241 -0
- data/doc/Generating.md +332 -0
- data/doc/Navigating.md +670 -0
- data/doc/Parsing.md +133 -0
- data/doc/README.md +51 -0
- data/doc/Serializing.md +100 -0
- data/doc/Tokenizing.md +136 -0
- data/doc/Validating.md +140 -0
- data/lib/ruby/array.rb +226 -0
- data/lib/ruby/blank.rb +52 -0
- data/lib/ruby/exception.rb +14 -0
- data/lib/ruby/hash.rb +14 -0
- data/lib/ruby/module.rb +60 -0
- data/lib/ruby/object.rb +56 -0
- data/lib/ruby/string.rb +89 -0
- data/lib/ruby/to_d.rb +82 -0
- data/lib/ruby/to_date.rb +26 -0
- data/lib/ruby/to_time.rb +21 -0
- data/lib/ruby/try.rb +46 -0
- data/lib/stupidedi/builder.rb +6 -0
- data/lib/stupidedi/color.rb +94 -0
- data/lib/stupidedi/config/code_list_config.rb +45 -0
- data/lib/stupidedi/config/functional_group_config.rb +72 -0
- data/lib/stupidedi/config/interchange_config.rb +86 -0
- data/lib/stupidedi/config/transaction_set_config.rb +73 -0
- data/lib/stupidedi/config.rb +100 -0
- data/lib/stupidedi/either.rb +286 -0
- data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
- data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
- data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
- data/lib/stupidedi/exceptions/output_error.rb +7 -0
- data/lib/stupidedi/exceptions/parse_error.rb +7 -0
- data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
- data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
- data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
- data/lib/stupidedi/exceptions.rb +13 -0
- data/lib/stupidedi/inspect.rb +26 -0
- data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
- data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
- data/lib/stupidedi/interchanges/element_types.rb +11 -0
- data/lib/stupidedi/interchanges.rb +21 -0
- data/lib/stupidedi/parser/builder_dsl.rb +292 -0
- data/lib/stupidedi/parser/constraint_table.rb +566 -0
- data/lib/stupidedi/parser/generation.rb +147 -0
- data/lib/stupidedi/parser/identifier_stack.rb +248 -0
- data/lib/stupidedi/parser/instruction.rb +112 -0
- data/lib/stupidedi/parser/instruction_table.rb +222 -0
- data/lib/stupidedi/parser/navigation.rb +786 -0
- data/lib/stupidedi/parser/state_machine.rb +62 -0
- data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
- data/lib/stupidedi/parser/states/failure_state.rb +69 -0
- data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
- data/lib/stupidedi/parser/states/initial_state.rb +62 -0
- data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
- data/lib/stupidedi/parser/states/loop_state.rb +74 -0
- data/lib/stupidedi/parser/states/table_state.rb +90 -0
- data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
- data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
- data/lib/stupidedi/parser/tokenization.rb +195 -0
- data/lib/stupidedi/parser.rb +32 -0
- data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
- data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
- data/lib/stupidedi/reader/input/file_input.rb +157 -0
- data/lib/stupidedi/reader/input.rb +31 -0
- data/lib/stupidedi/reader/position.rb +78 -0
- data/lib/stupidedi/reader/result.rb +172 -0
- data/lib/stupidedi/reader/segment_dict.rb +176 -0
- data/lib/stupidedi/reader/separators.rb +90 -0
- data/lib/stupidedi/reader/stream_reader.rb +173 -0
- data/lib/stupidedi/reader/token_reader.rb +465 -0
- data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
- data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
- data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
- data/lib/stupidedi/reader.rb +121 -0
- data/lib/stupidedi/schema/abstract_def.rb +76 -0
- data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
- data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
- data/lib/stupidedi/schema/abstract_use.rb +79 -0
- data/lib/stupidedi/schema/code_list.rb +99 -0
- data/lib/stupidedi/schema/component_element_use.rb +76 -0
- data/lib/stupidedi/schema/composite_element_def.rb +103 -0
- data/lib/stupidedi/schema/composite_element_use.rb +78 -0
- data/lib/stupidedi/schema/element_req.rb +57 -0
- data/lib/stupidedi/schema/functional_group_def.rb +124 -0
- data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
- data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
- data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
- data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
- data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
- data/lib/stupidedi/schema/generation/models.rb +85 -0
- data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
- data/lib/stupidedi/schema/generation/runner.rb +161 -0
- data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
- data/lib/stupidedi/schema/generation/support.rb +78 -0
- data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
- data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
- data/lib/stupidedi/schema/generation.rb +94 -0
- data/lib/stupidedi/schema/interchange_def.rb +103 -0
- data/lib/stupidedi/schema/loop_def.rb +156 -0
- data/lib/stupidedi/schema/repeat_count.rb +86 -0
- data/lib/stupidedi/schema/segment_def.rb +122 -0
- data/lib/stupidedi/schema/segment_req.rb +46 -0
- data/lib/stupidedi/schema/segment_use.rb +99 -0
- data/lib/stupidedi/schema/simple_element_def.rb +51 -0
- data/lib/stupidedi/schema/simple_element_use.rb +83 -0
- data/lib/stupidedi/schema/syntax_note.rb +52 -0
- data/lib/stupidedi/schema/table_def.rb +178 -0
- data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
- data/lib/stupidedi/schema.rb +30 -0
- data/lib/stupidedi/sets.rb +42 -0
- data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
- data/lib/stupidedi/transaction_sets/builder.rb +188 -0
- data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
- data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
- data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
- data/lib/stupidedi/transaction_sets/common.rb +8 -0
- data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
- data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
- data/lib/stupidedi/transaction_sets/validation.rb +9 -0
- data/lib/stupidedi/transaction_sets.rb +25 -0
- data/lib/stupidedi/values/abstract_element_val.rb +19 -0
- data/lib/stupidedi/values/abstract_val.rb +133 -0
- data/lib/stupidedi/values/composite_element_val.rb +102 -0
- data/lib/stupidedi/values/functional_group_val.rb +105 -0
- data/lib/stupidedi/values/interchange_val.rb +102 -0
- data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
- data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
- data/lib/stupidedi/values/loop_val.rb +73 -0
- data/lib/stupidedi/values/repeated_element_val.rb +113 -0
- data/lib/stupidedi/values/segment_val.rb +105 -0
- data/lib/stupidedi/values/segment_val_group.rb +20 -0
- data/lib/stupidedi/values/simple_element_val.rb +80 -0
- data/lib/stupidedi/values/table_val.rb +69 -0
- data/lib/stupidedi/values/transaction_set_val.rb +69 -0
- data/lib/stupidedi/values/transmission_val.rb +56 -0
- data/lib/stupidedi/values.rb +22 -0
- data/lib/stupidedi/version.rb +4 -0
- data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
- data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
- data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
- data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
- data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
- data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
- data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
- data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
- data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
- data/lib/stupidedi/versions/common/element_types.rb +29 -0
- data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
- data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
- data/lib/stupidedi/versions/common.rb +11 -0
- data/lib/stupidedi/versions.rb +25 -0
- data/lib/stupidedi/writer/claredi.rb +178 -0
- data/lib/stupidedi/writer/default.rb +119 -0
- data/lib/stupidedi/writer.rb +7 -0
- data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
- data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
- data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
- data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
- data/lib/stupidedi/zipper/path.rb +132 -0
- data/lib/stupidedi/zipper/root_cursor.rb +120 -0
- data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
- data/lib/stupidedi/zipper.rb +45 -0
- data/lib/stupidedi.rb +69 -0
- data/lib/tediparse.rb +1 -0
- metadata +249 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Stupidedi
|
|
3
|
+
using Refinements
|
|
4
|
+
|
|
5
|
+
module Parser
|
|
6
|
+
# The {ConstraintTable} is a data structure that contains one or more
|
|
7
|
+
# {Instruction} values for the same segment identifier. Each concrete
|
|
8
|
+
# subclass implements different strategies for narrowing down the
|
|
9
|
+
# {Instruction} list.
|
|
10
|
+
#
|
|
11
|
+
# Reducing the number of valid {Instruction} values is important because
|
|
12
|
+
# executing more than one {Instruction} creates a non-deterministic state --
|
|
13
|
+
# more than one valid parse tree exists -- which slows the parser. Most
|
|
14
|
+
# often there is only one valid {Instruction} but the parser cannot
|
|
15
|
+
# (efficiently or at all) narrow the tree down without evaluating the
|
|
16
|
+
# constraints declared by each {Instruction}'s {Schema::SegmentUse}, which
|
|
17
|
+
# is done here.
|
|
18
|
+
#
|
|
19
|
+
class ConstraintTable
|
|
20
|
+
# @return [Array<Instruction>]
|
|
21
|
+
abstract :matches, :args => %w(segment_tok strict mode state)
|
|
22
|
+
|
|
23
|
+
# @return [Array<Instruction>]
|
|
24
|
+
attr_reader :instructions
|
|
25
|
+
|
|
26
|
+
def copy(changes = {})
|
|
27
|
+
self.class.new \
|
|
28
|
+
changes.fetch(:instructions, instructions)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [void]
|
|
32
|
+
# :nocov:
|
|
33
|
+
def pretty_print(q)
|
|
34
|
+
name = self.class.name.split("::").last
|
|
35
|
+
q.text "#{name}.build"
|
|
36
|
+
q.group(2, "([", "])") do
|
|
37
|
+
q.breakable
|
|
38
|
+
instructions.each do |op|
|
|
39
|
+
unless q.current_group.first?
|
|
40
|
+
q.text ","
|
|
41
|
+
q.breakable
|
|
42
|
+
end
|
|
43
|
+
q.pp op
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
# :nocov:
|
|
48
|
+
|
|
49
|
+
# @todo
|
|
50
|
+
def critique(segment_tok, segment_uses)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Performs no filtering of the {Instruction} list. This is used when there
|
|
54
|
+
# already is a single {Instruction} or when a {Reader::SegmentTok} doesn't
|
|
55
|
+
# provide any more information to filter the list.
|
|
56
|
+
#
|
|
57
|
+
class Stub < ConstraintTable
|
|
58
|
+
def initialize(instructions)
|
|
59
|
+
@instructions = instructions
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Array<Instruction>]
|
|
63
|
+
def matches(segment_tok, strict, mode, state = nil)
|
|
64
|
+
@instructions.tap do |xs|
|
|
65
|
+
critique(segment_tok, xs.map(&:segment_use)) if strict
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Chooses the {Instruction} that pops the fewest number of states. For
|
|
71
|
+
# example, in the X222 837P an HL segment signals the start of a new
|
|
72
|
+
# 2000 loop, but may or may not begin a new Table 2 -- the specifications
|
|
73
|
+
# aren't actually clear. This rule will not create a new Table 2, but
|
|
74
|
+
# just add a new 2000 loop under the current one.
|
|
75
|
+
#
|
|
76
|
+
class Shallowest < ConstraintTable
|
|
77
|
+
def initialize(instructions)
|
|
78
|
+
@instructions = instructions
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Array<Instruction>]
|
|
82
|
+
def matches(segment_tok, strict, mode, state = nil)
|
|
83
|
+
@__matches ||= begin
|
|
84
|
+
shallowest = @instructions.map(&:pop_count).min
|
|
85
|
+
@instructions.select{|i| i.pop_count == shallowest }.tap do |xs|
|
|
86
|
+
critique(segment_tok, xs.map(&:segment_use)) if strict
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Chooses the {Instruction} that pops the greatest number of states.
|
|
93
|
+
#
|
|
94
|
+
class Deepest < ConstraintTable
|
|
95
|
+
# def initialize(instructions)
|
|
96
|
+
# @instructions = instructions
|
|
97
|
+
# end
|
|
98
|
+
|
|
99
|
+
# # @return [Array<Instruction>]
|
|
100
|
+
# def matches(segment_tok, strict, mode)
|
|
101
|
+
# @__matches ||= begin
|
|
102
|
+
# deepest = @instructions.map(&:pop_count).max
|
|
103
|
+
# @instructions.select{|i| i.pop_count == deepest }.tap do |xs|
|
|
104
|
+
# critique(segment_tok, xs.map(&:segment_use)) if strict
|
|
105
|
+
# end
|
|
106
|
+
# end
|
|
107
|
+
# end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Chooses the subset of {Instruction} values based on the distinguishing
|
|
111
|
+
# values allowed by each {Schema::SegmentUse}. For instance, there are
|
|
112
|
+
# often several loops that begin with `NM1`, which are distinguished by
|
|
113
|
+
# the qualifier in element `NM101`.
|
|
114
|
+
#
|
|
115
|
+
class ValueBased < ConstraintTable
|
|
116
|
+
def initialize(instructions)
|
|
117
|
+
@instructions = instructions
|
|
118
|
+
@__basis = {}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Array<Instruction>]
|
|
122
|
+
def matches(segment_tok, strict, mode, state = nil)
|
|
123
|
+
invalid = true # Were all present possibly distinguishing elements invalid?
|
|
124
|
+
present = false # Were any possibly distinguishing elements present?
|
|
125
|
+
|
|
126
|
+
disjoint, distinct = basis(@instructions, mode)
|
|
127
|
+
|
|
128
|
+
# First check single elements that can narrow the search space to
|
|
129
|
+
# a single matching Instruction.
|
|
130
|
+
disjoint.each do |(n, m), map|
|
|
131
|
+
value = deconstruct(segment_tok.element_toks, n, m)
|
|
132
|
+
|
|
133
|
+
case value
|
|
134
|
+
when nil, :not_used, :default
|
|
135
|
+
# value wasn't present in segment_tok, can't use it to decide
|
|
136
|
+
else
|
|
137
|
+
singleton = map.at(value)
|
|
138
|
+
present = true
|
|
139
|
+
|
|
140
|
+
unless singleton.nil?
|
|
141
|
+
# Success, search is terminated
|
|
142
|
+
return singleton
|
|
143
|
+
else
|
|
144
|
+
if strict
|
|
145
|
+
designator = "#{segment_tok.id}#{"%02d" % (n + 1)}"
|
|
146
|
+
designator = designator + "-%02d" % (m + 1) unless m.nil?
|
|
147
|
+
|
|
148
|
+
raise ArgumentError,
|
|
149
|
+
"value #{value.to_s} is not allowed in element #{designator}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# If we reach this line, none of the present elements could, on its
|
|
156
|
+
# own, narrow the search space to a single Instruction. We now test
|
|
157
|
+
# the combination of elements to iteratively narrow the search space
|
|
158
|
+
space = @instructions
|
|
159
|
+
|
|
160
|
+
# @todo: These filters could be ordered by probable effectiveness,
|
|
161
|
+
# so we narrow the search space by the largest amount in the fewest
|
|
162
|
+
# number of steps.
|
|
163
|
+
distinct.each do |(n, m), map|
|
|
164
|
+
value = deconstruct(segment_tok.element_toks, n, m)
|
|
165
|
+
|
|
166
|
+
unless value.nil?
|
|
167
|
+
# Lookup which instructions are compatible with this input
|
|
168
|
+
subset = map.at(value)
|
|
169
|
+
present = true
|
|
170
|
+
|
|
171
|
+
unless subset.blank?
|
|
172
|
+
invalid = false
|
|
173
|
+
space &= subset
|
|
174
|
+
|
|
175
|
+
if space.length <= 1
|
|
176
|
+
# Success, search is terminated
|
|
177
|
+
return space
|
|
178
|
+
end
|
|
179
|
+
else
|
|
180
|
+
# This value isn't compatible with any instruction
|
|
181
|
+
if strict
|
|
182
|
+
designator = "#{segment_tok.id}#{"%02d" % (n + 1)}"
|
|
183
|
+
designator = designator + "-%02d" % (m + 1) unless m.nil?
|
|
184
|
+
|
|
185
|
+
raise ArgumentError,
|
|
186
|
+
"value #{value.to_s} is not allowed in element #{designator}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
if invalid and present
|
|
193
|
+
# Some elements were present, but all contained invalid values, and
|
|
194
|
+
# even ignoring those we could not narrow the matches to a single
|
|
195
|
+
# instruction.
|
|
196
|
+
#
|
|
197
|
+
# We could return the remaining search space, but it is safest to
|
|
198
|
+
# mark this as an invalid segment and avoid the non-determinism
|
|
199
|
+
[]
|
|
200
|
+
else
|
|
201
|
+
# Some elements were present and none were invalid, but it was not
|
|
202
|
+
# possible to narrow the set of matches to a single instruction.
|
|
203
|
+
#
|
|
204
|
+
# When the survivors are sibling slots in the same loop sharing a
|
|
205
|
+
# segment id (e.g. two N3 slots in a partner-customised 4010 PO850
|
|
206
|
+
# N1 loop), use the parser's current parse tree to pick the slot
|
|
207
|
+
# whose preceding-sibling structure has actually been entered
|
|
208
|
+
# (Variant A — structural reachability). The :insert gate skips
|
|
209
|
+
# disambiguation during :find navigation, where the caller wants
|
|
210
|
+
# the full set of candidates a segment could be bound to. For
|
|
211
|
+
# genuinely ambiguous cases (different parent loops, or different
|
|
212
|
+
# segment ids) the helper bails out to the full search space,
|
|
213
|
+
# which will cause non-determinism in the parser.
|
|
214
|
+
if mode == :insert
|
|
215
|
+
disambiguate_sibling_slots(space, state)
|
|
216
|
+
else
|
|
217
|
+
space
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Resolve conflicts between instructions that have identical SegmentUse
|
|
223
|
+
# values. For each SegmentUse, this chooses the Instruction that pops
|
|
224
|
+
# the fewest number of states.
|
|
225
|
+
#
|
|
226
|
+
# @return [Array<Instruction>]
|
|
227
|
+
def shallowest(instructions)
|
|
228
|
+
grouped = instructions.group_by{|i| i.segment_use.object_id }
|
|
229
|
+
grouped.flat_map do |k, is|
|
|
230
|
+
shallowest = is.map(&:pop_count).min
|
|
231
|
+
is.select{|i| i.pop_count == shallowest }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# When ValueBased filtering leaves multiple Instructions whose
|
|
236
|
+
# SegmentUses are sibling slots in the same parent loop sharing a
|
|
237
|
+
# segment id, pick the slot whose ancestor structure has actually
|
|
238
|
+
# been entered in the current parse tree. Concretely: among the
|
|
239
|
+
# candidate sibling positions, keep only those greater than the
|
|
240
|
+
# highest-position segment already consumed in the parent loop;
|
|
241
|
+
# then pick the earliest among those. If `state` (and thus the
|
|
242
|
+
# parse tree) is not available, fall back to picking the earliest
|
|
243
|
+
# position outright (Variant B). Falls back to the input unchanged
|
|
244
|
+
# when the precondition fails, preserving the existing non-
|
|
245
|
+
# determinism error for genuinely ambiguous cases (e.g. survivors
|
|
246
|
+
# in different parent loops).
|
|
247
|
+
#
|
|
248
|
+
# All survivors share a segment id by construction (instructions
|
|
249
|
+
# are grouped by segment_id in InstructionTable#constraints before
|
|
250
|
+
# ConstraintTable.build is called).
|
|
251
|
+
#
|
|
252
|
+
# @return [Array<Instruction>]
|
|
253
|
+
def disambiguate_sibling_slots(instructions, state = nil)
|
|
254
|
+
return instructions if instructions.length <= 1
|
|
255
|
+
|
|
256
|
+
collapsed = shallowest(instructions)
|
|
257
|
+
return collapsed if collapsed.length <= 1
|
|
258
|
+
|
|
259
|
+
uses = collapsed.map(&:segment_use)
|
|
260
|
+
parent = uses.first.parent
|
|
261
|
+
return instructions if parent.nil?
|
|
262
|
+
return instructions unless uses.all?{|u| u.parent.equal?(parent) }
|
|
263
|
+
|
|
264
|
+
reachable = collapsed
|
|
265
|
+
highest = highest_consumed_position(state, parent) if state
|
|
266
|
+
if highest
|
|
267
|
+
# Strict `>` assumes non-repeating sibling slots: a candidate
|
|
268
|
+
# whose position equals `highest` is excluded because that
|
|
269
|
+
# slot has already been consumed. If a partner grammar exposes
|
|
270
|
+
# repeating sibling slots, this may need to relax to `>=`.
|
|
271
|
+
ahead = collapsed.select{|i| i.segment_use.position > highest }
|
|
272
|
+
reachable = ahead unless ahead.empty?
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
min_pos = reachable.map{|i| i.segment_use.position }.min
|
|
276
|
+
reachable.select{|i| i.segment_use.position == min_pos }
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Walks up from the state's value-tree zipper to find the LoopVal/
|
|
280
|
+
# TableVal whose definition matches `parent` (object identity), then
|
|
281
|
+
# returns the highest `usage.position` among its already-consumed
|
|
282
|
+
# segment children. Returns nil when the matching container can't be
|
|
283
|
+
# found (e.g. the loop hasn't been opened yet) or has no consumed
|
|
284
|
+
# segments — both of which mean we have no structural signal to use.
|
|
285
|
+
#
|
|
286
|
+
# @return [Integer, nil]
|
|
287
|
+
def highest_consumed_position(state, parent)
|
|
288
|
+
return nil if state.nil?
|
|
289
|
+
z = state.zipper
|
|
290
|
+
while z
|
|
291
|
+
node = z.node
|
|
292
|
+
if (node.loop? or node.table?) and node.definition.equal?(parent)
|
|
293
|
+
positions = node.children
|
|
294
|
+
.select{|c| c.segment? and c.usage }
|
|
295
|
+
.map{|c| c.usage.position }
|
|
296
|
+
return positions.max if positions.any?
|
|
297
|
+
return nil
|
|
298
|
+
end
|
|
299
|
+
break if z.root?
|
|
300
|
+
z = z.up
|
|
301
|
+
end
|
|
302
|
+
nil
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# @return [Array(Array<(Integer, Integer, Map)>, Array<(Integer, Integer, Map)>)]
|
|
306
|
+
def basis(instructions, mode)
|
|
307
|
+
@__basis[mode] ||= begin
|
|
308
|
+
# When inserting segments, given a choice between two otherwise
|
|
309
|
+
# equivalent instructions, prefer the one with smallest `pop_count`.
|
|
310
|
+
# For example, when inserting an HL*20 in X221 835, the new 2000A
|
|
311
|
+
# loop could potentially go in the current "Table 2 - Billing
|
|
312
|
+
# Provider Detail" (smaller pop_count), or the parser could create
|
|
313
|
+
# a whole new table (larger pop_count).
|
|
314
|
+
#
|
|
315
|
+
# When searching for segments in a parse tree (mode == :read), we
|
|
316
|
+
# need to try both choices. That's because this "Table 2 - Billing
|
|
317
|
+
# Provider Detail" could be followed by a different "Table 2 -
|
|
318
|
+
# Subscriber Detail", which is then followed by another "Table 2 -
|
|
319
|
+
# Billing Provider Detail". Then the next HL*20 would belong to the
|
|
320
|
+
# uncle table, not the current table.
|
|
321
|
+
if mode == :insert
|
|
322
|
+
instructions = shallowest(instructions)
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
disjoint_elements = []
|
|
326
|
+
distinct_elements = []
|
|
327
|
+
|
|
328
|
+
# The first SegmentUse is used to represent the structure that must
|
|
329
|
+
# be shared by the others: number of elements and type of elements
|
|
330
|
+
element_uses = instructions.head.segment_use.definition.element_uses
|
|
331
|
+
|
|
332
|
+
# Iterate over each element across all SegmentUses (think columns)
|
|
333
|
+
# NM1*[IL]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
|
|
334
|
+
# NM1*[40]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
|
|
335
|
+
element_uses.length.times do |n|
|
|
336
|
+
if element_uses.at(n).composite?
|
|
337
|
+
ms = 0 .. element_uses.at(n).definition.component_uses.length - 1
|
|
338
|
+
else
|
|
339
|
+
ms = [nil]
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# If this is a composite element, we iterate over each component.
|
|
343
|
+
# Otherwise this loop iterates once with the index {m} set to nil.
|
|
344
|
+
ms.each do |m|
|
|
345
|
+
last = nil # the last subset we examined
|
|
346
|
+
total = Sets.empty # the union of all examined subsets
|
|
347
|
+
|
|
348
|
+
distinct = false
|
|
349
|
+
disjoint = true
|
|
350
|
+
|
|
351
|
+
instructions.each do |i|
|
|
352
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
|
353
|
+
|
|
354
|
+
unless m.nil?
|
|
355
|
+
element_use = element_use.definition.component_uses.at(m)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
allowed_vals = element_use.allowed_values
|
|
359
|
+
|
|
360
|
+
# We want to know if every instruction's set of allowed values
|
|
361
|
+
# is disjoint (with one another). Instead of comparing each set
|
|
362
|
+
# with every other set, which takes (N-1)! comparisons, we can
|
|
363
|
+
# do it in N steps.
|
|
364
|
+
disjoint &&= allowed_vals.disjoint?(total)
|
|
365
|
+
|
|
366
|
+
# We also want to know if one instruction's set of allowed vals
|
|
367
|
+
# contains elements that aren't present in at least one other
|
|
368
|
+
# set. The opposite condition is easy to test: all sets contain
|
|
369
|
+
# the same elements (are equal). So we can similarly, check this
|
|
370
|
+
# condition in N steps rather than (N-1)!
|
|
371
|
+
distinct ||= allowed_vals != last unless last.nil?
|
|
372
|
+
|
|
373
|
+
total = allowed_vals.union(total)
|
|
374
|
+
last = allowed_vals
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# puts "#{n}.#{m}: disjoint(#{disjoint}) distinct(#{distinct})"
|
|
378
|
+
|
|
379
|
+
if disjoint
|
|
380
|
+
# Since each instruction's set of allowed values is disjoint, we
|
|
381
|
+
# can build a function/hash that returns the single instruction,
|
|
382
|
+
# given one of the values. When given a value outside the set of
|
|
383
|
+
# all (combined) values, it returns nil.
|
|
384
|
+
disjoint_elements << [[n, m], build_disjoint(total, n, m, instructions)]
|
|
385
|
+
elsif distinct
|
|
386
|
+
# Not all instructions have the same set of allowed values. So
|
|
387
|
+
# we can build a function/hash that accepts one of the values
|
|
388
|
+
# and returns the subset of the instructions where that value
|
|
389
|
+
# can occur. This might be some, none, or all of the original
|
|
390
|
+
# instructions, so clearly this provides less information than
|
|
391
|
+
# if each allowed value set was disjoint.
|
|
392
|
+
|
|
393
|
+
# Currently disabled (and untested) because it doesn't look like
|
|
394
|
+
# any of the HIPAA schemas would use this -- so testing it would
|
|
395
|
+
# be a pain.
|
|
396
|
+
#
|
|
397
|
+
distinct_elements << [[n, m], build_distinct(total, n, m, instructions)]
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
[disjoint_elements, distinct_elements]
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# @return [Hash<String, Array<Instruction>>]
|
|
407
|
+
def build_disjoint(total, n, m, instructions)
|
|
408
|
+
if total.finite?
|
|
409
|
+
# The sum of all allowed value sets is finite, so we know that each
|
|
410
|
+
# individual allowed value set is finite (we can iterate over it).
|
|
411
|
+
map = Hash.new
|
|
412
|
+
|
|
413
|
+
instructions.each do |i|
|
|
414
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
|
415
|
+
|
|
416
|
+
unless m.nil?
|
|
417
|
+
element_use = element_use.definition.component_uses.at(m)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
allowed_vals = element_use.allowed_values
|
|
421
|
+
allowed_vals.each{|v| map[v] = i.cons }
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
map
|
|
425
|
+
else
|
|
426
|
+
# At least one of allowed value sets is infinite. This happens when
|
|
427
|
+
# it is RelativeComplement, which declares the values that are *not*
|
|
428
|
+
# allowed in the set.
|
|
429
|
+
map = Hash.new{|h,k| h[k] = instructions }
|
|
430
|
+
|
|
431
|
+
instructions.each do |i|
|
|
432
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
|
433
|
+
unless m.nil?
|
|
434
|
+
element_use = element_use.definition.component_uses.at(m)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
allowed_vals = element_use.allowed_values
|
|
438
|
+
|
|
439
|
+
unless allowed_vals.finite?
|
|
440
|
+
allowed_vals.complement.each{|v| map[v] -= i }
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Clear the default_proc so accesses don't change the Hash
|
|
445
|
+
map.default = instructions
|
|
446
|
+
map
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# @return [Hash<String, Array<Instruction>>]
|
|
451
|
+
def build_distinct(total, n, m, instructions)
|
|
452
|
+
if total.finite?
|
|
453
|
+
# The sum of all allowed value sets is finite, so we know that each
|
|
454
|
+
# individual allowed value set is finite (we can iterate over it).
|
|
455
|
+
map = Hash.new{|h,k| h[k] = [] }
|
|
456
|
+
|
|
457
|
+
instructions.each do |i|
|
|
458
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
|
459
|
+
|
|
460
|
+
unless m.nil?
|
|
461
|
+
element_use = element_use.definition.component_uses.at(m)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
allowed_vals = element_use.allowed_values
|
|
465
|
+
allowed_vals.each{|v| map[v] << i }
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Clear the default_proc so accesses don't change the Hash
|
|
469
|
+
map.default = []
|
|
470
|
+
map
|
|
471
|
+
else
|
|
472
|
+
# At least one of allowed value sets is infinite. This happens when
|
|
473
|
+
# it is RelativeComplement, which declares the values that are *not*
|
|
474
|
+
# allowed in the set.
|
|
475
|
+
map = Hash.new{|h,k| h[k] = instructions }
|
|
476
|
+
|
|
477
|
+
instructions.each do |i|
|
|
478
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
|
479
|
+
|
|
480
|
+
unless m.nil?
|
|
481
|
+
element_use = element_use.definition.component_uses.at(m)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
allowed_vals = element_use.allowed_values
|
|
485
|
+
|
|
486
|
+
unless allowed_vals.finite?
|
|
487
|
+
allowed_vals.complement.each{|v| map[v] -= i }
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Clear the default_proc so accesses don't change the Hash
|
|
492
|
+
map.default = instructions
|
|
493
|
+
map
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Return the value of the `m`-th elemnt, or if `n` is not nil, return
|
|
498
|
+
# the value of the `n`-th component from the `n`-th element. When the
|
|
499
|
+
# value is blank, the function returns `nil`.
|
|
500
|
+
#
|
|
501
|
+
# @param [Array<Reader::SimpleElementTok, Reader::CompositeElementTok>] element_toks
|
|
502
|
+
# @param [Integer] m
|
|
503
|
+
# @param [Integer, nil] n
|
|
504
|
+
#
|
|
505
|
+
# @return [String, nil]
|
|
506
|
+
def deconstruct(element_toks, m, n)
|
|
507
|
+
element_tok = element_toks.at(m)
|
|
508
|
+
element_tok = element_tok.element_toks.at(0) if element_tok.try(:repeated?)
|
|
509
|
+
|
|
510
|
+
if element_tok.blank?
|
|
511
|
+
nil
|
|
512
|
+
elsif n.nil?
|
|
513
|
+
element_tok.value
|
|
514
|
+
else
|
|
515
|
+
element_tok = element_tok.component_toks.at(n)
|
|
516
|
+
|
|
517
|
+
if element_tok.blank?
|
|
518
|
+
nil
|
|
519
|
+
else
|
|
520
|
+
element_tok.value
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
class << ConstraintTable
|
|
528
|
+
# @group Constructors
|
|
529
|
+
#########################################################################
|
|
530
|
+
|
|
531
|
+
# Given a list of {Instruction} values for the same segment identifier,
|
|
532
|
+
# this method constructs the appropriate concrete subclass of
|
|
533
|
+
# {ConstraintTable}.
|
|
534
|
+
#
|
|
535
|
+
# @param [Array<Instruction>] instructions
|
|
536
|
+
# @return [ConstraintTable]
|
|
537
|
+
def build(instructions)
|
|
538
|
+
if instructions.length <= 1
|
|
539
|
+
ConstraintTable::Stub.new(instructions)
|
|
540
|
+
elsif instructions.any?{|i| i.segment_use.nil? } and
|
|
541
|
+
not instructions.all?{|i| i.segment_use.nil? }
|
|
542
|
+
# When one of the instructions has a nil segment_use, it means
|
|
543
|
+
# the SegmentUse is determined when pushing the new state. There
|
|
544
|
+
# isn't a way to know the segment constraints from here.
|
|
545
|
+
ConstraintTable::Stub.new(instructions)
|
|
546
|
+
else
|
|
547
|
+
segment_uses = instructions.map{|i| i.segment_use }
|
|
548
|
+
|
|
549
|
+
if segment_uses.map{|u| u.object_id }.uniq.length <= 1
|
|
550
|
+
# The same SegmentUse may appear more than once, because the
|
|
551
|
+
# segment can be placed at different levels in the tree. If
|
|
552
|
+
# all the instructions have the same SegmentUse, they also have
|
|
553
|
+
# the same element constraints so we can't use them to narrow
|
|
554
|
+
# down the instruction list.
|
|
555
|
+
ConstraintTable::Shallowest.new(instructions)
|
|
556
|
+
else
|
|
557
|
+
ConstraintTable::ValueBased.new(instructions)
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# @endgroup
|
|
563
|
+
#########################################################################
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|