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,147 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Parser
6
+ module Generation
7
+ # Consumes all input from `reader` and returns the updated
8
+ # {StateMachine} along with the result of the last attempt
9
+ # to read a segment.
10
+ #
11
+ # The `nondeterminism` argument specifies a limit on how many
12
+ # parse trees can be built simultaneously due to ambiguity in
13
+ # the input and/or specification. This prevents runaway memory
14
+ # CPU consumption (see GH-129), and will return a {Reader::Result.failure}
15
+ # once exceeded.
16
+ #
17
+ # The default value is 1, resulting in an error if any input
18
+ # is ambiguous.
19
+ #
20
+ # NOTE: The error is detected *after* the resources are already
21
+ # been consumed. The extra parse trees are returned (in memory)
22
+ # via the {StateMachine} to aide diagnosis.
23
+ #
24
+ # @return [(StateMachine, Reader::Result)]
25
+ def read(reader, options = {})
26
+ limit = options.fetch(:nondeterminism, 1)
27
+ machine = self
28
+ reader_e = reader.read_segment
29
+
30
+ while reader_e.defined?
31
+ reader_e = reader_e.flatmap do |segment_tok, reader_|
32
+ machine, reader__ =
33
+ machine.insert(segment_tok, false, reader_)
34
+
35
+ if machine.active.length <= limit
36
+ reader__.read_segment
37
+ else
38
+ matches = machine.active.map do |m|
39
+ if segment_use = m.node.zipper.node.usage
40
+ "SegmentUse(#{segment_use.position}, #{segment_use.id},
41
+ #{segment_use.requirement.inspect}, #{segment_use.repeat_count.inspect})".join
42
+ else
43
+ m.node.zipper.node.inspect
44
+ end
45
+ end.join(", ")
46
+
47
+ return machine,
48
+ Reader::Result.failure("too much non-determinism: #{matches}", reader__.input, true)
49
+ end
50
+ end
51
+ end
52
+
53
+ return machine, reader_e
54
+ end
55
+
56
+ # @return [(StateMachine, Reader::TokenReader)]
57
+ def insert(segment_tok, strict, reader)
58
+ active = @active.flat_map do |zipper|
59
+ state = zipper.node
60
+ instructions = state.instructions.matches(segment_tok, strict, :insert, state)
61
+
62
+ if instructions.empty?
63
+ zipper.append(FailureState.mksegment(segment_tok, state)).cons
64
+ else
65
+ instructions.map do |op|
66
+ successor = execute(op, zipper, reader, segment_tok)
67
+ reader = update_reader(op, reader, successor)
68
+ successor
69
+ end
70
+ end
71
+ end
72
+
73
+ return StateMachine.new(@config, active), reader
74
+ end
75
+
76
+ # Three things change together when executing an {Instruction}:
77
+ #
78
+ # 1. The stack of instruction tables that indicates where a segment
79
+ # would be located if it existed, or was added to the parse tree
80
+ #
81
+ # 2. The parse tree, to which we add the new syntax nodes using a
82
+ # zipper.
83
+ #
84
+ # 3. The corresponding tree of states, which tie together the first
85
+ # two and are also updated using a zipper
86
+ #
87
+ # @return [AbstractCursor<StateMachine>]
88
+ def execute(op, zipper, reader, segment_tok)
89
+ table = zipper.node.instructions # 1.
90
+ value = zipper.node.zipper # 2.
91
+ state = zipper # 3.
92
+
93
+ op.pop_count.times do
94
+ value = value.up
95
+ state = state.up
96
+ end
97
+
98
+ if op.push.nil?
99
+ # This instruction doesn't create a child node in the parse tree,
100
+ # but it might move us forward to a sibling or upward to an uncle
101
+ segment = AbstractState.mksegment(segment_tok, op.segment_use)
102
+ value = value.append(segment)
103
+
104
+ # If we're moving upward, pop off the current table(s). If we're
105
+ # moving forward, shift off the previous instructions. Important
106
+ # that these are done in order.
107
+ instructions = table.pop(op.pop_count).drop(op.drop_count)
108
+
109
+ # Create a new AbstractState node that has a new InstructionTable
110
+ # and also points to a new AbstractVal tree (with the new segment)
111
+ state.append(state.node.copy(
112
+ :zipper => value,
113
+ :instructions => instructions))
114
+ else
115
+ # Make a new sibling or uncle that will be the parent to the child
116
+ parent = state.node.copy \
117
+ :zipper => value,
118
+ :children => [],
119
+ :separators => reader.try(&:separators),
120
+ :segment_dict => reader.try(&:segment_dict),
121
+ :instructions => table.pop(op.pop_count).drop(op.drop_count)
122
+
123
+ # Note, `state` is a cursor pointing at a state, while `parent`
124
+ # is an actual state
125
+ state = state.append(parent) unless state.root?
126
+
127
+ # Note, eg, op.push == TableState; op.push.push == TableState.push
128
+ op.push.push(state, parent, segment_tok, op.segment_use, @config)
129
+ end
130
+ end
131
+
132
+ def update_reader(op, reader, successor)
133
+ # We might be moving up or down past the interchange or functional
134
+ # group envelope, which control the set of separators and the set
135
+ # of all possible segments
136
+ unless op.push.nil? and (op.pop_count.zero? or reader.stream?)
137
+ unless reader.separators.eql?(successor.node.separators) \
138
+ and reader.segment_dict.eql?(successor.node.segment_dict)
139
+ reader.copy \
140
+ :separators => successor.node.separators,
141
+ :segment_dict => successor.node.segment_dict
142
+ end
143
+ end || reader
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Parser
6
+ # @todo: Documentation
7
+ #
8
+ class IdentifierStack
9
+ def initialize(start)
10
+ @state = Empty.new(Sequence.new(start))
11
+ end
12
+
13
+ # Returns a new interchange control number (ISA13, IEA02)
14
+ #
15
+ # @return [String]
16
+ def isa(*args)
17
+ @state = @state.isa(*args)
18
+ @state.id
19
+ end
20
+
21
+ # Returns a new functional Group Control Number (GS06, GE02)
22
+ #
23
+ # @return [String]
24
+ def gs(*args)
25
+ @state = @state.gs(*args)
26
+ @state.id
27
+ end
28
+
29
+ # Returns a new Transaction Set Control Number (ST02)
30
+ #
31
+ # @return [String]
32
+ def st(*args)
33
+ @state = @state.st(*args)
34
+ @state.id
35
+ end
36
+
37
+ # Returns a new Hierarchical ID Number (HL01)
38
+ #
39
+ # @return [String]
40
+ def hl(*args)
41
+ @state = @state.hl(*args)
42
+ @state.id
43
+ end
44
+
45
+ # Returns the last identifier created
46
+ def pop
47
+ @state.id.tap { @state = @state.pop }
48
+ end
49
+
50
+ def pop_isa
51
+ @state.id.tap { @state = @state.pop_isa }
52
+ end
53
+
54
+ def pop_gs
55
+ @state.id.tap { @state = @state.pop_gs }
56
+ end
57
+
58
+ def pop_st
59
+ @state.id.tap { @state = @state.pop_st }
60
+ end
61
+
62
+ def pop_hl
63
+ @state.id.tap { @state = @state.pop_hl }
64
+ end
65
+
66
+ def respond_to?(name)
67
+ @state.respond_to?(name)
68
+ end
69
+
70
+ def method_missing(name, *args)
71
+ @state.__send__(name, *args)
72
+ end
73
+
74
+ #########################################################################
75
+ # State Representations
76
+
77
+ class Sequence
78
+ def initialize(start)
79
+ @value = start
80
+ end
81
+
82
+ def succ!
83
+ self.tap { @value += 1 }
84
+ end
85
+
86
+ def value
87
+ @value
88
+ end
89
+ end
90
+
91
+ class Empty
92
+ def initialize(sequence)
93
+ @count, @sequence = 0, sequence
94
+ end
95
+
96
+ def isa
97
+ ISA.new(self, @sequence).tap { @count += 1; @sequence.succ! }
98
+ end
99
+
100
+ # Returns number of interchanges (@note this is not needed by X12)
101
+ #
102
+ # @return [Integer]
103
+ def count
104
+ @count
105
+ end
106
+ end
107
+
108
+ class ISA
109
+ attr_reader :id
110
+
111
+ def initialize(parent, sequence)
112
+ @count, @parent, @sequence, @id = 0, parent, sequence, sequence.value
113
+ end
114
+
115
+ def gs(start = nil)
116
+ sequence = start.try{|n| Sequence.new(n) } || @sequence
117
+ GS.new(self, sequence).tap { @count += 1; sequence.succ! }
118
+ end
119
+
120
+ # Number of Transaction Sets Included (IEA01)
121
+ #
122
+ # @return [Integer]
123
+ def count
124
+ @count
125
+ end
126
+
127
+ def pop_isa
128
+ @parent
129
+ end
130
+
131
+ def pop
132
+ warn "DEPRECATION WARNING: IdentifierStack#pop is deprecated, use pop_isa (when removing an ISA identifier from the stack)"
133
+ pop_isa
134
+ end
135
+ end
136
+
137
+ class GS
138
+ attr_reader :id
139
+
140
+ def initialize(parent, sequence)
141
+ @count, @parent, @sequence, @id = 0, parent, sequence, sequence.value
142
+ end
143
+
144
+ def st(start = nil)
145
+ sequence = start.try{|n| Sequence.new(n) } || @sequence
146
+ ST.new(self, sequence).tap { @count += 1; sequence.succ! }
147
+ end
148
+
149
+ # Number of Transaction Sets Included (GE01)
150
+ #
151
+ # @return [Integer]
152
+ def count
153
+ @count
154
+ end
155
+
156
+ def pop_gs
157
+ @parent
158
+ end
159
+
160
+ def pop
161
+ warn "DEPRECATION WARNING: IdentifierStack#pop is deprecated, use pop_gs (when removing an GS identifier from the stack)"
162
+ pop_gs
163
+ end
164
+ end
165
+
166
+ class ST
167
+ def initialize(parent, sequence)
168
+ @parent, @id = parent, sequence.value
169
+ @hl_sequence = Sequence.new(0)
170
+ @lx_sequence = Sequence.new(0)
171
+ end
172
+
173
+ def id
174
+ "%04d" % @id
175
+ end
176
+
177
+ def hl
178
+ HL.new(self, @hl_sequence.succ!, @lx_sequence)
179
+ end
180
+
181
+ def lx
182
+ @lx_sequence.succ!.value
183
+ end
184
+
185
+ # Number of Included Segments (SE01)
186
+ #
187
+ # @return [Integer]
188
+ def count(builder)
189
+ m = Either.success(builder.machine)
190
+
191
+ while m.defined?
192
+ if m.flatmap(&:segment).map{|s| s.node.id == :ST }.fetch(false)
193
+ return m.flatmap{|n| n.distance(builder.machine) }.fetch(0) + 2
194
+ else
195
+ m = m.flatmap(&:parent)
196
+ end
197
+ end
198
+ end
199
+
200
+ def pop_st
201
+ @parent
202
+ end
203
+
204
+ def pop
205
+ warn "DEPRECATION WARNING: IdentifierStack#pop is deprecated, use pop_st (when removing an ST identifier from the stack)"
206
+ pop_st
207
+ end
208
+ end
209
+
210
+ class HL
211
+ attr_reader :id
212
+
213
+ def initialize(parent, hl_sequence, lx_sequence)
214
+ @parent, @id, @hl_sequence, @lx_sequence =
215
+ parent, hl_sequence.value, hl_sequence, lx_sequence
216
+ end
217
+
218
+ def hl
219
+ HL.new(self, @hl_sequence.succ!, @lx_sequence)
220
+ end
221
+
222
+ def lx
223
+ @lx_sequence.succ!.value
224
+ end
225
+
226
+ # Parent HL number (HL02)
227
+ #
228
+ # @return [Integer]
229
+ def parent_hl
230
+ if @parent.is_a?(HL)
231
+ @parent.id
232
+ else
233
+ raise RuntimeError, "this HL*#{@id} does not have a parent HL"
234
+ end
235
+ end
236
+
237
+ def pop_hl
238
+ @parent
239
+ end
240
+
241
+ def pop
242
+ warn "DEPRECATION WARNING: IdentifierStack#pop is deprecated, use pop_ht (when removing an HL identifier from the stack)"
243
+ pop_hl
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Parser
6
+ class Instruction
7
+ include Inspect
8
+
9
+ # The segment identifier to which this {Instruction} applies
10
+ #
11
+ # @return [Symbol]
12
+ attr_reader :segment_id
13
+
14
+ # The segment use contains helpful information about the context of the
15
+ # segment within a definition tree (eg the parent structure's definition).
16
+ # It also enumerates the allowed values for segment qualifiers, which is
17
+ # used to minimize non-determinism.
18
+ #
19
+ # @return [Schema::SegmentUse]
20
+ attr_reader :segment_use
21
+
22
+ # This indicates the number of levels to ascend and terminate within the
23
+ # tree before storing the segment.
24
+ #
25
+ # @return [Integer]
26
+ attr_reader :pop_count
27
+
28
+ # This controls the allowed order in which structures may occur, by
29
+ # indicating the number of instructions to remove from the beginning of
30
+ # the instruction table.
31
+ #
32
+ # Repeatable structures have a value that ensures that their instruction
33
+ # remains in the successor table.
34
+ #
35
+ # Sibling structures that have the same position (as defined by their
36
+ # {Schema::SegmentUse}) will have equal drop_count values such that all of
37
+ # the sibling instructions remain in the successor table when any one of
38
+ # them is executed.
39
+ #
40
+ # @return [Integer]
41
+ attr_reader :drop_count
42
+
43
+ # This indicates that a child node should be added to the tree, which
44
+ # will then contain the segment.
45
+ #
46
+ # When a segment indicates the start of a child structure, the class
47
+ # indicated by this attribute is expected to respond to `push` by
48
+ # creating a new {AbstractState}.
49
+ #
50
+ # @return [Zipper::AbstractCursor]
51
+ attr_reader :push
52
+
53
+ def initialize(segment_id, segment_use, pop, drop, push)
54
+ @segment_id, @segment_use, @pop_count, @drop_count, @push =
55
+ segment_id || segment_use.definition.id, segment_use, pop, drop, push
56
+ end
57
+
58
+ def hash
59
+ [Instruction, state].hash
60
+ end
61
+
62
+ # @return [Instruction]
63
+ def copy(changes = {})
64
+ Instruction.new \
65
+ changes.fetch(:segment_id, @segment_id),
66
+ changes.fetch(:segment_use, @segment_use),
67
+ changes.fetch(:pop_count, @pop_count),
68
+ changes.fetch(:drop_count, @drop_count),
69
+ changes.fetch(:push, @push)
70
+ end
71
+
72
+ # @return [void]
73
+ def pretty_print(q)
74
+ id = "% 3s" % @segment_id.to_s
75
+
76
+ unless @segment_use.nil?
77
+ width = 18
78
+ name = @segment_use.definition.name
79
+
80
+ # Truncate the segment name to `width` characters
81
+ if name.length > width - 2
82
+ id = id + ": #{name.slice(0, width - 2)}.."
83
+ else
84
+ id = id + ": #{name.ljust(width)}"
85
+ end
86
+ end
87
+
88
+ q.text "Instruction[#{"% 3s" % id}]"
89
+
90
+ q.group(6, "(", ")") do
91
+ q.breakable ""
92
+
93
+ unless @segment_use.nil?
94
+ q.text "position: #{@segment_use.position},"
95
+ q.breakable
96
+ end
97
+
98
+ q.text "pop: #{@pop_count},"
99
+ q.breakable
100
+
101
+ q.text "drop: #{@drop_count}"
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def state
108
+ [@segment_id, @segment_use, @pop_count, @drop_count, @push].hash
109
+ end
110
+ end
111
+ end
112
+ end