stupidedi 1.3.21 → 1.3.22

Sign up to get free protection for your applications and to get access to all the features.
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