stupidedi 1.3.21 → 1.3.22

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/stupidedi/builder/builder_dsl.rb +5 -0
  3. data/lib/stupidedi/builder/constraint_table.rb +100 -74
  4. data/lib/stupidedi/builder/generation.rb +73 -70
  5. data/lib/stupidedi/builder/instruction.rb +10 -0
  6. data/lib/stupidedi/builder/instruction_table.rb +19 -7
  7. data/lib/stupidedi/builder/state_machine.rb +9 -3
  8. data/lib/stupidedi/builder/states/abstract_state.rb +1 -1
  9. data/lib/stupidedi/builder/states/failure_state.rb +1 -1
  10. data/lib/stupidedi/builder/states/initial_state.rb +4 -4
  11. data/lib/stupidedi/contrib/002001/guides/SH856.rb +4 -4
  12. data/lib/stupidedi/contrib/002001/transaction_set_defs/PO830.rb +2 -2
  13. data/lib/stupidedi/contrib/003010/guides/PS830.rb +4 -4
  14. data/lib/stupidedi/contrib/003010/guides/RA820.rb +22 -12
  15. data/lib/stupidedi/contrib/003010/transaction_set_defs/PC860.rb +2 -2
  16. data/lib/stupidedi/contrib/003010/transaction_set_defs/PS830.rb +1 -1
  17. data/lib/stupidedi/contrib/003050/guides/PO850.rb +2 -2
  18. data/lib/stupidedi/contrib/003050/transaction_set_defs/PO850.rb +2 -2
  19. data/lib/stupidedi/contrib/004010/guides.rb +0 -1
  20. data/lib/stupidedi/contrib/004010/transaction_set_defs/AR943.rb +1 -1
  21. data/lib/stupidedi/contrib/004010/transaction_set_defs/IM210.rb +2 -2
  22. data/lib/stupidedi/contrib/004010/transaction_set_defs/RE944.rb +1 -1
  23. data/lib/stupidedi/contrib/004010/transaction_set_defs/SH856.rb +1 -7
  24. data/lib/stupidedi/editor.rb +0 -1
  25. data/lib/stupidedi/guides/004010/guide_builder.rb +1 -1
  26. data/lib/stupidedi/guides/005010/X223-HC837I.rb +1192 -1195
  27. data/lib/stupidedi/guides/005010/guide_builder.rb +1 -1
  28. data/lib/stupidedi/schema.rb +1 -0
  29. data/lib/stupidedi/schema/auditor.rb +435 -0
  30. data/lib/stupidedi/schema/loop_def.rb +18 -1
  31. data/lib/stupidedi/schema/transaction_set_def.rb +12 -0
  32. data/lib/stupidedi/version.rb +1 -1
  33. data/lib/stupidedi/versions/functional_groups/004010/transaction_set_defs/HP835.rb +3 -17
  34. data/lib/stupidedi/versions/functional_groups/005010/element_types/time_val.rb +3 -2
  35. data/lib/stupidedi/versions/functional_groups/005010/segment_defs.rb +9 -6
  36. data/lib/stupidedi/versions/functional_groups/005010/transaction_set_defs/HB271.rb +25 -9
  37. data/lib/stupidedi/versions/functional_groups/005010/transaction_set_defs/HP835.rb +2 -2
  38. data/lib/stupidedi/zipper.rb +20 -1
  39. data/lib/stupidedi/zipper/memoized_cursor.rb +2 -0
  40. data/lib/stupidedi/zipper/path.rb +10 -0
  41. data/lib/stupidedi/zipper/root_cursor.rb +1 -1
  42. data/lib/stupidedi/zipper/stack_cursor.rb +174 -0
  43. data/spec/examples/stupidedi/audit_spec.rb +58 -0
  44. data/spec/spec_helper.rb +21 -13
  45. data/spec/support/rcov.rb +9 -4
  46. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dca3cbe38e197d5e24eb127bb9ea501a7417e8ebdef6ce29c0841cd8a7f618f8
