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,176 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Reader
6
+ class SegmentDict
7
+ include Inspect
8
+
9
+ # Return the top element from the stack. This will throw an exception
10
+ # if the stack is {#empty?}
11
+ abstract :top
12
+
13
+ # Return the remainder of the stack. This will throw an exception if
14
+ # the stack is {#empty?}
15
+ #
16
+ # @return [SegmentDict]
17
+ abstract :pop
18
+
19
+ # Returns a new SegmentDict with +top+ pushed to the top of the stack
20
+ #
21
+ # @return [SegmentDict]
22
+ abstract :push, :args => %w(top)
23
+
24
+ # Searches the stack for the definition of the given segment identifier
25
+ abstract :at, :args => %w(segment_id)
26
+
27
+ # Returns true if a node in the stack has a definition for the given
28
+ # segment identifier
29
+ abstract :defined_at?, :args => %w(segment_id)
30
+
31
+ # True if the stack of dictionaries is empty
32
+ abstract :empty?
33
+
34
+ # @private
35
+ class NonEmpty < SegmentDict
36
+ attr_reader :top
37
+
38
+ # @return [SegmentDict]
39
+ attr_reader :pop
40
+
41
+ def initialize(top, pop = SegmentDict.empty)
42
+ @top, @pop = top, pop
43
+ end
44
+
45
+ # @return [SegmentDict]
46
+ def copy(changes = {})
47
+ NonEmpty.new \
48
+ changes.fetch(:top, @top),
49
+ changes.fetch(:pop, @pop)
50
+ end
51
+
52
+ def push(top)
53
+ if top.is_a?(Module)
54
+ copy(:top => Constants.new(top), :pop => self)
55
+ else
56
+ copy(:top => top, :pop => self)
57
+ end
58
+ end
59
+
60
+ def at(segment_id)
61
+ if @top.defined_at?(segment_id)
62
+ @top.at(segment_id)
63
+ else
64
+ @pop.at(segment_id)
65
+ end
66
+ end
67
+
68
+ def defined_at?(segment_id)
69
+ @top.defined_at?(segment_id) or
70
+ @pop.defined_at?(segment_id)
71
+ end
72
+
73
+ def empty?
74
+ false
75
+ end
76
+
77
+ # @return [void]
78
+ def pretty_print(q)
79
+ q.text "SegmentDict"
80
+ q.group(2, "(", ")") do
81
+ q.breakable ""
82
+
83
+ node = self
84
+ until node.empty?
85
+ unless q.current_group.first?
86
+ q.text ","
87
+ q.breakable
88
+ end
89
+ q.pp node.top
90
+ node = node.pop
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # @private
97
+ Empty = Class.new(SegmentDict) do
98
+ def top
99
+ raise TypeError, "empty stack"
100
+ end
101
+
102
+ def pop
103
+ raise TypeError, "stack underflow"
104
+ end
105
+
106
+ def push(top)
107
+ if top.is_a?(Module)
108
+ NonEmpty.new(Constants.new(top), self)
109
+ else
110
+ NonEmpty.new(top, self)
111
+ end
112
+ end
113
+
114
+ def defined_at?(segment_id)
115
+ false
116
+ end
117
+
118
+ def at(segment_id)
119
+ raise TypeError, "empty stack"
120
+ end
121
+
122
+ def empty?
123
+ true
124
+ end
125
+
126
+ # @return [void]
127
+ def pretty_print(q)
128
+ q.text "SegmentDict.empty"
129
+ end
130
+ end.new
131
+
132
+ # @private
133
+ class Constants
134
+ def initialize(namespace)
135
+ @namespace = namespace
136
+ @constants = Hash.new
137
+ namespace.constants.each{|c| @constants[c.to_sym] = true }
138
+ end
139
+
140
+ def at(segment_id)
141
+ @namespace.const_get(segment_id)
142
+ end
143
+
144
+ def defined_at?(segment_id)
145
+ @constants.include?(segment_id.to_sym)
146
+ end
147
+
148
+ # @return [void]
149
+ def pretty_print(q)
150
+ q.text("#{@namespace.name}.constants")
151
+ end
152
+ end
153
+ end
154
+
155
+ class << SegmentDict
156
+ # @group Constructors
157
+ #########################################################################
158
+
159
+ # @return [SegmentDict::Empty]
160
+ def empty
161
+ SegmentDict::Empty
162
+ end
163
+
164
+ # @return [SegmentDict::NonEmpty]
165
+ def build(top)
166
+ SegmentDict::Empty.push(top)
167
+ end
168
+
169
+ # @endgroup
170
+ #########################################################################
171
+ end
172
+
173
+ SegmentDict.eigenclass.send(:protected, :new)
174
+ SegmentDict::NonEmpty.eigenclass.send(:public, :new)
175
+ end
176
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Reader
6
+ #
7
+ # Stores the separators used to tokenize X12 from an input stream and
8
+ # serialize it to an output stream.
9
+ #
10
+ # @see X222.pdf B.1.1.2.5 Delimiters
11
+ #
12
+ class Separators
13
+ # @return [String]
14
+ attr_accessor :component # :
15
+
16
+ # @return [String]
17
+ attr_accessor :repetition # ^
18
+
19
+ # @return [String]
20
+ attr_accessor :element # *
21
+
22
+ # @return [String]
23
+ attr_accessor :segment # ~
24
+
25
+ def initialize(component, repetition, element, segment)
26
+ @component, @repetition, @element, @segment =
27
+ component, repetition, element, segment
28
+ end
29
+
30
+ # @return [Separators]
31
+ def copy(changes = {})
32
+ Separators.new \
33
+ changes.fetch(:component, @component),
34
+ changes.fetch(:repetition, @repetition),
35
+ changes.fetch(:element, @element),
36
+ changes.fetch(:segment, @segment)
37
+ end
38
+
39
+ # Creates a new value that has the separators from `other`, when they
40
+ # are not nil, and will use separators from `self` otherwise.
41
+ def merge(other)
42
+ Separators.new \
43
+ other.component || @component,
44
+ other.repetition || @repetition,
45
+ other.element || @element,
46
+ other.segment || @segment
47
+ end
48
+
49
+ # @return [AbstractSet<String>]
50
+ def characters
51
+ chars =
52
+ [@component, @repetition, @element, @segment].select{|s| s.present? }
53
+
54
+ Sets.absolute(chars.join.split(//), Reader::C_BYTES.split(//))
55
+ end
56
+
57
+ # @return [String]
58
+ def inspect
59
+ "Separators(#{@component.inspect}, #{@repetition.inspect}, #{@element.inspect}, #{@segment.inspect})"
60
+ end
61
+ end
62
+
63
+ class << Separators
64
+ # @group Constructors
65
+ #########################################################################
66
+
67
+ # @return [Separators]
68
+ def empty
69
+ new(nil, nil, nil, nil)
70
+ end
71
+
72
+ # @return [Separators]
73
+ def build(hash)
74
+ Separators.new \
75
+ hash[:component],
76
+ hash[:repetition],
77
+ hash[:element],
78
+ hash[:segment]
79
+ end
80
+
81
+ # @return [Separators]
82
+ def default
83
+ Separators.new(":", "^", "*", "~")
84
+ end
85
+
86
+ # @endgroup
87
+ #########################################################################
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Reader
6
+ #
7
+ # The {StreamReader} is intended to scan the input for a valid ISA segment,
8
+ # after which the {TokenReader} class can be used to tokenize the remaining
9
+ # input.
10
+ #
11
+ # Because X12 specifications have no bearing on what happens outside the
12
+ # interchange envelope (from `IEA` to `ISA`), out-of-band data like blank
13
+ # lines, human readable text, etc can occur between interchanges. This
14
+ # reader is designed to deal with that problem.
15
+ #
16
+ class StreamReader
17
+ include Inspect
18
+
19
+ attr_reader :input
20
+
21
+ def initialize(input)
22
+ @input = input
23
+ end
24
+
25
+ # @return true
26
+ def stream?
27
+ true
28
+ end
29
+
30
+ # True if there is no remaining input
31
+ def empty?
32
+ @input.empty?
33
+ end
34
+
35
+ # Read a single character
36
+ #
37
+ # @return [Either<Result<Character, StreamReader>>]
38
+ def read_character
39
+ unless @input.empty?
40
+ result(@input.at(0), advance(1))
41
+ else
42
+ failure("less than one character available")
43
+ end
44
+ end
45
+
46
+ # Skip a single character
47
+ #
48
+ # @return [Either<StreamReader>]
49
+ def consume_character
50
+ unless @input.empty?
51
+ success(advance(1))
52
+ else
53
+ failure("less than one character available")
54
+ end
55
+ end
56
+
57
+ # This method is unaware of subtle differences between interchange
58
+ # versions, so it really only tokenizes 16 elements, plus the element
59
+ # terminator and segment separators. It does not presume to understand
60
+ # the meaning of the elements, like the repetition separator or the
61
+ # component separator, because these are interchange version-dependent.
62
+ #
63
+ # @return [Either<Result<SegmentTok TokenReader>>]
64
+ def read_segment
65
+ consume_isa.flatmap do |rest|
66
+ # The character after "ISA" is defined to be the element separator
67
+ rest.read_character.flatmap do |char, aR|
68
+ separators = Separators.new(nil, nil, char, nil)
69
+ remaining = success(TokenReader.new(aR.input, separators))
70
+ elements = []
71
+
72
+ # Read 15 simple elements into an array. Consume/discard the element
73
+ # separator that follows each one.
74
+ 15.times do
75
+ remaining =
76
+ remaining.flatmap(&:read_simple_element).flatmap do |e, eR|
77
+ elements << e
78
+
79
+ # Throw away the following element separator
80
+ eR.consume_prefix(separators.element)
81
+ end
82
+ end
83
+
84
+ # We have to assume the last (16th) element is fixed-length because
85
+ # it is not terminated by an element separator. The {read_character}
86
+ # method defined by TokenReader skips past control characters.
87
+ remaining.flatmap do |w|
88
+ w.read_character.flatmap do |isa16, cR|
89
+ elements << SimpleElementTok.build(isa16, w.input, cR.input)
90
+
91
+ # The character after the last element is defined to be the
92
+ # segment terminator. The {read_character} method here, defined
93
+ # by StreamReader, does not skip past control character, so the
94
+ # separator could be a control character.
95
+ cR.stream.read_character.flatmap do |char_, dR|
96
+ if char_ == separators.element
97
+ failure("element separator and segment terminator must be distinct", dR.input)
98
+ else
99
+ separators.segment = char_
100
+
101
+ token = SegmentTok.build(:ISA, elements,
102
+ rest.input.position, dR.input.position)
103
+
104
+ result(token, TokenReader.new(dR.input, separators))
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end.or do |reason|
110
+ # We read "ISA" but failed to tokenize the input that followed. This
111
+ # was probably a random occurrence of the sequence "ISA", so we'll
112
+ # skip past it and try again.
113
+ #
114
+ # @todo: We should log this as a warning, because we could otherwise
115
+ # be silently discarding an entire interchange if the ISA segment
116
+ # was bogus
117
+ rest.read_segment
118
+ end
119
+ end
120
+ end
121
+
122
+ protected
123
+
124
+ # Consume the next occurence of "ISA"
125
+ #
126
+ # @return [Either<StreamReader>]
127
+ def consume_isa
128
+ position = 0
129
+ buffer = " "
130
+
131
+ # @todo: This would probably more optimal if it was written like
132
+ # consume("I") >>= consume_prefix("S") >>= consume_prefix("A")
133
+ while @input.defined_at?(position)
134
+ character = @input.at(position)
135
+ position += 1
136
+
137
+ unless Reader.is_control_character?(character)
138
+ # Slide the "window" forward one character
139
+ buffer = buffer.slice(1..-1)
140
+ buffer = buffer + character.upcase
141
+ if buffer == "ISA"
142
+ return success(advance(position))
143
+ end
144
+ end
145
+ end
146
+
147
+ failure("reached end of input before finding ISA segment identifier")
148
+ end
149
+
150
+ private
151
+
152
+ def advance(n)
153
+ unless @input.defined_at?(n-1)
154
+ raise IndexError, "less than #{n} characters available"
155
+ else
156
+ StreamReader.new(@input.drop(n))
157
+ end
158
+ end
159
+
160
+ def failure(message, remainder = input)
161
+ Reader::Failure.new(message, remainder, false)
162
+ end
163
+
164
+ def success(value)
165
+ Either.success(value)
166
+ end
167
+
168
+ def result(value, remainder)
169
+ Reader::Success.new(value, remainder)
170
+ end
171
+ end
172
+ end
173
+ end