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,178 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Writer
6
+ class Claredi
7
+ def initialize(node)
8
+ @node = node
9
+ end
10
+
11
+ # @return [String]
12
+ def write(out = StringIO.new)
13
+ out << %Q(<html lang="en"><head>)
14
+ out << %Q(<meta charset="iso-8859-1">)
15
+ out << style
16
+ out << js
17
+ out << "</head><body>"
18
+ build(@node, out)
19
+ out << "</body></html>"
20
+ out
21
+ end
22
+
23
+ private
24
+
25
+ def js
26
+ <<-JS
27
+ <script type="text/javascript">
28
+ function toggle() {
29
+ var trim = document.getElementsByClassName("trim");
30
+ for (var k = 0; k < trim.length; k ++)
31
+ trim[k].classList.toggle("hide");
32
+ }
33
+ </script>
34
+ JS
35
+ end
36
+
37
+ # @return [String]
38
+ def style
39
+ <<-CSS
40
+ <style>
41
+ body { font-size: 0.75em; }
42
+
43
+ .interchange, .functionalgr, .table, .loop > .label {
44
+ font-weight: bold;
45
+ font-family: Optima, Trebuchet, sans-serif;
46
+ }
47
+
48
+ .interchange, .functionalgr, .transaction {
49
+ margin-left: 1em;
50
+ }
51
+
52
+ .transaction {
53
+ margin-top: 1.5em;
54
+ margin-bottom: 1em;
55
+ }
56
+
57
+ .transaction > .label {
58
+ font-weight: bold;
59
+ font-size: 1.5em;
60
+ }
61
+
62
+ .table, .loop {
63
+ margin-top: 0.5em;
64
+ margin-bottom: 0.5em;
65
+ margin-left: 0.5em;
66
+ }
67
+
68
+ .table { margin-left: 1em; margin-bottom: 2em; }
69
+
70
+ .table > .label/*
71
+ .interchange > .label,
72
+ .functionalgr > .label*/ {
73
+ font-size: 1.25em;
74
+ border-bottom: 3px solid black;
75
+ margin-top: 1em;
76
+ margin-bottom: 0.5em;
77
+ }
78
+
79
+ .loop { border: 1px solid grey; border-right: 0; }
80
+ .loop > .label { padding: 3px; background-color: #ddd; }
81
+ .loop > .segment { margin: 3px 0 3px 6px; }
82
+
83
+ .segment {
84
+ font-weight: normal;
85
+ font-family: 'Andale Mono', Monospace, monospace;
86
+ }
87
+
88
+ .segment .label:first-child { font-weight: bold; }
89
+
90
+ .trim { color: #bbb; display: inline; }
91
+ .hide { display: none; }
92
+
93
+ #form {
94
+ display: block;
95
+ padding: 3px;
96
+ border: 1px solid #ccc;
97
+ margin: 3px 3px 30px 3px;
98
+ font-family: Optima, Trebuchet, sans-serif;
99
+ background-color: #eee;
100
+ }
101
+ </style>
102
+ CSS
103
+ end
104
+
105
+ # @return [void]
106
+ def build(node, out)
107
+ if node.element?
108
+ if node.composite?
109
+ tmp = StringIO.new
110
+ node.children.each{|e| build(e, tmp) }
111
+ tmp.string.gsub!(%r{^(<span [^>]+>):}, "\\1*")
112
+ tmp.string.gsub!(%r{((?:<span [^<]+>:?</span>)+)$}, %Q(<em class="trim hide">\\1</em>))
113
+ out << tmp.string
114
+ elsif node.component?
115
+ out << %Q(<span class="label" title="#{node.definition.name}">:#{node.to_x12}</span>)
116
+ elsif node.repeated?
117
+ tmp = StringIO.new
118
+ node.children.each{|e| build(e, tmp) }
119
+ tmp.string.gsub!("*", "^")
120
+ out << tmp.string
121
+ else
122
+ out << %Q(<span class="label" title="#{node.definition.name}">*#{node.to_x12}</span>)
123
+ end
124
+
125
+ elsif node.segment?
126
+ out << %Q(<div class="segment"><span class="label" title="#{node.definition.name}">)
127
+ out << node.definition.id
128
+ out << "</span>"
129
+ tmp = StringIO.new
130
+ node.children.each{|e| build(e, tmp) }
131
+ tmp.string.gsub!(%r{((?:<span [^>]+>\*</span>)+)$}, %Q(<div class="trim hide">\\1</div>))
132
+ out << "#{tmp.string}~</div>\n"
133
+
134
+ elsif node.loop?
135
+ if m = /^(\d\w+)(?: [:-])? (.+)$/.match(node.definition.id)
136
+ id = m.captures[0]
137
+ name = m.captures[1]
138
+ name = name.split(/\s+/).map(&:capitalize).join(" ")
139
+ out << %Q(<div class="loop"><div class="label">#{name} (#{id})</div>\n)
140
+ else
141
+ out << %Q(<div class="loop"><div class="label">#{node.definition.id}</div>\n)
142
+ end
143
+
144
+ node.children.each{|c| build(c, out) }
145
+ out << "</div>\n"
146
+
147
+ elsif node.table?
148
+ out << %Q(<div class="table"><div class="label">#{node.definition.id}</div>\n)
149
+ node.children.each{|c| build(c, out) }
150
+ out << "</div>\n"
151
+
152
+ elsif node.transaction_set?
153
+ out << %Q(<div class="transaction"><div class="label">Transaction Set #{node.definition.id}</div>\n)
154
+ node.children.each{|c| build(c, out) }
155
+ out << "</div>\n"
156
+
157
+ elsif node.functional_group?
158
+ out << %Q(<div class="functionalgr">)
159
+ # out << %Q(<div class="label">Functional Group (#{node.definition.id})</div>\n)
160
+ node.children.each{|c| build(c, out) }
161
+ out << "</div>\n"
162
+
163
+ elsif node.interchange?
164
+ out << %Q(<div class="interchange">)
165
+ # out << %Q(<div class="label">Interchange (#{node.definition.id})</div>\n)
166
+ node.children.each{|c| build(c, out) }
167
+ out << "</div>\n"
168
+
169
+ elsif node.transmission?
170
+ out << %Q(<label id="form"><input type="checkbox" id="hide" onchange="toggle()" checked/> Hide empty suffixes</label>)
171
+ out << %Q(<div class="transmission">\n)
172
+ node.children.each{|c| build(c, out) }
173
+ out << "</div>\n"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,119 @@
1
+ module Stupidedi
2
+ using Refinements
3
+
4
+ module Writer
5
+ class Default
6
+ # @return [Reader::Separators]
7
+ attr_reader :separators
8
+
9
+ def initialize(zipper, separators = Reader::Separators.empty)
10
+ @zipper, @separators =
11
+ zipper, separators
12
+ end
13
+
14
+ # @return out
15
+ def write(out = "")
16
+ unless @zipper.node.transmission? or @zipper.node.interchange?
17
+ common = @separators.characters & @zipper.node.characters
18
+ message = common.to_a.map(&:inspect).join(", ")
19
+
20
+ if common.present?
21
+ raise Exceptions::OutputError,
22
+ "separator characters #{message} occur as data"
23
+ end
24
+ end
25
+
26
+ out.tap { recurse(@zipper.node, @separators, out) }
27
+ end
28
+
29
+ private
30
+
31
+ def recurse(value, separators, out)
32
+ return if value.invalid?
33
+
34
+ if value.segment?
35
+ segment(value, separators, out)
36
+ else
37
+ if value.interchange?
38
+ unless separators.merge(value.separators) == separators
39
+ common = separators.characters & value.characters
40
+ message = common.to_a.map(&:inspect).join(", ")
41
+
42
+ if common.present?
43
+ raise Exceptions::OutputError,
44
+ "separator characters #{message} occur as data"
45
+ end
46
+
47
+ # Change ISA11 and ISA16
48
+ value = value.replace_separators(separators)
49
+ end
50
+
51
+ separators = value.separators
52
+
53
+ raise Exceptions::OutputError,
54
+ "separators.segment cannot be blank" if separators.segment.blank?
55
+
56
+ raise Exceptions::OutputError,
57
+ "separators.element cannot be blank" if separators.element.blank?
58
+ end
59
+
60
+ value.children.each{|c| recurse(c, separators, out) }
61
+ end
62
+ end
63
+
64
+ def segment(s, separators, out)
65
+ # It's likely annoying for the user to need to check for at least
66
+ # one non-blank element before generating a segment. Instead, we
67
+ # can check that after the segment was generated and supress any
68
+ # empty segments here.
69
+ return if s.empty?
70
+
71
+ out << s.id.to_s
72
+
73
+ # Trailing empty elements (including component elements) can be omitted,
74
+ # so "NM1*XX*1:2::::*****~" should be abbreviated to "NM1*XX*1:2~".
75
+ elements = s.children.
76
+ reverse.drop_while(&:empty?).reverse # Remove the trailing empties
77
+
78
+ elements.each do |e|
79
+ out << separators.element
80
+ element(e, separators, out)
81
+ end
82
+
83
+ out << separators.segment
84
+ end
85
+
86
+ def element(e, separators, out)
87
+ if e.simple?
88
+ out << e.to_x12
89
+
90
+ elsif e.composite?
91
+ components = e.children.
92
+ reverse.drop_while(&:empty?).reverse
93
+
94
+ unless components.empty?
95
+ out << components.head.to_x12
96
+ end
97
+
98
+ components.tail.each do |c|
99
+ out << separators.component
100
+ out << c.to_x12
101
+ end
102
+
103
+ elsif e.repeated?
104
+ occurrences = e.children.
105
+ reverse.drop_while(&:empty?).reverse
106
+
107
+ unless occurrences.empty?
108
+ element(occurrences.head, separators, out)
109
+ end
110
+
111
+ occurrences.tail.each do |o|
112
+ out << separators.repetition
113
+ element(o, separators, out)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ module Writer
4
+ autoload :Claredi, "stupidedi/writer/claredi"
5
+ autoload :Default, "stupidedi/writer/default"
6
+ end
7
+ end
@@ -0,0 +1,351 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Zipper
6
+ class AbstractCursor
7
+ # @return [#leaf?, #children, #copy]
8
+ abstract :node
9
+
10
+ # @return [AbstractPath]
11
+ abstract :path
12
+
13
+ # @group Querying the Tree Location
14
+ #########################################################################
15
+
16
+ # (see AbstractPath#depth)
17
+ def depth
18
+ path.depth
19
+ end
20
+
21
+ # (see AbstractPath#first?)
22
+ def first?
23
+ path.first?
24
+ end
25
+
26
+ # (see AbstractPath#last?)
27
+ def last?
28
+ path.last?
29
+ end
30
+
31
+ # True if the node has no children
32
+ abstract :leaf?
33
+
34
+ # True if the node has no parent
35
+ abstract :root?
36
+
37
+ # Returns nodes between this zipper and the other, including `self.node`
38
+ # and `other.node` as end points. When this and the other zipper point to
39
+ # the same node within the tree, a single node is returned. Otherwise, the
40
+ # nodes are returned in order according to a left-to-right depth-first
41
+ # pre-order traversal.
42
+ #
43
+ # @note This method assumes `other` is a zipper for the same tree as the
44
+ # tree wrapped by `this`. In general, there is no way to know if that is
45
+ # or isn't the case, without comparing the entire tree. If this method
46
+ # is called on two different trees, the results are undefined.
47
+ #
48
+ # @return [Array]
49
+ def between(other)
50
+ # Collect ancestors of self, sorted oldest first (deepest last). This
51
+ # forms a boundary of nodes, which is called a "spine" below
52
+ zipper = self
53
+ lspine = [self]
54
+
55
+ until zipper.root?
56
+ zipper = zipper.up
57
+ lspine.unshift(zipper)
58
+ end
59
+
60
+ # Collect ancestors of self, sorted oldest first (deepest last). This
61
+ # forms a list of boundary nodes, which is called a "spine" below
62
+ zipper = other
63
+ rspine = [other]
64
+
65
+ until zipper.root?
66
+ zipper = zipper.up
67
+ rspine.unshift(zipper)
68
+ end
69
+
70
+ # Now we have two spines, both beginning with the root node. We remove
71
+ # the prefix common to both spines.
72
+ while a = lspine.first and b = rspine.first
73
+ if a.path == b.path
74
+ lspine.shift
75
+ rspine.shift
76
+ else
77
+ break
78
+ end
79
+ end
80
+
81
+ if lspine.empty?
82
+ # The other node is a child of self's node, and rspine contains all
83
+ # the nodes along the path between the two nodes, not including the
84
+ # self node.
85
+ return node.cons(rspine.map(&:node))
86
+
87
+ elsif rspine.empty?
88
+ # Self's node is a child of other's node, and lspine contains all
89
+ # the nodes along the path between the two nodes, not including the
90
+ # other node
91
+ return other.node.cons(lspine.map(&:node))
92
+
93
+ elsif lspine.head.path.position > rspine.head.path.position
94
+ # The first elements of lspine and rspine are siblings that share a
95
+ # common parent. Arrange them such that lspine is on the left, and
96
+ # so rspine is on the right
97
+ lspine, rspine = rspine, lspine
98
+ end
99
+
100
+ between = []
101
+
102
+ # Starting at the bottom of the left spine working upward, accumulate
103
+ # all the nodes to the right of the spine. Remember this is contained
104
+ # within the subtree under lspine.head
105
+ lspine.each{|z| between << z.node }
106
+ lspine.tail.reverse.each do |zipper1|
107
+ until zipper1.last?
108
+ zipper1 = zipper1.next
109
+ between.concat(zipper1.flatten)
110
+ end
111
+ end
112
+
113
+ # For the sibling nodes directly between (not including) lspine.head
114
+ # and rspine.head, we can accumulate the entire subtrees.
115
+ count = rspine.head.path.position - lspine.head.path.position - 1
116
+ zipper = lspine.head
117
+
118
+ count.times do
119
+ zipper = zipper.next
120
+ between.concat(zipper.flatten)
121
+ end
122
+
123
+ between << rspine.head.node
124
+
125
+ rspine.tail.each do |zipper_|
126
+ count = zipper_.path.position
127
+ zipper_ = zipper_.first
128
+
129
+ # We have to do a bit more work to traverse the siblings in left-to-
130
+ # right order, because `zipper_` is now the left spine. We start on
131
+ # the first sibling and move left a fixed number of times
132
+ count.times do
133
+ between.concat(zipper_.flatten)
134
+ zipper_ = zipper_.next
135
+ end
136
+
137
+ # Now zipper_ is along the left spine. We don't expand it here, but the
138
+ # next item in rspine is the next child along the left spine
139
+ between << zipper_.node
140
+ end
141
+
142
+ between
143
+ end
144
+
145
+ # Flattens the subtree into an Array of nodes. The nodes are arranged
146
+ # according to a depth-first left-to-right preorder traversal.
147
+ #
148
+ # @return [Array]
149
+ def flatten
150
+ nodes = []
151
+ queue = [node]
152
+
153
+ while node = queue.shift
154
+ nodes << node
155
+ queue.unshift(*node.children) unless node.leaf?
156
+ end
157
+
158
+ nodes
159
+ end
160
+
161
+ # @group Traversing the Tree
162
+ #########################################################################
163
+
164
+ # Navigate to the first child node
165
+ #
166
+ # @return [MemoizedCursor]
167
+ def down
168
+ @__down ||= begin
169
+ if leaf?
170
+ raise Exceptions::ZipperError,
171
+ "cannot descend into leaf node"
172
+ end
173
+
174
+ head, *tail = @node.children
175
+
176
+ MemoizedCursor.new(head,
177
+ Hole.new([], @path, tail), self)
178
+ end
179
+ end
180
+
181
+ # Navigate to the first child node, or if there are no children,
182
+ # create a placeholder where the first child node will be placed
183
+ #
184
+ # @return [AbstractCursor]
185
+ def dangle
186
+ if node.leaf?
187
+ raise Exceptions::ZipperError,
188
+ "cannot descend into leaf node"
189
+ end
190
+
191
+ if leaf?
192
+ DanglingCursor.new(self)
193
+ else
194
+ down
195
+ end
196
+ end
197
+
198
+ # Navigate to the `nth` child node
199
+ #
200
+ # @return [AbstractCursor]
201
+ def child(n)
202
+ if n < 0
203
+ raise Exceptions::ZipperError,
204
+ "child index cannot be negative"
205
+ end
206
+
207
+ cursor = down
208
+ until n.zero?
209
+ cursor = cursor.next
210
+ n -= 1
211
+ end
212
+ cursor
213
+ end
214
+
215
+ # Returns a list of cursors, one pointing to each child node.
216
+ #
217
+ # @return [Array<MemoizedCursor>]
218
+ def children
219
+ children = []
220
+
221
+ unless leaf?
222
+ zipper = down
223
+ children << zipper
224
+
225
+ until zipper.last?
226
+ zipper = zipper.next
227
+ children << zipper
228
+ end
229
+ end
230
+
231
+ children
232
+ end
233
+
234
+ # Recursively descend to each node's `nth` child
235
+ #
236
+ # @return [AbstractCursor]
237
+ def descendant(n, *ns)
238
+ cursor = self
239
+
240
+ n.cons(ns).each do |n_|
241
+ cursor = cursor.child(n_)
242
+ end
243
+
244
+ cursor
245
+ end
246
+
247
+ # Navigate to the parent node
248
+ #
249
+ # @return [AbstractCursor]
250
+ abstract :up
251
+
252
+ # Navigate to the next (rightward) sibling node
253
+ #
254
+ # @return [AbstractCursor]
255
+ abstract :next
256
+
257
+ # Navigate to the previous (leftward) sibling node
258
+ #
259
+ # @return [AbstractCursor]
260
+ abstract :prev
261
+
262
+ # Navigate to the first (leftmost) sibling node
263
+ #
264
+ # @return [AbstractCursor]
265
+ abstract :first
266
+
267
+ # Navigate to the last (rightmost) sibling node
268
+ #
269
+ # @return [AbstractCursor]
270
+ abstract :last
271
+
272
+ # Navigate to the root node
273
+ #
274
+ # @return [RootCursor]
275
+ def root
276
+ cursor = self
277
+ cursor = cursor.up until cursor.root?
278
+ cursor
279
+ end
280
+
281
+ # @group Editing the Tree
282
+ #########################################################################
283
+
284
+ # Insert a new sibling node after (to the right of) the current node,
285
+ # and navigate to the new sibling node
286
+ #
287
+ # @return [EditedCursor]
288
+ abstract :append, :args => %w(sibling)
289
+
290
+ # Insert a new sibling node before (to the left of) the current node,
291
+ # and navigate to the new sibling node
292
+ #
293
+ # @return [EditedCursor]
294
+ abstract :prepend, :args => %w(sibling)
295
+
296
+ # (see #append)
297
+ def insert_right(sibling)
298
+ append(sibling)
299
+ end
300
+
301
+ # (see #prepend)
302
+ def insert_left(sibling)
303
+ prepend(sibling)
304
+ end
305
+
306
+ # Insert a new child node before (to the left of) any existing children
307
+ # nodes and navigate to the new child node
308
+ #
309
+ # @return [EditedCursor]
310
+ def prepend_child(child)
311
+ if node.leaf?
312
+ raise Exceptions::ZipperError,
313
+ "cannot add child to leaf node"
314
+ end
315
+
316
+ EditedCursor.new(child,
317
+ Hole.new([], path, node.children), self)
318
+ end
319
+
320
+ # Insert a new child node after (to the right of) any existing children
321
+ # nodes and navigate to the new child node
322
+ #
323
+ # @return [EditedCursor]
324
+ def append_child(child)
325
+ if node.leaf?
326
+ raise Exceptions::ZipperError,
327
+ "cannot add child to leaf node"
328
+ end
329
+
330
+ EditedCursor.new(child,
331
+ Hole.new(node.children.reverse, path, []), self)
332
+ end
333
+
334
+ # Replace the current node with the given node
335
+ #
336
+ # @return [AbstractCursor]
337
+ abstract :replace, :args => %w(node)
338
+
339
+ # Remove the current node, and navigate to the next (rightward) node if
340
+ # one exists. Otherwise, navigate to the previous (leftward) node if one
341
+ # exists. Otherwise, create a placeholder where the next sibling node will
342
+ # be created.
343
+ #
344
+ # @return [EditedCursor]
345
+ abstract :delete
346
+
347
+ # @endgroup
348
+ #########################################################################
349
+ end
350
+ end
351
+ end