4
- data.tar.gz: d7d967d793ee60bed8b4f4794f1168a102c07369db5deb1814727ff14d6a4b4e
3
+ metadata.gz: 837e13488c24a9cd85bc735feda78b9b59b3c04655389a6ee192c8be4098570d
4
+ data.tar.gz: 7f569b2a9ba3d81b7fc539e9c2ff1cb6a1693bca2e289c3b4ba23876b8263564
5
5
  SHA512:
6
- metadata.gz: 075d7961db6721b5e60125c7a1fc98e58ed1a2847c55dce06c9ff138a0a0d745a95ca0d798e6c38923fbdfa250b60b6c026a4242fe308d7df170385b3b44ef9c
7
- data.tar.gz: a899f00431881d6edd7cbf083c551f70af676bff1cc9b21df8dcbd9bc41723bf8dab402484f3a028b79b05c7abcbec745c3b95dd854c10a0c9337d62e03eb632
6
+ metadata.gz: 501651cef1151a4f2121a3a026f7ebf5ed62eec5cd021429ad8190f6ea4d6b286df817f2bda6a6f100326a7e78121484aa6839f5545c41799607144fd83d372c
7
+ data.tar.gz: 847782f2907e2caf81dd00f39b74b16a9c9f84ff70ea7e69014c6374d5ac9a60e9b7f028376a55c72bdc2fe9d91f2dcb48299d855e385381f7f89ed415dfa7bf
@@ -18,6 +18,9 @@ module Stupidedi
18
18
  # @return [Boolean]
19
19
  attr_writer :strict
20
20
 
21
+ # @return [DslReader]
22
+ attr_reader :reader
23
+
21
24
  def_delegators :@machine, :pretty_print, :segment, :element, :zipper, :successors, :empty?, :first?, :last?, :deterministic?
22
25
 
23
26
  def initialize(machine, strict = true)
@@ -80,6 +83,8 @@ module Stupidedi
80
83
  while p.respond_to?(:parent)
81
84
  break if qancestors.include?(p)
82
85
 
86
+ # Now that we're sure that this syntax node (loop, table, etc)
87
+ # is done being constructed, we can perform more validations.
83
88
  critique(p)
84
89
  p = p.parent
85
90
  end
@@ -24,6 +24,9 @@ module Stupidedi
24
24
  # @return [Array<Instruction>]
25
25
  abstract :matches, :args => %w(segment_tok)
26
26
 
27
+ # @return [Array<Instruction>]
28
+ attr_reader :instructions
29
+
27
30
  #
28
31
  # Performs no filtering of the {Instruction} list. This is used when there
29
32
  # already is a single {Instruction} or when a {Reader::SegmentTok} doesn't
@@ -107,21 +110,25 @@ module Stupidedi
107
110
 
108
111
  # @return [Array<Instruction>]
109
112
  def matches(segment_tok, strict)
110
- invalid = true # Were all possibly distinguishing elements invalid?
113
+ invalid = true # Were all present possibly distinguishing elements invalid?
111
114
  present = false # Were any possibly distinguishing elements present?
112
115
 
113
- @__basis ||= basis(shallowest(@instructions))
114
- @__basis.head.each do |(n, m), map|
116
+ disjoint, distinct = basis(@instructions)
117
+
118
+ # First check single elements that can narrow the search space to
119
+ # a single matching Instruction.
120
+ disjoint.each do |(n, m), map|
115
121
  value = deconstruct(segment_tok.element_toks, n, m)
116
122
 
117
123
  case value
118
124
  when nil, :not_used, :default
119
- # ignore
125
+ # value wasn't present in segment_tok, can't use it to decide
120
126
  else
121
127
  singleton = map.at(value)
122
128
  present = true
123
129
 
124
130
  unless singleton.nil?
131
+ # Success, search is terminated
125
132
  return singleton
126
133
  else
127
134
  if strict
@@ -140,13 +147,14 @@ module Stupidedi
140
147
  # the combination of elements to iteratively narrow the search space
