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.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +319 -0
  3. data/LICENSE +26 -0
  4. data/README.md +308 -0
  5. data/Rakefile +33 -0
  6. data/bin/tediparse +102 -0
  7. data/doc/Defining.md +97 -0
  8. data/doc/Generating-Grammars.md +241 -0
  9. data/doc/Generating.md +332 -0
  10. data/doc/Navigating.md +670 -0
  11. data/doc/Parsing.md +133 -0
  12. data/doc/README.md +51 -0
  13. data/doc/Serializing.md +100 -0
  14. data/doc/Tokenizing.md +136 -0
  15. data/doc/Validating.md +140 -0
  16. data/lib/ruby/array.rb +226 -0
  17. data/lib/ruby/blank.rb +52 -0
  18. data/lib/ruby/exception.rb +14 -0
  19. data/lib/ruby/hash.rb +14 -0
  20. data/lib/ruby/module.rb +60 -0
  21. data/lib/ruby/object.rb +56 -0
  22. data/lib/ruby/string.rb +89 -0
  23. data/lib/ruby/to_d.rb +82 -0
  24. data/lib/ruby/to_date.rb +26 -0
  25. data/lib/ruby/to_time.rb +21 -0
  26. data/lib/ruby/try.rb +46 -0
  27. data/lib/stupidedi/builder.rb +6 -0
  28. data/lib/stupidedi/color.rb +94 -0
  29. data/lib/stupidedi/config/code_list_config.rb +45 -0
  30. data/lib/stupidedi/config/functional_group_config.rb +72 -0
  31. data/lib/stupidedi/config/interchange_config.rb +86 -0
  32. data/lib/stupidedi/config/transaction_set_config.rb +73 -0
  33. data/lib/stupidedi/config.rb +100 -0
  34. data/lib/stupidedi/either.rb +286 -0
  35. data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
  36. data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
  37. data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
  38. data/lib/stupidedi/exceptions/output_error.rb +7 -0
  39. data/lib/stupidedi/exceptions/parse_error.rb +7 -0
  40. data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
  41. data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
  42. data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
  43. data/lib/stupidedi/exceptions.rb +13 -0
  44. data/lib/stupidedi/inspect.rb +26 -0
  45. data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
  46. data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
  47. data/lib/stupidedi/interchanges/element_types.rb +11 -0
  48. data/lib/stupidedi/interchanges.rb +21 -0
  49. data/lib/stupidedi/parser/builder_dsl.rb +292 -0
  50. data/lib/stupidedi/parser/constraint_table.rb +566 -0
  51. data/lib/stupidedi/parser/generation.rb +147 -0
  52. data/lib/stupidedi/parser/identifier_stack.rb +248 -0
  53. data/lib/stupidedi/parser/instruction.rb +112 -0
  54. data/lib/stupidedi/parser/instruction_table.rb +222 -0
  55. data/lib/stupidedi/parser/navigation.rb +786 -0
  56. data/lib/stupidedi/parser/state_machine.rb +62 -0
  57. data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
  58. data/lib/stupidedi/parser/states/failure_state.rb +69 -0
  59. data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
  60. data/lib/stupidedi/parser/states/initial_state.rb +62 -0
  61. data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
  62. data/lib/stupidedi/parser/states/loop_state.rb +74 -0
  63. data/lib/stupidedi/parser/states/table_state.rb +90 -0
  64. data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
  65. data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
  66. data/lib/stupidedi/parser/tokenization.rb +195 -0
  67. data/lib/stupidedi/parser.rb +32 -0
  68. data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
  69. data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
  70. data/lib/stupidedi/reader/input/file_input.rb +157 -0
  71. data/lib/stupidedi/reader/input.rb +31 -0
  72. data/lib/stupidedi/reader/position.rb +78 -0
  73. data/lib/stupidedi/reader/result.rb +172 -0
  74. data/lib/stupidedi/reader/segment_dict.rb +176 -0
  75. data/lib/stupidedi/reader/separators.rb +90 -0
  76. data/lib/stupidedi/reader/stream_reader.rb +173 -0
  77. data/lib/stupidedi/reader/token_reader.rb +465 -0
  78. data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
  79. data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
  80. data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
  81. data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
  82. data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
  83. data/lib/stupidedi/reader.rb +121 -0
  84. data/lib/stupidedi/schema/abstract_def.rb +76 -0
  85. data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
  86. data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
  87. data/lib/stupidedi/schema/abstract_use.rb +79 -0
  88. data/lib/stupidedi/schema/code_list.rb +99 -0
  89. data/lib/stupidedi/schema/component_element_use.rb +76 -0
  90. data/lib/stupidedi/schema/composite_element_def.rb +103 -0
  91. data/lib/stupidedi/schema/composite_element_use.rb +78 -0
  92. data/lib/stupidedi/schema/element_req.rb +57 -0
  93. data/lib/stupidedi/schema/functional_group_def.rb +124 -0
  94. data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
  95. data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
  96. data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
  97. data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
  98. data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
  99. data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
  100. data/lib/stupidedi/schema/generation/models.rb +85 -0
  101. data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
  102. data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
  103. data/lib/stupidedi/schema/generation/runner.rb +161 -0
  104. data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
  105. data/lib/stupidedi/schema/generation/support.rb +78 -0
  106. data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
  107. data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
  108. data/lib/stupidedi/schema/generation.rb +94 -0
  109. data/lib/stupidedi/schema/interchange_def.rb +103 -0
  110. data/lib/stupidedi/schema/loop_def.rb +156 -0
  111. data/lib/stupidedi/schema/repeat_count.rb +86 -0
  112. data/lib/stupidedi/schema/segment_def.rb +122 -0
  113. data/lib/stupidedi/schema/segment_req.rb +46 -0
  114. data/lib/stupidedi/schema/segment_use.rb +99 -0
  115. data/lib/stupidedi/schema/simple_element_def.rb +51 -0
  116. data/lib/stupidedi/schema/simple_element_use.rb +83 -0
  117. data/lib/stupidedi/schema/syntax_note.rb +52 -0
  118. data/lib/stupidedi/schema/table_def.rb +178 -0
  119. data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
  120. data/lib/stupidedi/schema.rb +30 -0
  121. data/lib/stupidedi/sets.rb +42 -0
  122. data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
  123. data/lib/stupidedi/transaction_sets/builder.rb +188 -0
  124. data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
  125. data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
  126. data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
  127. data/lib/stupidedi/transaction_sets/common.rb +8 -0
  128. data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
  129. data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
  130. data/lib/stupidedi/transaction_sets/validation.rb +9 -0
  131. data/lib/stupidedi/transaction_sets.rb +25 -0
  132. data/lib/stupidedi/values/abstract_element_val.rb +19 -0
  133. data/lib/stupidedi/values/abstract_val.rb +133 -0
  134. data/lib/stupidedi/values/composite_element_val.rb +102 -0
  135. data/lib/stupidedi/values/functional_group_val.rb +105 -0
  136. data/lib/stupidedi/values/interchange_val.rb +102 -0
  137. data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
  138. data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
  139. data/lib/stupidedi/values/loop_val.rb +73 -0
  140. data/lib/stupidedi/values/repeated_element_val.rb +113 -0
  141. data/lib/stupidedi/values/segment_val.rb +105 -0
  142. data/lib/stupidedi/values/segment_val_group.rb +20 -0
  143. data/lib/stupidedi/values/simple_element_val.rb +80 -0
  144. data/lib/stupidedi/values/table_val.rb +69 -0
  145. data/lib/stupidedi/values/transaction_set_val.rb +69 -0
  146. data/lib/stupidedi/values/transmission_val.rb +56 -0
  147. data/lib/stupidedi/values.rb +22 -0
  148. data/lib/stupidedi/version.rb +4 -0
  149. data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
  150. data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
  151. data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
  152. data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
  153. data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
  154. data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
  155. data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
  156. data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
  157. data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
  158. data/lib/stupidedi/versions/common/element_types.rb +29 -0
  159. data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
  160. data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
  161. data/lib/stupidedi/versions/common.rb +11 -0
  162. data/lib/stupidedi/versions.rb +25 -0
  163. data/lib/stupidedi/writer/claredi.rb +178 -0
  164. data/lib/stupidedi/writer/default.rb +119 -0
  165. data/lib/stupidedi/writer.rb +7 -0
  166. data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
  167. data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
  168. data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
  169. data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
  170. data/lib/stupidedi/zipper/path.rb +132 -0
  171. data/lib/stupidedi/zipper/root_cursor.rb +120 -0
  172. data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
  173. data/lib/stupidedi/zipper.rb +45 -0
  174. data/lib/stupidedi.rb +69 -0
  175. data/lib/tediparse.rb +1 -0
  176. metadata +249 -0
