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