141
148
  space = @instructions
142
149
 
143
- # @todo: These filters should be ordered by probable effectiveness,
150
+ # @todo: These filters could be ordered by probable effectiveness,
144
151
  # so we narrow the search space by the largest amount in the fewest
145
152
  # number of steps.
146
- @__basis.last.each do |(n, m), map|
153
+ distinct.each do |(n, m), map|
147
154
  value = deconstruct(segment_tok.element_toks, n, m)
148
155
 
149
156
  unless value.nil?
157
+ # Lookup which instructions are compatible with this input
150
158
  subset = map.at(value)
151
159
  present = true
152
160
 
@@ -155,9 +163,11 @@ module Stupidedi
155
163
  space &= subset
156
164
 
157
165
  if space.length <= 1
166
+ # Success, search is terminated
158
167
  return space
159
168
  end
160
169
  else
170
+ # This value isn't compatible with any instruction
161
171
  if strict
162
172
  designator = "#{segment_tok.id}#{'%02d' % n}"
163
173
  designator = designator + "-%02d" % m unless m.nil?
@@ -170,14 +180,27 @@ module Stupidedi
170
180
  end
171
181
 
172
182
  if invalid and present
183
+ # Some elements were present, but all contained invalid values, and
184
+ # even ignoring those we could not narrow the matches to a single
185
+ # instruction.
186
+ #
187
+ # We could return the remaining search space, but it is safest to
188
+ # mark this as an invalid segment and avoid the non-determinism
173
189
  []
174
190
  else
191
+ # Some elements were present and none were invalid, but it was not
192
+ # possible to narrow the set of matches to a single instruction.
193
+ #
194
+ # It seems wrong to mark the segment invalid. The worst thing you
195
+ # could say about the input is that it may have been missing a
196
+ # required element that could have resolved the ambiguity; but it's
197
+ # also possible all required elements were present and the grammar
198
+ # is the problem (not the input). So here we return the remaining
199
+ # search space, which will cause non-determinism in the parser.
175
200
  space
176
201
  end
177
202
  end
178
203
 
179
- private
180
-
181
204
  # Resolve conflicts between instructions that have identical SegmentUse
182
205
  # values. For each SegmentUse, this chooses the Instruction that pops
183
206
  # the greatest number of states.
@@ -203,84 +226,87 @@ module Stupidedi
203
226
 
204
227
  # @return [Array(Array<(Integer, Integer, Map)>, Array<(Integer, Integer, Map)>)]
205
228
  def basis(instructions)
206
- disjoint_elements = []
207
- distinct_elements = []
208
-
209
- # The first SegmentUse is used to represent the structure that must
210
- # be shared by the others: number of elements and type of elements
211
- element_uses = instructions.head.segment_use.definition.element_uses
212
-
213
- # Iterate over each element across all SegmentUses (think columns)
214
- # NM1*[IL]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
215
- # NM1*[40]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
216
- element_uses.length.times do |n|
217
- if element_uses.at(n).composite?
218
- ms = 0 .. element_uses.at(n).definition.component_uses.length - 1
219
- else
220
- ms = [nil]
221
- end
229
+ @__basis ||= begin
230
+ instructions = shallowest(instructions)
231
+ disjoint_elements = []
232
+ distinct_elements = []
233
+
234
+ # The first SegmentUse is used to represent the structure that must
235
+ # be shared by the others: number of elements and type of elements
236
+ element_uses = instructions.head.segment_use.definition.element_uses
237
+
238
+ # Iterate over each element across all SegmentUses (think columns)
239
+ # NM1*[IL]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
240
+ # NM1*[40]*[ ]*..*..*..*..*..*[ ]*..*..*{..}*..
241
+ element_uses.length.times do |n|
242
+ if element_uses.at(n).composite?
243
+ ms = 0 .. element_uses.at(n).definition.component_uses.length - 1
244
+ else
245
+ ms = [nil]
246
+ end
222
247
 
