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,188 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module TransactionSets
6
+ #
7
+ # {Builder} is a simple DSL for construction a transaction set
8
+ #
9
+ module Builder
10
+
11
+ autoload :Dsl, "stupidedi/transaction_sets/builder/dsl"
12
+
13
+ end
14
+
15
+
16
+ class << Builder
17
+ # @return [Schema::TransactionSetDef]
18
+ def build(functional_group, id, name, *table_defs)
19
+ Schema::TransactionSetDef.build(functional_group, id, name, *table_defs)
20
+ end
21
+
22
+ # @group Element Constraints
23
+ #######################################################################
24
+
25
+ # @param [String, ...] values
26
+ def Values(*values)
27
+ [:Values, values]
28
+ end
29
+
30
+ # @param [Integer] n
31
+ def MaxLength(n)
32
+ [:MaxLength, n]
33
+ end
34
+
35
+ # @param [Integer] n
36
+ def MaxPrecision(n)
37
+ [:MaxPrecision, n]
38
+ end
39
+
40
+ # @endgroup
41
+ #######################################################################
42
+
43
+ # @group Definition Constructors
44
+ #######################################################################
45
+
46
+ # @param [Schema::ElementReq] requirement
47
+ # @param [String] name
48
+ def Element(requirement, name, *constraints)
49
+ [:Element, requirement, name, constraints]
50
+ end
51
+
52
+ # @return [SegmentUse]
53
+ def Segment(position, segment_def, name, requirement, repeat_count, *elements)
54
+ unless elements.length == segment_def.element_uses.length
55
+ raise Exceptions::InvalidSchemaError,
56
+ "segment #{segment_def.id} has #{segment_def.element_uses.length}" +
57
+ " elements but #{elements.length} arguments were given"
58
+ end
59
+
60
+ element_idx = "00"
61
+ element_uses = elements.zip(segment_def.element_uses).map do |e, u|
62
+ e_tag, e_requirement, e_name, e_arguments = e
63
+ element_idx = element_idx.succ
64
+ descriptor = "#{segment_def.id}#{element_idx}"
65
+
66
+ unless e_tag == :Element
67
+ raise Exceptions::InvalidSchemaError,
68
+ "given argument for #{descriptor} must be Element(...)"
69
+ end
70
+
71
+ if u.composite?
72
+ e_repeat_count, e_arguments = e_arguments.partition{|x| x.is_a?(Schema::RepeatCount) }
73
+
74
+ changes = Hash.new
75
+ changes[:requirement] = e_requirement
76
+
77
+ if e_repeat_count.length == 1
78
+ changes[:repeat_count] = e_repeat_count.head
79
+ elsif e_repeat_count.length > 1
80
+ raise Exceptions::InvalidSchemaError,
81
+ "more than one RepeatCount was specified for #{descriptor}"
82
+ end
83
+
84
+ unless e_requirement.forbidden?
85
+ unless e_arguments.length == u.definition.component_uses.length
86
+ raise Exceptions::InvalidSchemaError,
87
+ "composite element #{u.definition.id} at #{descriptor} has
88
+ #{u.definition.component_uses.length} component elements but
89
+ #{e_arguments.length} arguments were given".join
90
+ end
91
+
92
+ # ComponentElementUses
93
+ component_idx = "00"
94
+ component_uses = e_arguments.zip(u.definition.component_uses).map do |e1, c|
95
+ c_tag, c_requirement, c_name, c_arguments = e1
96
+ component_idx = component_idx.succ
97
+ descriptor = "#{descriptor}-#{component_idx}"
98
+
99
+ unless c_tag == :Element
100
+ raise Exceptions::InvalidSchemaError,
101
+ "given argument for #{descriptor} must be Element(...)"
102
+ end
103
+
104
+ mod_element(c, descriptor, c_requirement, c_name, c_arguments)
105
+ end
106
+
107
+ changes[:definition] = u.definition.copy(:name => e_name,
108
+ :component_uses => component_uses)
109
+ else
110
+ changes[:definition] = u.definition.copy(:name => e_name)
111
+ end
112
+
113
+ u.copy(changes)
114
+ else
115
+ mod_element(u, descriptor, e_requirement, e_name, e_arguments)
116
+ end
117
+ end
118
+
119
+ segment_def.
120
+ copy(:name => name, :element_uses => element_uses).
121
+ use(position, requirement, repeat_count)
122
+ end
123
+
124
+ # @endgroup
125
+ #######################################################################
126
+
127
+ private
128
+
129
+ # @return [Schema::SimpleElementUse]
130
+ def mod_element(element_use, descriptor, requirement, name, arguments)
131
+ unless requirement.is_a?(Schema::ElementReq)
132
+ raise Exceptions::InvalidSchemaError,
133
+ "first argument to Element must be a Schema::ElementReq but got #{requirement.inspect} for #{descriptor}"
134
+ end
135
+
136
+ unless name.is_a?(String)
137
+ raise Exceptions::InvalidSchemaError,
138
+ "second argument to Element must be a String for #{descriptor}"
139
+ end
140
+
141
+ changes = Hash.new # changes to SegmentUse
142
+ dhanges = Hash.new # changes to SegmentDef
143
+
144
+ changes[:requirement] = requirement
145
+ changes[:definition] = element_use.definition.copy(:name => name)
146
+
147
+ repeat_count = arguments.select{|x| x.is_a?(Schema::RepeatCount) }
148
+
149
+ if repeat_count.length == 1
150
+ changes[:repeat_count] = repeat_count.head
151
+ elsif repeat_count.length > 1
152
+ raise Exceptions::InvalidSchemaError,
153
+ "more than one RepeatCount specified for #{descriptor}"
154
+ end
155
+
156
+ allowed_values = arguments.select{|x| x.is_a?(Array) and x.head == :Values }
157
+
158
+ if allowed_values.length == 1
159
+ begin
160
+ changes[:allowed_values] = element_use.allowed_values.replace(allowed_values.head.last)
161
+ rescue ArgumentError => e
162
+ raise Exceptions::InvalidSchemaError,
163
+ "#{e.message} for #{descriptor}"
164
+ end
165
+ elsif allowed_values.length > 1
166
+ raise Exceptions::InvalidSchemaError,
167
+ "more than one Values specified for #{descriptor}"
168
+ end
169
+
170
+ max_length = arguments.select{|x| x.is_a?(Array) and x.head == :MaxLength }
171
+
172
+ if max_length.length == 1
173
+ dhanges[:max_length] = max_length.head.last
174
+ elsif max_length.length > 1
175
+ raise Exceptions::InvalidSchemaError,
176
+ "more than one MaxLength specified for #{descriptor}"
177
+ end
178
+
179
+ if dhanges.empty?
180
+ element_use.copy(changes)
181
+ else
182
+ element_use.copy(changes.merge(:definition =>
183
+ element_use.definition.copy(dhanges)))
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module TransactionSets
4
+ module Common
5
+ module Implementations
6
+ #
7
+ # @see X222.pdf 2.2 Implementation Usage
8
+ #
9
+ module ElementReqs
10
+ # Declares that this element must always be sent. Required elements
11
+ # within Situational segments when the segment occurs.
12
+ #
13
+ # Similarly, required component elements within situational composite
14
+ # elements only occur when the composite element occurs.
15
+ Required = Schema::ElementReq.new(true, false, "R")
16
+
17
+ # Use of this element varies, depending on data content and business
18
+ # context as described in the defining rule. The defining rule is
19
+ # documented in a Situational Rule attached to the element.
20
+ #
21
+ # There are two forms of Situational Rules. The first is "Required when
22
+ # <condition>. If not required by <condition>, the element may be
23
+ # provided at the discretion of the sender, but it cannot be required or
24
+ # requested by the receiver.'
25
+ #
26
+ # The other form is "Required when <condition>. If not required by
27
+ # <condition>, do not send". The element described by the rule cannot
28
+ # occur except when the condition holds.
29
+ Situational = Schema::ElementReq.new(false, false, "S")
30
+
31
+ # This element must never be sent.
32
+ NotUsed = Schema::ElementReq.new(false, true, "N")
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module TransactionSets
4
+ module Common
5
+ module Implementations
6
+ #
7
+ # @see X222.pdf 2.2 Implementation Usage
8
+ #
9
+ module SegmentReqs
10
+ # Declares that this segment must always be sent. Required segments
11
+ # within Situational loops only occur when the loop occurs.
12
+ Required = Schema::SegmentReq.new(true, false, "R")
13
+
14
+ # Use of this segment varies, depending on data content and business
15
+ # context as described in the defining rule. The defining rule is
16
+ # documented in a Situational Rule attached to the segment.
17
+ #
18
+ # There are two forms of Situational Rules. The first is "Required when
19
+ # <condition>. If not required by <condition>, the segment may be
20
+ # provided at the discretion of the sender, but it cannot be required or
21
+ # requested by the receiver.'
22
+ #
23
+ # The other form is "Required when <condition>. If not required by
24
+ # <condition>, do not send". The segment described by the rule cannot
25
+ # occur except when the condition holds.
26
+ Situational = Schema::SegmentReq.new(false, false, "S")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module TransactionSets
4
+ module Common
5
+ module Implementations
6
+ autoload :ElementReqs, "stupidedi/transaction_sets/common/implementations/element_reqs"
7
+ autoload :SegmentReqs, "stupidedi/transaction_sets/common/implementations/segment_reqs"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module TransactionSets
4
+ module Common
5
+ autoload :Implementations, "stupidedi/transaction_sets/common/implementations"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,395 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module TransactionSets
6
+ module Validation
7
+ #
8
+ # @todo
9
+ #
10
+ class Ambiguity
11
+ include Color
12
+ include Parser::Tokenization
13
+
14
+ attr_reader :config
15
+ attr_reader :machine
16
+ attr_reader :reader
17
+ attr_reader :queue
18
+
19
+ def initialize(machine, reader, isa_elements, gs_elements, st_elements)
20
+ @config = machine.config
21
+ @reader = reader
22
+ @queue = []
23
+ @queued = Set.new
24
+
25
+ @elements = Hash.new{|h,k| h[k] = [] }
26
+ @elements[:ISA] = isa_elements
27
+ @elements[:GS] = gs_elements
28
+ @elements[:ST] = st_elements
29
+
30
+ enqueue(machine.active.head)
31
+ end
32
+
33
+ def audit
34
+ while cursor = @queue.shift
35
+ step(Stupidedi::Parser::StateMachine.new(@config, [cursor]))
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def enqueue(zipper)
42
+ # Avoid unecessary traversal over instruction tables that we've already
43
+ # queued. NOTE: This means some segments in the definition could be
44
+ # skipped entirely, due to have the same #successors as another segment.
45
+ #
46
+ # For instance, consider HP835's Table 1:
47
+ #
48
+ # d::TableDef.header("Table 1 - Header",
49
+ # b::Segment(100, s::ST, "Transaction Set Header", ...)
50
+ # b::Segment(200, s::BPR, "Financial Information", ...)
51
+ # b::Segment(400, s::TRN, "Reassociation Trace Number", ...)
52
+ # b::Segment(500, s::CUR, "Foreign Currency Information", ...)
53
+ # b::Segment(600, s::REF, "Receiver Identification", ...)
54
+ # b::Segment(600, s::REF, "Version Identification", ...)
55
+ # b::Segment(700, s::DTM, "Production Date", ...)
56
+ #
57
+ # It's important to know that two segments at the same position don't
58
+ # come "before" or "after" each other, even though they might be listed
59
+ # in a particular order.
60
+ #
61
+ # Starting at the CUR segment, its successors are the two REF segments
62
+ # and some other stuff. If we generate the first REF segment, due to
63
+ # having the same position (600) as the other REF segment, it also has
64
+ # both REF segments as successors. The second REF segment also has the
65
+ # same set of successors.
66
+ #
67
+ # Therefore, all three segments (CUR, REF, REF) have the same set of
68
+ # successors and it doesn't matter which one we choose; we only need
69
+ # to choose one.
70
+ #
71
+ id = zipper.node.instructions.hash
72
+ unless @queued.member?(id)
73
+ @queued.add(id)
74
+ @queue.push(zipper)
75
+ end
76
+ end
77
+
78
+ def step(machine)
79
+ machine.successors.head.constraints.each do |segment_id, table|
80
+ case table
81
+ when Stupidedi::Parser::ConstraintTable::Shallowest,
82
+ Stupidedi::Parser::ConstraintTable::Deepest
83
+ segment_tok =
84
+ mksegment_tok(@reader.segment_dict, segment_id, @elements[segment_id], nil)
85
+
86
+ instructions = table.matches(segment_tok, false, :insert)
87
+ if instructions.length > 1
88
+ raise Exceptions::InvalidSchemaError,
89
+ "proceeding from #{pp_machine(machine)}, there are no
90
+ constraints on segment #{segment_id} to choose between ".join +
91
+ table.pretty_inspect
92
+ end
93
+
94
+ op = instructions.head
95
+ as = machine.execute(op, machine.active.head, @reader, segment_tok)
96
+ enqueue(as)
97
+
98
+ when Stupidedi::Parser::ConstraintTable::Stub
99
+ if table.instructions.length == 1
100
+ segment_tok =
101
+ mksegment_tok(@reader.segment_dict, segment_id, @elements[segment_id], nil)
102
+
103
+ op = table.matches(nil, false, :insert).head
104
+ as = machine.execute(op, machine.active.head, @reader, segment_tok)
105
+ enqueue(as)
106
+ else
107
+ raise Exceptions::InvalidSchemaError,
108
+ "stub proceeding from #{pp_machine(machine)}, there are no
109
+ constraints on segment #{segment_id} to choose between ".join +
110
+ table.pretty_inspect
111
+ end
112
+
113
+ when Stupidedi::Parser::ConstraintTable::ValueBased
114
+ disjoint, distinct = table.basis(table.instructions, :insert)
115
+
116
+ if disjoint.empty?
117
+ # We're definitely going to throw an error now, but we need to
118
+ # get more information to make a useful error message. First
119
+ # check that interesting elements are required
120
+ designators = optional_elements(distinct, table.instructions)
121
+
122
+ case designators.length
123
+ when 0
124
+ when 1
125
+ raise Exceptions::InvalidSchemaError,
126
+ "proceeding from #{pp_machine(machine)}, the element
127
+ #{designators.join(", ")} is designated optional, but
128
+ is necessary to choose between ".join + table.pretty_inspect
129
+ else
130
+ raise Exceptions::InvalidSchemaError,
131
+ "proceeding from #{pp_machine(machine)}, the elements
132
+ #{designators.join(", ")} are designated optional, but
133
+ at least one is necessary to choose between ".join +
134
+ table.pretty_inspect
135
+ end
136
+
137
+ # Next report cases of element values that cause ambiguity
138
+ designators, ex_designator, ex_value, ex_instructions =
139
+ distinct_elements(distinct, table.instructions)
140
+
141
+ case designators.length
142
+ when 0
143
+ raise Exceptions::InvalidSchemaError,
144
+ "proceeding from #{pp_machine(machine)}, there are no
145
+ constraints on segment #{segment_id} to choose
146
+ between ".join + table.pretty_inspect
147
+ when 1
148
+ raise Exceptions::InvalidSchemaError,
149
+ "proceeding from #{pp_machine(machine)}, the element
150
+ #{ex_designator} has overlapping values among possible
151
+ choices; for example when it has value the #{ex_value},
152
+ it is not possible to choose between ".join +
153
+ table.copy(:instructions => ex_instructions).pretty_inspect
154
+ else
155
+ raise Exceptions::InvalidSchemaError,
156
+ "proceeding from #{pp_machine(machine)}, the elements
157
+ #{designators.join(", ")} each have overlapping values
158
+ among choices; for example when #{ex_designator} has
159
+ the value #{ex_value}, it is not possible to choose
160
+ between ".join + table.copy(:instructions => ex_instructions).pretty_inspect
161
+ end
162
+ else
163
+ # It's possible there is no ambiguity here, but we need to check
164
+ designators = optional_elements(disjoint, table.instructions)
165
+
166
+ case designators.length
167
+ when 0
168
+ when 1
169
+ raise Exceptions::InvalidSchemaError,
170
+ "proceeding from #{pp_machine(machine)}, the element
171
+ #{designators.join(", ")} is designated optional in at
172
+ least one case, but is necessary to choose between ".join +
173
+ table.pretty_inspect
174
+ else
175
+ raise Exceptions::InvalidSchemaError,
176
+ "proceeding from #{pp_machine(machine)}, the elements
177
+ #{designators.join(", ")} are designated optional in at
178
+ least one case each, but at least one is necessary to
179
+ choose between ".join + table.pretty_inspect
180
+ end
181
+
182
+ mksegments(table).each do |op_, segment_tok_|
183
+ enqueue(machine.execute(op_, machine.active.head, @reader, segment_tok_))
184
+ end
185
+ end
186
+ else
187
+ raise "unexpected kind of constraint table: #{constraint_table.class}"
188
+ end
189
+ end
190
+ end
191
+
192
+ # For each instruction in the given ConstraintTable::ValueBased, return
193
+ # the segment token which will be matched to that instruction.
194
+ #
195
+ # @return [Array<(Instruction, SegmentTok)>]
196
+ def mksegments(table)
197
+ disjoint, = table.basis(table.instructions, :insert)
198
+ remaining = Set.new(table.instructions)
199
+ segment_id = table.instructions.head.segment_use.id
200
+ segments = []
201
+
202
+ # Scan each interesting element location
203
+ disjoint.each do |(m, n), map|
204
+ if remaining.empty?
205
+ # We've already generated results for each possible instruction
206
+ break
207
+ end
208
+
209
+ # Likely many values are mapped to only a few unique keys; in this
210
+ # case, `instructions` is a singleton array (that's what makes the
211
+ # value non-ambiguous, there's only one possible instruction)
212
+ map.each do |value, instructions|
213
+ op, = instructions
214
+ repeatable = op.segment_use.definition.element_uses.at(m).repeatable?
215
+ elements = Array.new(m)
216
+ elements[m] =
217
+ if n.nil?
218
+ if repeatable
219
+ repeated(value)
220
+ else
221
+ value
222
+ end
223
+ else
224
+ components = Array.new(n)
225
+ components[n] = value
226
+
227
+ if repeatable
228
+ repeated(composite(components))
229
+ else
230
+ composite(components)
231
+ end
232
+ end
233
+
234
+ if remaining.member?(op)
235
+ remaining.delete(op)
236
+ segments.push([op, mksegment_tok(@reader.segment_dict, segment_id, elements, nil)])
237
+ end
238
+ end
239
+ end
240
+
241
+ segments
242
+ end
243
+
244
+ # Return a list of element designators that are needed to choose a single
245
+ # instruction, but are not #required? in the transaction set definition.
246
+ #
247
+ # @return [Array<String>]
248
+ def optional_elements(disjoint, instructions)
249
+ designators = []
250
+
251
+ disjoint.each do |(m, n), map|
252
+ required = true
253
+
254
+ # Element must be required for all possible segment_uses
255
+ instructions.each do |op|
256
+ element_use = op.segment_use.definition.element_uses.at(m)
257
+
258
+ if n.nil?
259
+ required &&= element_use.required?
260
+ else
261
+ component_use = element_use.definition.component_uses.at(n)
262
+ required &&= element_use.required? and component_use.required?
263
+ end
264
+ end
265
+
266
+ unless required
267
+ segment_id = instructions.head.segment_use.id
268
+ designators << "#{segment_id}-#{"%02d" % (m+1)}#{n.try{"-%02d" % (n+1)}}"
269
+ end
270
+ end
271
+
272
+ if designators.length < disjoint.length
273
+ # This means at least one designator *was* required, therefore we can
274
+ # be assured that valid input will not be ambiguous
275
+ []
276
+ else
277
+ # None of the useful elements were required, so even valid input (that
278
+ # has none of these elements present), will be ambiguous
279
+ designators
280
+ end
281
+ end
282
+
283
+ # Build a list of offending elements (eg, HL-03) and provide an example
284
+ # value that resolves to conflicting instructions.
285
+ #
286
+ # @return [(Array<String>, String, String, Array<Instruction>)]
287
+ def distinct_elements(distinct, instructions)
288
+ designators = []
289
+ ex_designator = nil
290
+ ex_value = nil
291
+ ex_instructions = nil
292
+
293
+ distinct.each do |(m, n), map|
294
+ designators <<
295
+ "#{instructions.head.segment_use.id}-#{"%02d" % (m+1)}#{n.try{"-%02d" % (n+1)}}"
296
+
297
+ if ex_designator.nil?
298
+ ex_designator = designators.last
299
+ ex_value, ex_instructions = map.max_by{|_, v| v.length }
300
+ end
301
+ end
302
+
303
+ return designators, ex_designator, ex_value, ex_instructions
304
+ end
305
+
306
+ # @return [String]
307
+ def pp_machine(machine)
308
+ zipper = machine.active.head
309
+ segment = zipper.node.zipper.node
310
+
311
+ if segment.valid?
312
+ id = "% 3s" % segment.definition.id.to_s
313
+ name = segment.definition.name
314
+ width = 18
315
+
316
+ if name.length > width - 2
317
+ id = id + ": #{name.slice(0, width - 2)}.."
318
+ else
319
+ id = id + ": #{name}"
320
+ end
321
+
322
+ args = "position:%d, repeat:%s" %
323
+ [segment.usage.position,
324
+ segment.usage.repeat_count.inspect]
325
+
326
+ ansi.segment("SegmentDef[#{ansi.bold(id)}](#{args})")
327
+ else
328
+ segment.pretty_inspect
329
+ end
330
+ end
331
+ end
332
+
333
+ class << Ambiguity
334
+ # @param interchange_def
335
+ # Required. tediparse does not bundle envelope grammars, so callers
336
+ # must pass an explicit InterchangeDef — typically the synthetic one
337
+ # used to drive the rest of their test harness.
338
+ def build(transaction_set_def, functional_group_def, interchange_def)
339
+ # Use dummy identifiers to link transaction_set_def to the parser
340
+ config = mkconfig(transaction_set_def, functional_group_def,
341
+ "ISA11", "GS01", "GS08", "ST01", interchange_def)
342
+
343
+ builder = Parser::BuilderDsl.new(Parser.build(config, Zipper::Stack), false)
344
+
345
+ # These lists of elements are re-used when Ambiguity needs to construct
346
+ # an ISA, GS, or ST segment as it walks the transaction_set_def
347
+ isa_elements =
348
+ [ "00", "AUTHORIZATION",
349
+ "00", "PASSWORD",
350
+ "ZZ", "SUBMITTER ID",
351
+ "ZZ", "RECEIVER ID",
352
+ "191225", "1230",
353
+ "^",
354
+ "ISA11",
355
+ "123456789", "0", "T", ":" ]
356
+
357
+ gs_elements =
358
+ [ "GS01",
359
+ "SENDER ID",
360
+ "RECEIVER ID",
361
+ "20191225", "1230", "1", "X",
362
+ "GS08" ]
363
+
364
+ st_elements =
365
+ [ "ST01",
366
+ "1234" ]
367
+
368
+ builder.ISA(*isa_elements)
369
+ builder.GS(*gs_elements)
370
+ builder.ST(*st_elements)
371
+
372
+ new(builder.machine, builder.reader, isa_elements, gs_elements, st_elements)
373
+ end
374
+
375
+ def mkconfig(definition, functional_group_def, isa11, gs01, gs08, st01, interchange_def)
376
+ Config.new.customize do |c|
377
+ c.interchange.customize do |x|
378
+ # We can use whatever interchange version we like, it does not
379
+ # have any bearing or relationship to the given `definition`
380
+ x.register(isa11, interchange_def)
381
+ end
382
+
383
+ c.functional_group.customize do |x|
384
+ x.register(gs08, functional_group_def)
385
+ end
386
+
387
+ c.transaction_set.customize do |x|
388
+ x.register(gs08, gs01, st01, definition)
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module TransactionSets
4
+ module Validation
5
+ #
6
+ #
7
+ #
8
+ class Implementation
9
+ end
10
+ end
11
+ end
12
+ end