gullah 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/LICENSE +21 -0
- data/README.md +87 -0
- data/Rakefile +11 -0
- data/TODO.md +2 -0
- data/examples/hat.rb +27 -0
- data/examples/trash.rb +42 -0
- data/examples/xml.rb +45 -0
- data/gullah.gemspec +31 -0
- data/lib/gullah/atom.rb +132 -0
- data/lib/gullah/boundary.rb +11 -0
- data/lib/gullah/dotifier.rb +127 -0
- data/lib/gullah/error.rb +7 -0
- data/lib/gullah/hopper.rb +142 -0
- data/lib/gullah/iterator.rb +67 -0
- data/lib/gullah/leaf.rb +24 -0
- data/lib/gullah/node.rb +553 -0
- data/lib/gullah/parse.rb +233 -0
- data/lib/gullah/picker.rb +56 -0
- data/lib/gullah/rule.rb +90 -0
- data/lib/gullah/segment.rb +92 -0
- data/lib/gullah/trash.rb +15 -0
- data/lib/gullah/version.rb +7 -0
- data/lib/gullah.rb +777 -0
- data/test/basic_test.rb +451 -0
- data/test/big_tree_test.rb +26 -0
- data/test/boundary_test.rb +29 -0
- data/test/date_test.rb +111 -0
- data/test/error_test.rb +245 -0
- data/test/json_test.rb +124 -0
- data/test/parse_demo_test.rb +33 -0
- data/test/precondition_test.rb +68 -0
- data/test/tests_per_subrule_test.rb +49 -0
- data/test/tree_walking_test.rb +88 -0
- metadata +157 -0
data/test/basic_test.rb
ADDED
@@ -0,0 +1,451 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
|
10
|
+
class BasicTest < Minitest::Test
|
11
|
+
class Simple
|
12
|
+
extend Gullah
|
13
|
+
|
14
|
+
rule :a, 'a+'
|
15
|
+
|
16
|
+
leaf :a, /\S+/
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_basic
|
20
|
+
parses = Simple.parse 'foo bar baz'
|
21
|
+
assert_equal 1, parses.length, 'only one optimal parse'
|
22
|
+
parse = parses.first
|
23
|
+
assert_equal parse.size, parse.nodes.count, 'nodes iterator works'
|
24
|
+
assert_equal 1, parse.roots.length, 'parse has a root node'
|
25
|
+
root = parse.roots.first
|
26
|
+
assert_equal :a, root.name, 'root node has the right label'
|
27
|
+
assert_equal 6, root.subtree.count, 'parse has the expected number of nodes'
|
28
|
+
assert_equal 4, root.subtree.count(&:significant?),
|
29
|
+
'parse has the expected balance of significant and ignorable nodes'
|
30
|
+
end
|
31
|
+
|
32
|
+
class FixedCount
|
33
|
+
extend Gullah
|
34
|
+
|
35
|
+
rule :a, 'a{2}'
|
36
|
+
|
37
|
+
leaf :a, /\S+/
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_fixed_count
|
41
|
+
parses = FixedCount.parse 'foo bar baz'
|
42
|
+
assert_equal 2, parses.length, '2 optimal parses'
|
43
|
+
parses.each do |p|
|
44
|
+
assert_equal 1, p.roots.length, 'parse has a root node'
|
45
|
+
root = p.roots.first
|
46
|
+
assert_equal 2, root.subtree.select(&:nonterminal?).count, 'parse has 2 nonterminal nodes'
|
47
|
+
assert root.subtree.select(&:nonterminal?).each do |_n|
|
48
|
+
assert_equal 2, b.children.length, 'nonterminal nodes each have 2 children'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Balanced
|
54
|
+
extend Gullah
|
55
|
+
|
56
|
+
rule :a, 'a{2}', tests: %i[balanced]
|
57
|
+
|
58
|
+
leaf :a, /\S+/
|
59
|
+
|
60
|
+
# we only want a perfectly symmetrical tree
|
61
|
+
def balanced(n)
|
62
|
+
if n.children.first.size == n.children.last.size
|
63
|
+
:pass
|
64
|
+
else
|
65
|
+
:fail
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_test
|
71
|
+
parses = Balanced.parse 'foo bar baz plugh'
|
72
|
+
assert_equal 1, parses.length, '1 optimal parse'
|
73
|
+
parse = parses.first
|
74
|
+
assert_equal 1, parse.roots.length, 'parse has a root node'
|
75
|
+
root = parse.roots.first
|
76
|
+
assertion = root.subtree
|
77
|
+
.select(&:nonterminal?)
|
78
|
+
.select { |n| n.name == :a }.all? do |n|
|
79
|
+
(att = n.attributes[:satisfied]) && att.include?([:balanced])
|
80
|
+
end
|
81
|
+
assert assertion, 'the balanced nodes are marked as such'
|
82
|
+
end
|
83
|
+
|
84
|
+
class Trash
|
85
|
+
extend Gullah
|
86
|
+
|
87
|
+
keep_whitespace
|
88
|
+
|
89
|
+
leaf :word, /\w+/
|
90
|
+
ignore :ws, /\s+/
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_trash
|
94
|
+
parses = Trash.parse 'There may be punctuation.'
|
95
|
+
assert_equal 1, parses.length, 'only one parse'
|
96
|
+
parse = parses.first
|
97
|
+
assert_equal 8, parse.roots.length, 'there are 8 nodes in the parse'
|
98
|
+
assert parse.roots.all?(&:leaf?), 'all nodes are leaf nodes'
|
99
|
+
assert_equal 3, parse.roots.select { |n| n.name == :ws }.count, 'there are 3 whitespace nodes'
|
100
|
+
assert_equal 3, parse.roots.select(&:ignorable?).count, 'there are 3 ignorable nodes'
|
101
|
+
assert_equal 4, parse.roots.select { |n| n.name == :word }.count, 'there are 4 word nodes'
|
102
|
+
assert_equal 1, parse.roots.select(&:trash?).count, 'there is 1 trash node'
|
103
|
+
last_node = parse.roots.last
|
104
|
+
assert last_node.trash?, 'the last node is the trash node'
|
105
|
+
end
|
106
|
+
|
107
|
+
# TODO: order dependence problem
|
108
|
+
class Cat
|
109
|
+
extend Gullah
|
110
|
+
|
111
|
+
rule :S, 'NP VP'
|
112
|
+
rule :NP, 'D NB'
|
113
|
+
rule :NB, 'A* N'
|
114
|
+
rule :VP, 'VP PP'
|
115
|
+
rule :VP, 'V'
|
116
|
+
rule :PP, 'P NP'
|
117
|
+
rule :P, 'prepositions'
|
118
|
+
rule :V, 'verbs'
|
119
|
+
rule :D, 'determiners'
|
120
|
+
rule :N, 'nouns'
|
121
|
+
rule :A, 'adjectives'
|
122
|
+
|
123
|
+
leaf :determiners, /\b(the|a)\b/i
|
124
|
+
leaf :nouns, /\b(cat|mat)\b/i
|
125
|
+
leaf :prepositions, /\b(on|in|around|above|beside)\b/i
|
126
|
+
leaf :verbs, /\b(sat|slept|moped)\b/
|
127
|
+
leaf :adjectives, /\b(big|small|hairy|bald)\b/i
|
128
|
+
ignore :whatever, /[^\w\s]+/
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_cat
|
132
|
+
parses = Cat.parse 'The cat sat on the mat.'
|
133
|
+
assert_equal 1, parses.length, 'there is only one parse of this sentence'
|
134
|
+
parse = parses.first
|
135
|
+
assert_equal 1, parse.roots.reject(&:ignorable?).count, 'there is a root node for this parse'
|
136
|
+
root = parse.roots.first
|
137
|
+
assert_equal :S, root.name, 'the root node is a sentence'
|
138
|
+
vp = root.descendants.find { |d| d.name == :VP }&.descendants&.find { |d| d.name == :V }
|
139
|
+
assert_equal 'sat', vp&.text, 'we have the expected verb'
|
140
|
+
end
|
141
|
+
|
142
|
+
class SubRules
|
143
|
+
extend Gullah
|
144
|
+
|
145
|
+
rule :s, 'thing+'
|
146
|
+
rule :thing, 'word | integer'
|
147
|
+
|
148
|
+
leaf :word, /[a-z]+/i
|
149
|
+
leaf :integer, /\d+/
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_subrules
|
153
|
+
parses = SubRules.parse '123 word'
|
154
|
+
assert_equal 1, parses.length, 'there is only one parse of this sentence'
|
155
|
+
parse = parses.first
|
156
|
+
assert_equal 1, parse.roots.reject(&:ignorable?).count, 'there is a root node for this parse'
|
157
|
+
root = parse.roots.first
|
158
|
+
assert_equal :s, root.name, 'found expected root'
|
159
|
+
assert_equal 2, root.subtree.select { |n| n.name == :thing }.count, 'two things'
|
160
|
+
assert_equal 1, root.subtree.select { |n| n.name == :word }.count, 'one word'
|
161
|
+
assert_equal 1, root.subtree.select { |n| n.name == :integer }.count, 'one integer'
|
162
|
+
end
|
163
|
+
|
164
|
+
class SubRulesWithTest
|
165
|
+
extend Gullah
|
166
|
+
|
167
|
+
rule :s, 'thing+'
|
168
|
+
rule :thing, 'word | integer', tests: %i[foo]
|
169
|
+
|
170
|
+
leaf :word, /[a-z]+/i
|
171
|
+
leaf :integer, /\d+/
|
172
|
+
|
173
|
+
def foo(_n)
|
174
|
+
%i[pass etc]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_subrules_with_test
|
179
|
+
parses = SubRulesWithTest.parse '123 word'
|
180
|
+
assert_equal 1, parses.length, 'there is only one parse of this sentence'
|
181
|
+
parse = parses.first
|
182
|
+
assert_equal 1, parse.roots.reject(&:ignorable?).count, 'there is a root node for this parse'
|
183
|
+
root = parse.roots.first
|
184
|
+
assert_equal :s, root.name, 'found expected root'
|
185
|
+
things = root.subtree.select { |n| n.name == :thing }
|
186
|
+
assert_equal 2, things.count, 'two things'
|
187
|
+
assert things.all? { |n| n.attributes[:satisfied].include?(%i[foo etc]) }, 'passing tests stuff in extra bits'
|
188
|
+
assert_equal 1, root.subtree.select { |n| n.name == :word }.count, 'one word'
|
189
|
+
assert_equal 1, root.subtree.select { |n| n.name == :integer }.count, 'one integer'
|
190
|
+
end
|
191
|
+
|
192
|
+
class SubRulesWithAncestorTest
|
193
|
+
extend Gullah
|
194
|
+
|
195
|
+
rule :s, 'thing+'
|
196
|
+
rule :thing, 'word | integer', tests: %i[foo]
|
197
|
+
|
198
|
+
leaf :word, /[a-z]+/i
|
199
|
+
leaf :integer, /\d+/
|
200
|
+
|
201
|
+
def foo(_root, _n)
|
202
|
+
:pass
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_subrules_with_ancestor_test
|
207
|
+
parses = SubRulesWithAncestorTest.parse '123 word'
|
208
|
+
assert_equal 1, parses.length, 'there is only one parse of this sentence'
|
209
|
+
parse = parses.first
|
210
|
+
assert_equal 1, parse.roots.reject(&:ignorable?).count, 'there is a root node for this parse'
|
211
|
+
root = parse.roots.first
|
212
|
+
assert_equal :s, root.name, 'found expected root'
|
213
|
+
things = root.subtree.select { |n| n.name == :thing }
|
214
|
+
assert_equal 2, things.count, 'two things'
|
215
|
+
things.each do |thing|
|
216
|
+
assert root.attributes[:satisfied_ancestor].include?([:foo, thing.position]),
|
217
|
+
'ancestor is marked when ancestor test passes'
|
218
|
+
assert thing.attributes[:satisfied_descendant].include?([:foo, root.position]),
|
219
|
+
'descendant is marked when ancestor test passes'
|
220
|
+
end
|
221
|
+
assert_equal 1, root.subtree.select { |n| n.name == :word }.count, 'one word'
|
222
|
+
assert_equal 1, root.subtree.select { |n| n.name == :integer }.count, 'one integer'
|
223
|
+
end
|
224
|
+
|
225
|
+
class LeftAncestor
|
226
|
+
extend Gullah
|
227
|
+
|
228
|
+
rule :s, 'word+'
|
229
|
+
rule :word, 'foo | bar'
|
230
|
+
|
231
|
+
leaf :foo, /foo/, tests: %i[preceded_by_bar]
|
232
|
+
leaf :bar, /bar/
|
233
|
+
|
234
|
+
def preceded_by_bar(root, n)
|
235
|
+
if n.prior.any? { |other| other.name == :bar }
|
236
|
+
:pass
|
237
|
+
elsif root.name == :s
|
238
|
+
:fail
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_left_ancestor
|
244
|
+
parses = LeftAncestor.parse 'bar foo'
|
245
|
+
assert_equal 1, parses.length, 'one parse'
|
246
|
+
parse = parses.first
|
247
|
+
assert_equal 1, parse.roots.length, 'one root for parse'
|
248
|
+
root = parse.roots.first
|
249
|
+
assert_equal 1, root.subtree.count { |n| n.name == :foo }, 'one foo'
|
250
|
+
parses = LeftAncestor.parse 'foo bar'
|
251
|
+
assert_equal 0, good(parses).length, "no good parses of 'foo bar'"
|
252
|
+
end
|
253
|
+
|
254
|
+
class RightAncestor
|
255
|
+
extend Gullah
|
256
|
+
|
257
|
+
rule :s, 'word+'
|
258
|
+
rule :word, 'foo | bar'
|
259
|
+
|
260
|
+
leaf :foo, /foo/, tests: %i[followed_by_bar]
|
261
|
+
leaf :bar, /bar/
|
262
|
+
|
263
|
+
def followed_by_bar(root, n)
|
264
|
+
if n.later.any? { |other| other.name == :bar }
|
265
|
+
:pass
|
266
|
+
elsif root.name == :s
|
267
|
+
:fail
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_right_ancestor
|
273
|
+
parses = RightAncestor.parse 'foo bar'
|
274
|
+
assert_equal 1, parses.length, 'one parse'
|
275
|
+
parse = parses.first
|
276
|
+
assert_equal 1, parse.roots.length, 'one root for parse'
|
277
|
+
root = parse.roots.first
|
278
|
+
assert_equal 1, root.subtree.count { |n| n.name == :foo }, 'one foo'
|
279
|
+
parses = RightAncestor.parse 'bar foo'
|
280
|
+
assert_equal 0, good(parses).length, "no good parses of 'bar foo'"
|
281
|
+
end
|
282
|
+
|
283
|
+
class LowerLimit
|
284
|
+
extend Gullah
|
285
|
+
|
286
|
+
rule :s, 'a{2,}'
|
287
|
+
leaf :a, /a/
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_lower_limit
|
291
|
+
parses = LowerLimit.parse 'a'
|
292
|
+
assert_equal 0, good(parses).length, 'we need at least one'
|
293
|
+
parses = LowerLimit.parse 'a a'
|
294
|
+
assert_equal 1, good(parses).length, 'two is enough'
|
295
|
+
parses = LowerLimit.parse 'a a a'
|
296
|
+
assert_equal 1, good(parses).length, 'more than two is fine'
|
297
|
+
end
|
298
|
+
|
299
|
+
class TwoLimits
|
300
|
+
extend Gullah
|
301
|
+
|
302
|
+
rule :s, 'a{2,3}'
|
303
|
+
leaf :a, /a/
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_two_limits
|
307
|
+
parses = LowerLimit.parse 'a'
|
308
|
+
assert_equal 0, good(parses).length, 'we need at least one'
|
309
|
+
parses = LowerLimit.parse 'a a'
|
310
|
+
assert_equal 1, good(parses).length, 'two is enough'
|
311
|
+
parses = LowerLimit.parse 'a a a'
|
312
|
+
assert_equal 1, good(parses).length, 'three is also good'
|
313
|
+
parses = LowerLimit.parse 'a a a a'
|
314
|
+
assert_equal 1, good(parses).length, 'four is too many'
|
315
|
+
end
|
316
|
+
|
317
|
+
class HoweverMany
|
318
|
+
extend Gullah
|
319
|
+
|
320
|
+
rule :s, 'b a*'
|
321
|
+
leaf :a, /a/
|
322
|
+
leaf :b, /b/
|
323
|
+
end
|
324
|
+
|
325
|
+
def test_however_many
|
326
|
+
parses = HoweverMany.parse 'b'
|
327
|
+
assert_equal 1, good(parses).length, "we don't even need one"
|
328
|
+
parses = HoweverMany.parse 'b a'
|
329
|
+
assert_equal 1, good(parses).length, 'but we can take one'
|
330
|
+
parses = HoweverMany.parse 'b a a'
|
331
|
+
assert_equal 1, good(parses).length, 'and we can take more than one'
|
332
|
+
end
|
333
|
+
|
334
|
+
class OneOrNone
|
335
|
+
extend Gullah
|
336
|
+
|
337
|
+
rule :s, 'b a?'
|
338
|
+
leaf :a, /a/
|
339
|
+
leaf :b, /b/
|
340
|
+
end
|
341
|
+
|
342
|
+
def test_one_or_none
|
343
|
+
parses = OneOrNone.parse 'b'
|
344
|
+
assert_equal 1, good(parses).length, "we don't even need one"
|
345
|
+
parses = OneOrNone.parse 'b a'
|
346
|
+
assert_equal 1, good(parses).length, 'but we can take one'
|
347
|
+
parses = OneOrNone.parse 'b a a'
|
348
|
+
assert_equal 0, good(parses).length, "and we can't take more than one"
|
349
|
+
end
|
350
|
+
|
351
|
+
class Literal
|
352
|
+
extend Gullah
|
353
|
+
|
354
|
+
rule :money, '"$" digits'
|
355
|
+
leaf :digits, /\d+/
|
356
|
+
end
|
357
|
+
|
358
|
+
def test_literal
|
359
|
+
parses = Literal.parse '$12'
|
360
|
+
assert_equal 1, good(parses).length, 'it parses'
|
361
|
+
parse = parses.first
|
362
|
+
assert_equal 1, parse.length, "there's a root node"
|
363
|
+
root = parse.roots.first
|
364
|
+
assert_equal 2, root.leaves.count, 'there are two leaves'
|
365
|
+
assert_equal '$', root.leaves.first.text, "the first leaf is '$'"
|
366
|
+
assert_equal '12', root.leaves.last.text, "the last leaf is '12'"
|
367
|
+
end
|
368
|
+
|
369
|
+
class FiltersAndFailures
|
370
|
+
extend Gullah
|
371
|
+
|
372
|
+
rule :numbers, 'integer+'
|
373
|
+
rule :integer, 'prime | nonprime'
|
374
|
+
rule :prime, 'number', tests: [:prime]
|
375
|
+
rule :nonprime, 'number', tests: [:nonprime]
|
376
|
+
|
377
|
+
leaf :number, /\d+/
|
378
|
+
|
379
|
+
def prime(n)
|
380
|
+
if [2, 3, 5, 7].include?(n.text.to_i)
|
381
|
+
:pass
|
382
|
+
else
|
383
|
+
:fail
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def nonprime(n)
|
388
|
+
prime(n) == :pass ? :fail : :pass
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def test_filters_and_failures
|
393
|
+
parses = FiltersAndFailures.parse '1 2', filters: []
|
394
|
+
assert parses.length > 1, 'removing the filters gives us many parses'
|
395
|
+
assert parses.any?(&:success?), 'there is at least one correct parse'
|
396
|
+
assert parses.any?(&:failure?), 'there is at least one failure'
|
397
|
+
assert parses.any?(&:errors?), 'there is at least one erroneous parse'
|
398
|
+
n = parses.find(&:errors?).roots.find(&:error?)
|
399
|
+
assert !n.nil?, 'found a node that failed its test'
|
400
|
+
assert n.attributes[:failures].any? { |ar| [[:prime], [:nonprime]].include? ar }, 'nature of failure is marked'
|
401
|
+
end
|
402
|
+
|
403
|
+
class Ambiguous
|
404
|
+
extend Gullah
|
405
|
+
|
406
|
+
rule :S, 'verb NP'
|
407
|
+
rule :NP, 'determiner noun'
|
408
|
+
|
409
|
+
leaf :determiner, /\b(?:a|an|the)\b/i
|
410
|
+
leaf :verb, /\b(?:run|walk|talk)\b/i
|
411
|
+
leaf :noun, /\b(?:run|walk|talk)\b/i
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_ambiguous
|
415
|
+
parses = Ambiguous.parse 'walk the walk'
|
416
|
+
assert_equal 1, parses.length, 'got one good parse'
|
417
|
+
parse = parses.first
|
418
|
+
assert_equal 1, parse.length, 'got one root node'
|
419
|
+
root = parse.roots.first
|
420
|
+
noun = root.subtree.find { |n| n.name == :noun }
|
421
|
+
assert !noun.nil?, 'found a noun'
|
422
|
+
assert_equal 'walk', noun.text, "the noun is 'walk'"
|
423
|
+
assert_equal root.leaves.last, noun, 'the noun is the last leaf'
|
424
|
+
verb = root.subtree.find { |n| n.name == :verb }
|
425
|
+
assert !verb.nil?, 'found a verb'
|
426
|
+
assert_equal 'walk', verb.text, "the verb is 'walk'"
|
427
|
+
assert_equal root.leaves.first, verb, 'the verb is the first leaf'
|
428
|
+
end
|
429
|
+
|
430
|
+
class Escapes
|
431
|
+
extend Gullah
|
432
|
+
|
433
|
+
rule :stuff, %(name\\?+ "literal\\"")
|
434
|
+
leaf :"name?", /\w+/
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_escapes
|
438
|
+
parse = Escapes.first 'some words then literal"'
|
439
|
+
assert_equal 1, parse.length
|
440
|
+
root = parse.roots.first
|
441
|
+
assert_equal 8, root.size
|
442
|
+
assert_equal 3, (root.descendants.count { |n| n.name == :"name?" })
|
443
|
+
assert_equal :'literal"', root.leaves.last.name
|
444
|
+
end
|
445
|
+
|
446
|
+
private
|
447
|
+
|
448
|
+
def good(parses)
|
449
|
+
parses.select(&:success?)
|
450
|
+
end
|
451
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
require 'gullah'
|
6
|
+
require 'byebug'
|
7
|
+
|
8
|
+
# :stopdoc:
|
9
|
+
|
10
|
+
# to verify that we can make one parse quickly even for a big tree
|
11
|
+
class BigTreeTest < Minitest::Test
|
12
|
+
class Binary
|
13
|
+
extend Gullah
|
14
|
+
|
15
|
+
rule :a, 'a{2}'
|
16
|
+
|
17
|
+
leaf :a, /\S+/
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_this
|
21
|
+
parse = Binary.first '1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16'
|
22
|
+
assert_equal 1, parse.roots.length, 'the parse has a root'
|
23
|
+
root = parse.roots.first
|
24
|
+
assert_equal 31, root.subtree.reject(&:ignorable?).count, 'the tree has the expected number of nodes'
|
25
|
+
end
|
26
|
+
end
|