223
- # If this is a composite element, we iterate over each component.
224
- # Otherwise this loop iterates once with the index {m} set to nil.
225
- ms.each do |m|
226
- last = nil # the last subset we examined
227
- total = Sets.empty # the union of all examined subsets
248
+ # If this is a composite element, we iterate over each component.
249
+ # Otherwise this loop iterates once with the index {m} set to nil.
250
+ ms.each do |m|
251
+ last = nil # the last subset we examined
252
+ total = Sets.empty # the union of all examined subsets
228
253
 
229
- distinct = false
230
- disjoint = true
254
+ distinct = false
255
+ disjoint = true
231
256
 
232
- instructions.each do |i|
233
- element_use = i.segment_use.definition.element_uses.at(n)
257
+ instructions.each do |i|
258
+ element_use = i.segment_use.definition.element_uses.at(n)
234
259
 
235
- unless m.nil?
236
- element_use = element_use.definition.component_uses.at(m)
237
- end
260
+ unless m.nil?
261
+ element_use = element_use.definition.component_uses.at(m)
262
+ end
238
263
 
239
- allowed_vals = element_use.allowed_values
264
+ allowed_vals = element_use.allowed_values
240
265
 
241
- # We want to know if every instruction's set of allowed values
242
- # is disjoint (with one another). Instead of comparing each set
243
- # with every other set, which takes (N-1)! comparisons, we can
244
- # do it in N steps.
245
- disjoint &&= allowed_vals.disjoint?(total)
266
+ # We want to know if every instruction's set of allowed values
267
+ # is disjoint (with one another). Instead of comparing each set
268
+ # with every other set, which takes (N-1)! comparisons, we can
269
+ # do it in N steps.
270
+ disjoint &&= allowed_vals.disjoint?(total)
246
271
 
247
- # We also want to know if one instruction's set of allowed vals
248
- # contains elements that aren't present in at least one other
249
- # set. The opposite condition is easy to test: all sets contain
250
- # the same elements (are equal). So we can similarly, check this
251
- # condition in N steps rather than (N-1)!
252
- distinct ||= allowed_vals != last unless last.nil?
272
+ # We also want to know if one instruction's set of allowed vals
273
+ # contains elements that aren't present in at least one other
274
+ # set. The opposite condition is easy to test: all sets contain
275
+ # the same elements (are equal). So we can similarly, check this
276
+ # condition in N steps rather than (N-1)!
277
+ distinct ||= allowed_vals != last unless last.nil?
253
278
 
254
- total = allowed_vals.union(total)
255
- last = allowed_vals
256
- end
279
+ total = allowed_vals.union(total)
280
+ last = allowed_vals
281
+ end
257
282
 
258
- # puts "#{n}.#{m}: disjoint(#{disjoint}) distinct(#{distinct})"
259
-
260
- if disjoint
261
- # Since each instruction's set of allowed values is disjoint, we
262
- # can build a function/hash that returns the single instruction,
263
- # given one of the values. When given a value outside the set of
264
- # all (combined) values, it returns nil.
265
- disjoint_elements << [[n, m], build_disjoint(total, n, m, instructions)]
266
- elsif distinct
267
- # Not all instructions have the same set of allowed values. So
268
- # we can build a function/hash that accepts one of the values
269
- # and returns the subset of the instructions where that value
270
- # can occur. This might be some, none, or all of the original
271
- # instructions, so clearly this provides less information than
272
- # if each allowed value set was disjoint.
273
-
274
- # Currently disabled (and untested) because it doesn't look like
275
- # any of the HIPAA schemas would use this -- so testing it would
276
- # be a pain.
277
- #
278
- distinct_elements << [[n, m], build_distinct(total, n, m, instructions)]
283
+ # puts "#{n}.#{m}: disjoint(#{disjoint}) distinct(#{distinct})"
284
+
285
+ if disjoint
286
+ # Since each instruction's set of allowed values is disjoint, we
287
+ # can build a function/hash that returns the single instruction,
288
+ # given one of the values. When given a value outside the set of
289
+ # all (combined) values, it returns nil.
290
+ disjoint_elements << [[n, m], build_disjoint(total, n, m, instructions)]
291
+ elsif distinct
292
+ # Not all instructions have the same set of allowed values. So
293
+ # we can build a function/hash that accepts one of the values
294
+ # and returns the subset of the instructions where that value
295
+ # can occur. This might be some, none, or all of the original
296
+ # instructions, so clearly this provides less information than
297
+ # if each allowed value set was disjoint.
298
+
299
+ # Currently disabled (and untested) because it doesn't look like
300
+ # any of the HIPAA schemas would use this -- so testing it would
301
+ # be a pain.
302
+ #
303
+ distinct_elements << [[n, m], build_distinct(total, n, m, instructions)]
304
+ end
279
305
  end
