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,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
|