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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +319 -0
- data/LICENSE +26 -0
- data/README.md +308 -0
- data/Rakefile +33 -0
- data/bin/tediparse +102 -0
- data/doc/Defining.md +97 -0
- data/doc/Generating-Grammars.md +241 -0
- data/doc/Generating.md +332 -0
- data/doc/Navigating.md +670 -0
- data/doc/Parsing.md +133 -0
- data/doc/README.md +51 -0
- data/doc/Serializing.md +100 -0
- data/doc/Tokenizing.md +136 -0
- data/doc/Validating.md +140 -0
- data/lib/ruby/array.rb +226 -0
- data/lib/ruby/blank.rb +52 -0
- data/lib/ruby/exception.rb +14 -0
- data/lib/ruby/hash.rb +14 -0
- data/lib/ruby/module.rb +60 -0
- data/lib/ruby/object.rb +56 -0
- data/lib/ruby/string.rb +89 -0
- data/lib/ruby/to_d.rb +82 -0
- data/lib/ruby/to_date.rb +26 -0
- data/lib/ruby/to_time.rb +21 -0
- data/lib/ruby/try.rb +46 -0
- data/lib/stupidedi/builder.rb +6 -0
- data/lib/stupidedi/color.rb +94 -0
- data/lib/stupidedi/config/code_list_config.rb +45 -0
- data/lib/stupidedi/config/functional_group_config.rb +72 -0
- data/lib/stupidedi/config/interchange_config.rb +86 -0
- data/lib/stupidedi/config/transaction_set_config.rb +73 -0
- data/lib/stupidedi/config.rb +100 -0
- data/lib/stupidedi/either.rb +286 -0
- data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
- data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
- data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
- data/lib/stupidedi/exceptions/output_error.rb +7 -0
- data/lib/stupidedi/exceptions/parse_error.rb +7 -0
- data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
- data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
- data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
- data/lib/stupidedi/exceptions.rb +13 -0
- data/lib/stupidedi/inspect.rb +26 -0
- data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
- data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
- data/lib/stupidedi/interchanges/element_types.rb +11 -0
- data/lib/stupidedi/interchanges.rb +21 -0
- data/lib/stupidedi/parser/builder_dsl.rb +292 -0
- data/lib/stupidedi/parser/constraint_table.rb +566 -0
- data/lib/stupidedi/parser/generation.rb +147 -0
- data/lib/stupidedi/parser/identifier_stack.rb +248 -0
- data/lib/stupidedi/parser/instruction.rb +112 -0
- data/lib/stupidedi/parser/instruction_table.rb +222 -0
- data/lib/stupidedi/parser/navigation.rb +786 -0
- data/lib/stupidedi/parser/state_machine.rb +62 -0
- data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
- data/lib/stupidedi/parser/states/failure_state.rb +69 -0
- data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
- data/lib/stupidedi/parser/states/initial_state.rb +62 -0
- data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
- data/lib/stupidedi/parser/states/loop_state.rb +74 -0
- data/lib/stupidedi/parser/states/table_state.rb +90 -0
- data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
- data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
- data/lib/stupidedi/parser/tokenization.rb +195 -0
- data/lib/stupidedi/parser.rb +32 -0
- data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
- data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
- data/lib/stupidedi/reader/input/file_input.rb +157 -0
- data/lib/stupidedi/reader/input.rb +31 -0
- data/lib/stupidedi/reader/position.rb +78 -0
- data/lib/stupidedi/reader/result.rb +172 -0
- data/lib/stupidedi/reader/segment_dict.rb +176 -0
- data/lib/stupidedi/reader/separators.rb +90 -0
- data/lib/stupidedi/reader/stream_reader.rb +173 -0
- data/lib/stupidedi/reader/token_reader.rb +465 -0
- data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
- data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
- data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
- data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
- data/lib/stupidedi/reader.rb +121 -0
- data/lib/stupidedi/schema/abstract_def.rb +76 -0
- data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
- data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
- data/lib/stupidedi/schema/abstract_use.rb +79 -0
- data/lib/stupidedi/schema/code_list.rb +99 -0
- data/lib/stupidedi/schema/component_element_use.rb +76 -0
- data/lib/stupidedi/schema/composite_element_def.rb +103 -0
- data/lib/stupidedi/schema/composite_element_use.rb +78 -0
- data/lib/stupidedi/schema/element_req.rb +57 -0
- data/lib/stupidedi/schema/functional_group_def.rb +124 -0
- data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
- data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
- data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
- data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
- data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
- data/lib/stupidedi/schema/generation/models.rb +85 -0
- data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
- data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
- data/lib/stupidedi/schema/generation/runner.rb +161 -0
- data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
- data/lib/stupidedi/schema/generation/support.rb +78 -0
- data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
- data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
- data/lib/stupidedi/schema/generation.rb +94 -0
- data/lib/stupidedi/schema/interchange_def.rb +103 -0
- data/lib/stupidedi/schema/loop_def.rb +156 -0
- data/lib/stupidedi/schema/repeat_count.rb +86 -0
- data/lib/stupidedi/schema/segment_def.rb +122 -0
- data/lib/stupidedi/schema/segment_req.rb +46 -0
- data/lib/stupidedi/schema/segment_use.rb +99 -0
- data/lib/stupidedi/schema/simple_element_def.rb +51 -0
- data/lib/stupidedi/schema/simple_element_use.rb +83 -0
- data/lib/stupidedi/schema/syntax_note.rb +52 -0
- data/lib/stupidedi/schema/table_def.rb +178 -0
- data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
- data/lib/stupidedi/schema.rb +30 -0
- data/lib/stupidedi/sets.rb +42 -0
- data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
- data/lib/stupidedi/transaction_sets/builder.rb +188 -0
- data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
- data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
- data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
- data/lib/stupidedi/transaction_sets/common.rb +8 -0
- data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
- data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
- data/lib/stupidedi/transaction_sets/validation.rb +9 -0
- data/lib/stupidedi/transaction_sets.rb +25 -0
- data/lib/stupidedi/values/abstract_element_val.rb +19 -0
- data/lib/stupidedi/values/abstract_val.rb +133 -0
- data/lib/stupidedi/values/composite_element_val.rb +102 -0
- data/lib/stupidedi/values/functional_group_val.rb +105 -0
- data/lib/stupidedi/values/interchange_val.rb +102 -0
- data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
- data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
- data/lib/stupidedi/values/loop_val.rb +73 -0
- data/lib/stupidedi/values/repeated_element_val.rb +113 -0
- data/lib/stupidedi/values/segment_val.rb +105 -0
- data/lib/stupidedi/values/segment_val_group.rb +20 -0
- data/lib/stupidedi/values/simple_element_val.rb +80 -0
- data/lib/stupidedi/values/table_val.rb +69 -0
- data/lib/stupidedi/values/transaction_set_val.rb +69 -0
- data/lib/stupidedi/values/transmission_val.rb +56 -0
- data/lib/stupidedi/values.rb +22 -0
- data/lib/stupidedi/version.rb +4 -0
- data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
- data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
- data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
- data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
- data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
- data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
- data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
- data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
- data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
- data/lib/stupidedi/versions/common/element_types.rb +29 -0
- data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
- data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
- data/lib/stupidedi/versions/common.rb +11 -0
- data/lib/stupidedi/versions.rb +25 -0
- data/lib/stupidedi/writer/claredi.rb +178 -0
- data/lib/stupidedi/writer/default.rb +119 -0
- data/lib/stupidedi/writer.rb +7 -0
- data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
- data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
- data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
- data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
- data/lib/stupidedi/zipper/path.rb +132 -0
- data/lib/stupidedi/zipper/root_cursor.rb +120 -0
- data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
- data/lib/stupidedi/zipper.rb +45 -0
- data/lib/stupidedi.rb +69 -0
- data/lib/tediparse.rb +1 -0
- 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
|