280
306
  end
281
- end
282
307
 
283
- [disjoint_elements, distinct_elements]
308
+ [disjoint_elements, distinct_elements]
309
+ end
284
310
  end
285
311
 
286
312
  # @return [Hash<String, Array<Instruction>>]
@@ -54,89 +54,92 @@ module Stupidedi
54
54
 
55
55
  # @return [(StateMachine, Reader::TokenReader)]
56
56
  def insert(segment_tok, reader)
57
- active = []
58
-
59
- @active.each do |zipper|
57
+ active = @active.flat_map do |zipper|
60
58
  state = zipper.node
61
59
  instructions = state.instructions.matches(segment_tok)
62
60
 
63
61
  if instructions.empty?
64
- active << zipper.append(FailureState.mksegment(segment_tok, state))
65
- next
66
- end
67
-
68
- instructions.each do |op|
69
- if op.push.nil?
70
- table = zipper.node.instructions
71
- value = zipper.node.zipper
72
- state = zipper
73
-
74
- op.pop_count.times do
75
- value = value.up
76
- state = state.up
77
- end
78
-
79
- # Create a new AbstractState node that has a new InstructionTable
80
- # and also points to a new AbstractVal tree (with the new segment)
81
- segment = AbstractState.mksegment(segment_tok, op.segment_use)
82
- successor = state.append(state.node.copy(
83
- :zipper => value.append(segment),
84
- :instructions => table.pop(op.pop_count).drop(op.drop_count)))
85
-
86
- unless op.pop_count.zero? or reader.stream?
87
- # More general than checking if segment_tok is an ISE/GE segment
88
- unless reader.separators.eql?(successor.node.separators) \
89
- and reader.segment_dict.eql?(successor.node.segment_dict)
90
- reader = reader.copy \
91
- :separators => successor.node.separators,
92
- :segment_dict => successor.node.segment_dict
93
- end
94
- end
95
- else
96
- table = zipper.node.instructions
97
- value = zipper.node.zipper
98
- state = zipper
99
-
100
- op.pop_count.times do
101
- value = value.up
102
- state = state.up
103
- end
104
-
105
- parent = state.node.copy \
106
- :zipper => value,
107
- :children => [],
108
- :separators => reader.separators,
109
- :segment_dict => reader.segment_dict,
110
- :instructions => table.pop(op.pop_count).drop(op.drop_count)
111
-
112
- state = state.append(parent) unless state.root?
113
-
114
- successor = op.push.push(state, parent, segment_tok, op.segment_use, @config)
115
-
116
- # More general than checking if segment_tok is an ISA/GS segment
117
- unless reader.separators.eql?(successor.node.separators) \
118
- and reader.segment_dict.eql?(successor.node.segment_dict)
119
- reader = reader.copy \
120
- :separators => successor.node.separators,
121
- :segment_dict => successor.node.segment_dict
122
- end
62
+ zipper.append(FailureState.mksegment(segment_tok, state)).cons
63
+ else
64
+ instructions.map do |op|
65
+ successor = execute(op, zipper, reader, segment_tok)
66
+ reader = update_reader(op, reader, successor)
67
+ successor
123
68
  end
