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.
- checksums.yaml +4 -4
- data/lib/stupidedi/builder/builder_dsl.rb +5 -0
- data/lib/stupidedi/builder/constraint_table.rb +100 -74
- data/lib/stupidedi/builder/generation.rb +73 -70
- data/lib/stupidedi/builder/instruction.rb +10 -0
- data/lib/stupidedi/builder/instruction_table.rb +19 -7
- data/lib/stupidedi/builder/state_machine.rb +9 -3
- data/lib/stupidedi/builder/states/abstract_state.rb +1 -1
- data/lib/stupidedi/builder/states/failure_state.rb +1 -1
- data/lib/stupidedi/builder/states/initial_state.rb +4 -4
- data/lib/stupidedi/contrib/002001/guides/SH856.rb +4 -4
- data/lib/stupidedi/contrib/002001/transaction_set_defs/PO830.rb +2 -2
- data/lib/stupidedi/contrib/003010/guides/PS830.rb +4 -4
- data/lib/stupidedi/contrib/003010/guides/RA820.rb +22 -12
- data/lib/stupidedi/contrib/003010/transaction_set_defs/PC860.rb +2 -2
- data/lib/stupidedi/contrib/003010/transaction_set_defs/PS830.rb +1 -1
- data/lib/stupidedi/contrib/003050/guides/PO850.rb +2 -2
- data/lib/stupidedi/contrib/003050/transaction_set_defs/PO850.rb +2 -2
- data/lib/stupidedi/contrib/004010/guides.rb +0 -1
- data/lib/stupidedi/contrib/004010/transaction_set_defs/AR943.rb +1 -1
- data/lib/stupidedi/contrib/004010/transaction_set_defs/IM210.rb +2 -2
- data/lib/stupidedi/contrib/004010/transaction_set_defs/RE944.rb +1 -1
- data/lib/stupidedi/contrib/004010/transaction_set_defs/SH856.rb +1 -7
- data/lib/stupidedi/editor.rb +0 -1
- data/lib/stupidedi/guides/004010/guide_builder.rb +1 -1
- data/lib/stupidedi/guides/005010/X223-HC837I.rb +1192 -1195
- data/lib/stupidedi/guides/005010/guide_builder.rb +1 -1
- data/lib/stupidedi/schema.rb +1 -0
- data/lib/stupidedi/schema/auditor.rb +435 -0
- data/lib/stupidedi/schema/loop_def.rb +18 -1
- data/lib/stupidedi/schema/transaction_set_def.rb +12 -0
- data/lib/stupidedi/version.rb +1 -1
- data/lib/stupidedi/versions/functional_groups/004010/transaction_set_defs/HP835.rb +3 -17
- data/lib/stupidedi/versions/functional_groups/005010/element_types/time_val.rb +3 -2
- data/lib/stupidedi/versions/functional_groups/005010/segment_defs.rb +9 -6
- data/lib/stupidedi/versions/functional_groups/005010/transaction_set_defs/HB271.rb +25 -9
- data/lib/stupidedi/versions/functional_groups/005010/transaction_set_defs/HP835.rb +2 -2
- data/lib/stupidedi/zipper.rb +20 -1
- data/lib/stupidedi/zipper/memoized_cursor.rb +2 -0
- data/lib/stupidedi/zipper/path.rb +10 -0
- data/lib/stupidedi/zipper/root_cursor.rb +1 -1
- data/lib/stupidedi/zipper/stack_cursor.rb +174 -0
- data/spec/examples/stupidedi/audit_spec.rb +58 -0
- data/spec/spec_helper.rb +21 -13
- data/spec/support/rcov.rb +9 -4
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 837e13488c24a9cd85bc735feda78b9b59b3c04655389a6ee192c8be4098570d
|
4
|
+
data.tar.gz: 7f569b2a9ba3d81b7fc539e9c2ff1cb6a1693bca2e289c3b4ba23876b8263564
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
114
|
-
|
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
|
-
#
|
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
|
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
|
-
|
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
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
230
|
-
|
254
|
+
distinct = false
|
255
|
+
disjoint = true
|
231
256
|
|
232
|
-
|
233
|
-
|
257
|
+
instructions.each do |i|
|
258
|
+
element_use = i.segment_use.definition.element_uses.at(n)
|
234
259
|
|
235
|
-
|
236
|
-
|
237
|
-
|
260
|
+
unless m.nil?
|
261
|
+
element_use = element_use.definition.component_uses.at(m)
|
262
|
+
end
|
238
263
|
|
239
|
-
|
264
|
+
allowed_vals = element_use.allowed_values
|
240
265
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
279
|
+
total = allowed_vals.union(total)
|
280
|
+
last = allowed_vals
|
281
|
+
end
|
257
282
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
#
|
133
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
139
|
-
#
|
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
|