@@ -0,0 +1,786 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Parser
6
+ module Navigation
7
+ #########################################################################
8
+ # @group Querying the Current Position
9
+
10
+ # @return [Array<InstructionTable>]
11
+ def successors
12
+ @active.map{|a| a.node.instructions }
13
+ end
14
+
15
+ # Is there exactly one valid parse tree in the current state?
16
+ def deterministic?
17
+ @active.length == 1
18
+ end
19
+
20
+ def empty?
21
+ value = @active.head.node.zipper
22
+
23
+ until value.root?
24
+ value = value.up
25
+ end
26
+
27
+ value.node.children.empty?
28
+ end
29
+
30
+ # Is this the first segment?
31
+ def first?
32
+ value = @active.head.node.zipper
33
+
34
+ until value.root?
35
+ return false unless value.first?
36
+ value = value.up
37
+ end
38
+
39
+ return true
40
+ end
41
+
42
+ # Is this the last segment?
43
+ def last?
44
+ value = @active.head.node.zipper
45
+
46
+ until value.root?
47
+ return false unless value.last?
48
+ value = value.up
49
+ end
50
+
51
+ return true
52
+ end
53
+
54
+ # Returns the number of segments apart the current state is from the
55
+ # given `StateMachine`'s state. Note the direction is not indicated by
56
+ # the return value, so `a.distance(b) == b.distance(a)` for all states
57
+ # `a` and `b`.
58
+ #
59
+ # @example
60
+ # m.distance(m) #=> Either.success(0)
61
+ # m.next(10).flatmap{|n| n.distance(m) } #=> Either.success(10)
62
+ #
63
+ # @return [Either<Integer>]
64
+ #
65
+ # @note This method uses AbstractCursor#between, which assumes the two
66
+ # cursors point to the same tree. If that is not the case, the results
67
+ # are undefined.
68
+ def distance(other)
69
+ zipper.flatmap do |a|
70
+ other.zipper.map do |b|
71
+ a.between(b).count(&:segment?) - 1
72
+ end
73
+ end
74
+ end
75
+
76
+ # @group Accessing the Current Node
77
+ #########################################################################
78
+
79
+ # Returns the current position within the parse tree, if the current state
80
+ # is deterministic.
81
+ #
82
+ # @return [Either<Zipper::AbstractCursor<Values::AbstractVal>>]
83
+ def zipper
84
+ if deterministic?
85
+ Either.success(@active.head.node.zipper)
86
+ else
87
+ Either.failure("non-deterministic state")
88
+ end
89
+ end
90
+
91
+ # Extracts the segment from the current state, if the current state is
92
+ # deterministic and positioned on a segment.
93
+ #
94
+ # @return [Either<Zipper::AbstractCursor<Values::SegmentVal>>]
95
+ def segment
96
+ zipper.flatmap do |z|
97
+ if z.node.segment?
98
+ Either.success(z)
99
+ else
100
+ Either.failure("not a segment")
101
+ end
102
+ end
103
+ end
104
+
105
+ # Extracts the segment from the current state, if the current state is
106
+ # deterministic and positioned on a segment.
107
+ #
108
+ # @return [Either<Values::SegmentVal>>]
109
+ def segmentn
110
+ segment.map(&:node)
111
+ end
112
+
113
+ # Extracts the *mth* element from the current segment, if the current
114
+ # state is deterministic. Accepts optional arguments to extract a specific
115
+ # occurrence of a repeated element and/or a specific component from a
116
+ # composite element.
117
+ #
118
+ # @return [Either<Zipper::AbstractCursor<Values::AbstractElementVal>>]
119
+ def element(m, n = nil, o = nil)
120
+ if m <= 0 or (n || 1) <= 0 or (o || 1) <= 0
121
+ raise ArgumentError,
122
+ "all arguments must be positive"
123
+ end
124
+
125
+ if n.nil? and not o.nil?
126
+ raise ArgumentError,
127
+ "third argument cannot be present unless second argument is present"
128
+ end
129
+
130
+ segment.flatmap do |s|
131
+ if s.node.invalid?
132
+ # InvalidSegmentVal doesn't have child AbstractElementVals, its
133
+ # children are SimpleElementTok, CompositeElementTok, etc, which
134
+ # are not parsed values.
135
+ return Either.failure("invalid segment")
136
+ end
137
+
138
+ segment_id = s.node.id.to_s
139
+ segment_def = s.node.definition
140
+ descriptor = segment_id
141
+
142
+ unless m <= segment_def.element_uses.length
143
+ raise ArgumentError,
144
+ "segment #{descriptor} has only #{segment_def.element_uses.length} elements"
145
+ end
146
+
147
+ element_use = segment_def.element_uses.at(m - 1)
148
+ element_def = element_use.definition
149
+ element_zip = s.child(m - 1)
150
+
151
+ if n.nil?
152
+ return Either.success(element_zip)
153
+
154
+ elsif element_use.composite? and not element_use.repeatable?
155
+ # m: element of segment
156
+ # n: component of composite element
157
+ # o: occurence of repeated component (commented-out below)
158
+ descriptor = "%s%02d" % [segment_id, m]
159
+ components = element_def.component_uses.length
160
+ unless n <= components
161
+ raise ArgumentError,
162
+ "composite element #{descriptor} only has #{components} components"
163
+ end
164
+
165
+ # component_use = element_def.component_uses.at(n - 1)
166
+
167
+ if o.nil?
168
+ if element_zip.node.blank?
169
+ Either.failure("#{descriptor} is empty")
170
+ else
171
+ # This is a component of a composite element
172
+ Either.success(element_zip.child(n - 1))
173
+ end
174
+
175
+ # @todo: There currently doesn't seem to be any instances of this in
176
+ # the real world (a composite element that has a component that can
177
+ # repeat), but perhaps this will happen in the future.
178
+ #
179
+ # elsif component_use.repeatable?
180
+ # repeat_count = component_use.repeat_count
181
+ # occurs_count = component_val.children.length
182
+ # descriptor = "%s%02d-%02d" % [segment_id, m, n]
183
+ # unless repeat_count.include?(o)
184
+ # raise ArgumentError,
185
+ # "repeatable component element #{descriptor} can only occur #{repeat_count.max} times"
186
+ # end
187
+ # component_zip = element_zip.child(n - 1)
188
+ # if component_zip.node.blank?
189
+ # return Either.failure("repeating component element #{descriptor} is blank")
190
+ # elsif occurs_count < n
191
+ # return Either.failure("repeating component element #{descriptor} only occurs #{occurs_count} times")
192
+ # else
193
+ # return Either.success(component_zip.child(o - 1))
194
+ # end
195
+
196
+ else
197
+ descriptor = "%s%02d-%02d" % [segment_id, m, n]
198
+ raise ArgumentError,
199
+ "component element #{descriptor} cannot be further deconstructed"
200
+ end
201
+
202
+ elsif element_use.repeatable?
203
+ # m: element of segment
204
+ # n: occurence of repeated element
205
+ # o: component of composite element
206
+ descriptor = "%s%02d" % [segment_id, m]
207
+ occurs_count = element_zip.children.count
208
+ unless element_use.repeat_count.include?(n)
209
+ raise ArgumentError,
210
+ "repeatable element #{descriptor} can only occur #{element_use.repeat_count.max} times"
211
+ end
212
+
213
+ if o.nil?
214
+ description = (element_use.composite?) ? "repeatable composite" : "repeatable"
215
+ if element_zip.node.blank?
216
+ Either.failure("#{description} element #{descriptor} does not occur")
217
+ elsif occurs_count < n
218
+ Either.failure("#{description} element #{descriptor} only occurs #{occurs_count} times")
219
+ else
220
+ Either.success(element_zip.child(n - 1))
221
+ end
222
+
223
+ elsif element_use.composite?
224
+ components = element_def.component_uses.length
225
+ unless o <= components
226
+ raise ArgumentError,
227
+ "repeatable composite element #{descriptor} only has #{components} components"
228
+ end
229
+
230
+ descriptor = "%s%02d" % [segment_id, m]
231
+
232
+ if element_zip.node.blank?
233
+ Either.failure("repeatable composite element #{descriptor} does not occur")
234
+ elsif occurs_count < n
235
+ Either.failure("repeatable composite element #{descriptor} only occurs #{occurs_count} times")
236
+ else
237
+ component_zip = element_zip.children.at(n - 1)
238
+ Either.success(component_zip.child(o - 1))
239
+ end
240
+
241
+ else
242
+ raise ArgumentError,
243
+ "repeatable element #{descriptor} cannot be further deconstructed"
244
+ end
245
+
246
+ else
247
+ raise ArgumentError,
248
+ "#{segment_id}#{"%02d" % m} is not a composite or repeated element"
249
+ end
250
+ end
251
+ end
252
+
253
+ # Extracts the *mth* element from the current segment, if the current
254
+ # state is deterministic. Accepts optional arguments to extract a specific
255
+ # occurrence of a repeated element and/or a specific component from a
256
+ # composite element.
257
+ #
258
+ # @return [Either<Values::AbstractElementVal>]
259
+ def elementn(m, n = nil, o = nil)
260
+ element(m, n, o).map(&:node)
261
+ end
262
+
263
+ # @group Navigating the Tree
264
+ #########################################################################
265
+
266
+ # Returns a new `StateMachine` positioned on the first segment in
267
+ # the parse tree, if there are any segments in the parse tree.
268
+ #
269
+ # @return [Either<StateMachine>]
270
+ def first
271
+ active = roots.map do |zipper|
272
+ state = zipper
273
+ value = zipper.node.zipper
274
+
275
+ until value.node.segment? or value.leaf?
276
+ value = value.down
277
+ state = state.down
278
+ end
279
+
280
+ unless value.node.segment?
281
+ return Either.failure("no segments")
282
+ end
283
+
284
+ # Synchronize the two parallel state and value nodes
285
+ unless value.eql?(state.node.zipper)
286
+ state = state.replace(state.node.copy(:zipper => value))
287
+ end
288
+
289
+ state
290
+ end
291
+
292
+ Either.success(StateMachine.new(@config, active))
293
+ end
294
+
295
+ # Returns a new `StateMachine` positioned on the last segment in
296
+ # the parse tree, if there are any segments in the parse tree.
297
+ #
298
+ # @return [Either<StateMachine>]
299
+ def last
300
+ active = roots.map do |zipper|
301
+ state = zipper
302
+ value = zipper.node.zipper
303
+
304
+ until value.node.segment? or value.leaf?
305
+ value = value.down.last
306
+ state = state.down.last
307
+ end
308
+
309
+ unless value.node.segment?
310
+ return Either.failure("no segments")
311
+ end
312
+
313
+ # Synchronize the two parallel state and value nodes
314
+ unless value.eql?(state.node.zipper)
315
+ state = state.replace(state.node.copy(:zipper => value))
316
+ end
317
+
318
+ state
319
+ end
320
+
321
+ Either.success(StateMachine.new(@config, active))
322
+ end
323
+
324
+ # Returns a new `StateMachine` positioned on the first segment of the
325
+ # parent structure. For example, when the current segment belongs to a
326
+ # loop but it's not the first segment in that loop, this method will
327
+ # rewind to the first segment in the loop. If the current position is
328
+ # the first segment of a loop, this method will rewind to the first
329
+ # segment in the loop's parent structure.
330
+ #
331
+ # @return [Either<StateMachine>]
332
+ def parent
333
+ active = []
334
+
335
+ @active.each do |zipper|
336
+ state = zipper
337
+ value = zipper.node.zipper
338
+
339
+ while value.first? and not value.root?
340
+ value = value.up
341
+ state = state.up
342
+ end
343
+
344
+ if value.root?
345
+ break
346
+ end
347
+
348
+ value = value.first
349
+ state = state.first
350
+
351
+ until value.node.segment?
352
+ value = value.down
353
+ state = state.down
354
+ end
355
+
356
+ # Synchronize the two parallel state and value nodes
357
+ unless value.eql?(state.node.zipper)
358
+ state = state.replace(state.node.copy(:zipper => value))
359
+ end
360
+
361
+ active << state
362
+ end
363
+
364
+ if active.empty?
365
+ Either.failure("no parent segment")
366
+ else
367
+ Either.success(StateMachine.new(@config, active))
368
+ end
369
+ end
370
+
371
+ # Returns a new `StateMachine` positioned on the next segment, if
372
+ # there is a next segment. Optionally, a `count` argument may be
373
+ # provided that indicates how many segments to advance.
374
+ #
375
+ # @return [Either<StateMachine>]
376
+ def next(count = 1)
377
+ unless count > 0
378
+ raise ArgumentError,
379
+ "count must be positive"
380
+ end
381
+
382
+ active = @active.map do |zipper|
383
+ state = zipper
384
+ value = zipper.node.zipper
385
+
386
+ count.times do
387
+ while not value.root? and value.last?
388
+ value = value.up
389
+ state = state.up
390
+ end
391
+
392
+ if value.root?
393
+ return Either.failure("cannot move to next after last segment")
394
+ end
395
+
396
+ value = value.next
397
+ state = state.next
398
+
399
+ until value.node.segment?
400
+ value = value.down
401
+ state = state.down
402
+ end
403
+ end
404
+
405
+ # Synchronize the two parallel state and value nodes
406
+ unless value.eql?(state.node.zipper)
407
+ state = state.replace(state.node.copy(:zipper => value))
408
+ end
409
+
410
+ state
411
+ end
412
+
413
+ Either.success(StateMachine.new(@config, active))
414
+ end
415
+
416
+ # Returns a new `StateMachine` positioned on the previous segment, if
417
+ # there is a previous segment. Optionally, a `count` argument may be
418
+ # provided that indicates how many segments to rewind.
419
+ #
420
+ # @return [Either<StateMachine>]
421
+ def prev(count = 1)
422
+ unless count > 0
423
+ raise ArgumentError,
424
+ "count must be positive"
425
+ end
426
+
427
+ active = @active.map do |zipper|
428
+ state = zipper
429
+ value = zipper.node.zipper
430
+
431
+ count.times do
432
+ while not value.root? and value.first?
433
+ value = value.up
434
+ state = state.up
435
+ end
436
+
437
+ if value.root?
438
+ return Either.failure("cannot move to prev before first segment")
439
+ end
440
+
441
+ state = state.prev
442
+ value = value.prev
443
+
444
+ until value.node.segment?
445
+ value = value.down.last
446
+ state = state.down.last
447
+ end
448
+ end
449
+
450
+ # Synchronize the two parallel state and value nodes
451
+ unless value.eql?(state.node.zipper)
452
+ state = state.replace(state.node.copy(:zipper => value))
453
+ end
454
+
455
+ state
456
+ end
457
+
458
+ Either.success(StateMachine.new(@config, active))
459
+ end
460
+
461
+ # Returns a `StateMachine` positioned on the next matching segment,
462
+ # excluding {Values::InvalidSegmentVal}s, that satisfies the given element
463
+ # constraints. The search space is limited to certain related elements
464
+ # described in [Navigating.md]
465
+ #
466
+ # @example
467
+ # machine.find(:ST, nil, nil, "005010X222")
468
+ #
469
+ # @return [Either<StateMachine>]
470
+ def find(id, *elements)
471
+ __find(false, id, elements)
472
+ end
473
+
474
+ # Returns a `StateMachine` positioned on the next matching segment,
475
+ # including {Values::InvalidSegmentVal}s, that satisfies the given element
476
+ # constraints. The search space is limited to certain related elements
477
+ # described in [Navigating.md]
478
+ #
479
+ # @example
480
+ # machine.find!(:ST, nil, nil, "005010X222")
481
+ #
482
+ # @return [Either<StateMachine>]
483
+ def find!(id, *elements)
484
+ __find(true, id, elements)
485
+ end
486
+
487
+ # @return [Integer]
488
+ def count(id, *elements)
489
+ __count(false, id, elements)
490
+ end
491
+
492
+ # @return [Integer]
493
+ def count!(id, *elements)
494
+ __count(true, id, elements)
495
+ end
496
+
497
+ # Convenience method to iterate repeated occurrences of a segment by
498
+ # iteratively calling `find`. Beware this doesn't check that the segment
499
+ # is allowed to repeat, so calling `iterate(:XYZ)` may yield an `XYZ`
500
+ # segment and then throw an exception when searching for the second, if
501
+ # `XYZ` is not repeatable.
502
+ #
503
+ # @yieldparam [StateMachine]
504
+ # @yieldreturn [Object]
505
+ # @return [Either<Array<Object>>]
506
+ def iterate(id, *elements)
507
+ a = []
508
+ m = __find(false, id, elements, true)
509
+ return m unless m.defined?
510
+
511
+ while m.defined?
512
+ m = m.flatmap do |n|
513
+ a << yield(n)
514
+ n.find(id, *elements)
515
+ end
516
+ end
517
+
518
+ Either.success(a)
519
+ end
520
+
521
+ # Sequence multiple traversals together, by iteratively calling
522
+ # `find`. Each argument must be either a single segment ID or a
523
+ # list whose first element is a segment ID and remaining elements
524
+ # are segment constraints.
525
+ #
526
+ # @example
527
+ # machine.sequence(:GS, :ST, :HL)
528
+ # machine.sequence(:GS, [:ST, "837"], [:HL, nil, "20"])
529
+ #
530
+ # @return [Either<StateMachine>]
531
+ def sequence(pattern, *patterns)
532
+ patterns.inject(find(*pattern)) do |m, p|
533
+ m = m.flatmap{|n| n.find(*p) }
534
+ end
535
+ end
536
+
537
+ private
538
+
539
+ # @return [Either<StateMachine>]
540
+ def __find(invalid, id, elements, assert_repeatable = false)
541
+ reachable = false
542
+ repeatable = false
543
+ matches = []
544
+ filter_tok = nil
545
+
546
+ @active.each do |zipper|
547
+ matched = false
548
+ filter_tok ||= mksegment_tok(zipper.node.segment_dict, id, elements, nil)
549
+
550
+ instructions = zipper.node.instructions.matches(filter_tok, true, :find)
551
+ reachable ||= !instructions.empty?
552
+
553
+ grouped = instructions.runs do |a,b|
554
+ a.push == b.push &&
555
+ a.pop_count == b.pop_count &&
556
+ a.drop_count == b.drop_count
557
+ end
558
+
559
+ grouped.each do |group|
560
+ break if matched
561
+
562
+ # Every transition follows the same shape
563
+ # 1. Move upward a number of nodes
564
+ # 2. Move left a number of nodes
565
+ # 3. Move downward a number of nodes
566
+ # 4. Stop on a segment
567
+
568
+ op_, = group
569
+ state = zipper
570
+ value = zipper.node.zipper
571
+
572
+ if assert_repeatable
573
+ # We have to do some extra work here, because `target` below won't
574
+ # have the additional instructions that could be added by calling
575
+ # `.push` on the new state class
576
+ repeatable ||= begin
577
+ suppose = StateMachine.new(@config, [state])
578
+ suppose, = suppose.execute(op_, state, nil, filter_tok)
579
+ suppose.node.instructions.matches(filter_tok, true, :find).present?
580
+ end
581
+ end
582
+
583
+ # 1. Move upward (possibly zero times)
584
+ op_.pop_count.times do
585
+ value = value.up
586
+ state = state.up
587
+ end
588
+
589
+ # 2. We know from the instruction `op` the *maximum* number of
590
+ # nodes to move left, but not exactly how many. Instead, we
591
+ # know what the InstructionTable is when we get there.
592
+ target = zipper.node.instructions.pop(op_.pop_count).drop(op_.drop_count)
593
+
594
+ # 3. If the segment we're searching for belongs in a new subtree,
595
+ # but it's not the only segment that might have "opened" that
596
+ # subtree (eg, Summary Table in 835 can begin with PLB or SE)
597
+ # then maybe the segment we're looking for comes *after* the
598
+ # first segment in this subtree.
599
+ #
600
+ # This is computed lazily below: non_leaders ||= ...
601
+ # non_leaders = nil
602
+
603
+ until state.last?
604
+ state = state.next
605
+ value = value.next
606
+ ops = group
607
+
608
+ # 2. Even if the InstructionTable matches, we still need to
609
+ # descend to some segment and compare it to the criteria. In
610
+ # most circumstances, this segment is directly below this
611
+ if target.eql?(state.node.instructions)
612
+ # 3. Move downward a number of nodes. Ultimately, we need to
613
+ # descend to a segment, but we have to be careful...
614
+ _value = value
615
+ _state = state
616
+
617
+ unless _value.node.segment?
618
+ _value = _value.down
619
+ _state = _state.down
620
+ end
621
+
622
+ while true
623
+ __value = _value
624
+ __state = _state
625
+
626
+ # Descend to the first segment
627
+ until __value.node.segment?
628
+ __value = __value.down
629
+ __state = __state.down
630
+ end
631
+
632
+ matched =
633
+ if __value.node.invalid?
634
+ invalid and not __filter?(filter_tok, __value.node)
635
+ else
636
+ # @note op.segment_use.nil? is true when searching for ISA,
637
+ # GS, and ST, because we can't know the SegmentUse until we
638
+ # deconstruct the token and look up the versions numbers
639
+ # in the Config
640
+ ops.any?{|op| op.segment_use.nil? or op.segment_use.eql?(__value.node.usage) } \
641
+ and not filter?(filter_tok, __value.node)
642
+ end
643
+
644
+ # 4. Stop on a segment
645
+ if matched
646
+ unless __value.eql?(__state.node.zipper)
647
+ __state = __state.replace(__state.node.copy(:zipper => __value))
648
+ end
649
+
650
+ matches << __state
651
+ break
652
+ end
653
+
654
+ # ops = non_leaders ||= group.reject do |op|
655
+ # op.push.nil? ||
656
+ # op.segment_use.nil? ||
657
+ # 1 >= zipper.node.instructions.instructions.count do |x|
658
+ # x.push.present? and
659
+ # (# This is hairy, but we know the instruction is pushing some
660
+ # # number of nested subtrees. We know from each AbstractState
661
+ # # subclass that we both either push a single subtree
662
+ # op.segment_use.parent.eql?(x.segment_use.try(:parent)) or
663
+ # # Or this instruction pushes one subtree while the other one
664
+ # # pushes two (eg, a new loop inside of a table)
665
+ # op.segment_use.parent.eql?(x.segment_use.try(:parent).try(:parent)) or
666
+ # # Or this instruction pushes two subtrees (eg, a new loop in
667
+ # # a new table) and the other also pushes two subtrees.
668
+ # op.segment_use.parent.parent.eql?(x.segment_use.try(:parent)))
669
+ # end
670
+ # end
671
+
672
+ break if ops.empty?
673
+ break if _value.last?
674
+
675
+ _value = _value.next
676
+ _state = _state.next
677
+ end
678
+
679
+ # 4. Stop on a segment
680
+ break if matched
681
+
682
+ elsif target.length > state.node.instructions.length
683
+ # The ancestor state can't be one of the rightward siblings,
684
+ # since the length of instruction tables is non-increasing as
685
+ # we move rightward
686
+ break
687
+ end
688
+ end
689
+ end
690
+ end
691
+
692
+ if assert_repeatable and not repeatable
693
+ raise Exceptions::ParseError,
694
+ "segment #{filter_tok.to_x12(Reader::Separators.default)} is not repeatable"
695
+ elsif not reachable
696
+ raise Exceptions::ParseError,
697
+ "segment #{filter_tok.to_x12(Reader::Separators.default)} cannot be reached"
698
+ elsif matches.empty?
699
+ Either.failure("segment #{filter_tok.to_x12(Reader::Separators.default)} does not occur")
700
+ else
701
+ Either.success(StateMachine.new(@config, matches))
702
+ end
703
+ end
704
+
705
+ # Returns true if the constraints modeled in `filter_tok` are not
706
+ # satisfied by the given `segment_val`, otherwise returns false.
707
+ def filter?(filter_tok, segment_val)
708
+ return true unless filter_tok.id == segment_val.id
709
+
710
+ filter_tok.element_toks.zip(segment_val.children) do |f_tok, e_val|
711
+ if f_tok.simple?
712
+ return true unless f_tok.blank? or e_val == f_tok.value
713
+ elsif f_tok.composite?
714
+ f_tok.component_toks.zip(e_val.children) do |c_tok, c_val|
715
+ return true unless c_tok.blank? or c_val == c_tok.value
716
+ end
717
+ elsif f_tok.present?
718
+ raise Exceptions::ParseError,
719
+ "only simple and composite elements can be filtered"
720
+ end
721
+ end
722
+
723
+ false
724
+ end
725
+
726
+ # Returns true if the constraints modeled in `filter_tok` are not
727
+ # satisfied by the given `invalid_val`, otherwise returns false.
728
+ def __filter?(filter_tok, invalid_val)
729
+ return true unless filter_tok.id == invalid_val.id
730
+
731
+ children = invalid_val.segment_tok.element_toks
732
+ filter_tok.element_toks.zip(children) do |f_tok, e_tok|
733
+ if f_tok.simple?
734
+ return true unless f_tok.blank? or f_tok.value == e_tok.value
735
+ elsif f_tok.composite?
736
+ children = e_tok.component_toks
737
+ f_tok.component_toks.zip(children) do |f_com, e_com|
738
+ return true unless f_com.blank? or f_com.value == e_com.value
739
+ end
740
+ elsif f_tok.present?
741
+ raise Exceptions::ParseError,
742
+ "only simple and composite elements can be filtered"
743
+ end
744
+ end
745
+
746
+ false
747
+ end
748
+
749
+ # @return [Integer]
750
+ def __count(invalid, id, elements)
751
+ cursor = __find(invalid, id, elements)
752
+ count = 0
753
+
754
+ while cursor.defined?
755
+ count += 1
756
+ cursor = cursor.flatmap{|c| c.send(:__find, invalid, id, elements) }
757
+ end
758
+
759
+ count
760
+ end
761
+
762
+ # Returns the cursor positioned at the root of the parse tree linked
763
+ # from each state.
764
+ #
765
+ # @return [Array<Zipper::RootCursor>]
766
+ def roots
767
+ @active.map do |zipper|
768
+ state = zipper
769
+ value = zipper.node.zipper
770
+
771
+ zipper.depth.times do
772
+ value = value.up
773
+ state = state.up
774
+ end
775
+
776
+ # Synchronize the two parallel state and value nodes
777
+ unless value.eql?(state.node.zipper)
778
+ state = state.replace(state.node.copy(:zipper => value))
779
+ end
780
+
781
+ state
782
+ end
783
+ end
784
+ end
785
+ end
786
+ end