124
-
125
- active << successor
126
69
  end
127
70
  end
128
71
 
129
72
  return StateMachine.new(@config, active), reader
130
73
  end
131
74
 
132
- # @return [StateMachine]
133
- def replace(segment_tok, reader)
134
- # @todo
75
+ # Three things change together when executing an {Instruction}:
76
+ #
77
+ # 1. The stack of instruction tables that indicates where a segment
78
+ # would be located if it existed, or was added to the parse tree
79
+ #
80
+ # 2. The parse tree, to which we add the new syntax nodes using a
81
+ # zipper.
82
+ #
83
+ # 3. The corresponding tree of states, which tie together the first
84
+ # two and are also updated using a zipper
85
+ #
86
+ # @return [AbstractCursor<StateMachine>]
87
+ def execute(op, zipper, reader, segment_tok)
88
+ table = zipper.node.instructions # 1.
89
+ value = zipper.node.zipper # 2.
90
+ state = zipper # 3.
91
+
92
+ op.pop_count.times do
93
+ value = value.up
94
+ state = state.up
95
+ end
96
+
97
+ if op.push.nil?
98
+ # This instruction doesn't create a child node in the parse tree,
99
+ # but it might move us forward to a sibling or upward to an uncle
100
+ segment = AbstractState.mksegment(segment_tok, op.segment_use)
101
+ value = value.append(segment)
102
+
103
+ # If we're moving upward, pop off the current table(s). If we're
104
+ # moving forward, shift off the previous instructions. Important
105
+ # that these are done in order.
106
+ instructions = table.pop(op.pop_count).drop(op.drop_count)
107
+
108
+ # Create a new AbstractState node that has a new InstructionTable
109
+ # and also points to a new AbstractVal tree (with the new segment)
110
+ state.append(state.node.copy(
111
+ :zipper => value,
112
+ :instructions => instructions))
113
+ else
114
+ # Make a new sibling or uncle that will be the parent to the child
115
+ parent = state.node.copy \
116
+ :zipper => value,
117
+ :children => [],
118
+ :separators => reader.separators,
119
+ :segment_dict => reader.segment_dict,
120
+ :instructions => table.pop(op.pop_count).drop(op.drop_count)
121
+
122
+ # Note, `state` is a cursor pointing at a state, while `parent`
123
+ # is an actual state
124
+ state = state.append(parent) unless state.root?
125
+
126
+ # Note, eg, op.push == TableState; op.push.push == TableState.push
127
+ op.push.push(state, parent, segment_tok, op.segment_use, @config)
128
+ end
135
129
  end
136
130
 
137
- # @return [StateMachine]
138
- def remove
139
- # @todo
131
+ def update_reader(op, reader, successor)
132
+ # We might be moving up or down past the interchange or functional
133
+ # group envelope, which control the set of separators and the set
134
+ # of all possible segments
135
+ unless op.push.nil? and (op.pop_count.zero? or reader.stream?)
136
+ unless reader.separators.eql?(successor.node.separators) \
137
+ and reader.segment_dict.eql?(successor.node.segment_dict)
138
+ reader.copy \
139
+ :separators => successor.node.separators,
140
+ :segment_dict => successor.node.segment_dict
141
+ end
142
+ end || reader
140
143
  end
141
144
 
142
145
  end
@@ -57,6 +57,10 @@ module Stupidedi
57
57
  segment_id || segment_use.definition.id, segment_use, pop, drop, push
58
58
  end
59
59
 
60
+ def hash
61
+ [Instruction, state].hash
62
+ end
63
+
60
64
  # @return [Instruction]
61
65
  def copy(changes = {})
62
66
  Instruction.new \
@@ -100,6 +104,12 @@ module Stupidedi
100
104
  end
101
105
  end
102
106
  end
107
+
108
+ private
109
+
110
+ def state
111
+ [@segment_id, @segment_use, @pop_count, @drop_count, @push].hash
112
+ end
103
113
  end
104
114
 
105
115
  end