rattler 0.3.0 → 0.4.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 (182) hide show
  1. data/README.rdoc +57 -37
  2. data/features/command_line/dest_option.feature +8 -21
  3. data/features/command_line/lib_option.feature +37 -0
  4. data/features/command_line/parser_generator.feature +7 -4
  5. data/features/grammar/back_reference.feature +37 -0
  6. data/features/grammar/fail.feature +3 -3
  7. data/features/grammar/labels.feature +11 -3
  8. data/features/grammar/list_matching.feature +14 -5
  9. data/features/grammar/literal.feature +30 -4
  10. data/features/grammar/nonterminal.feature +1 -1
  11. data/features/grammar/ordered_choice.feature +2 -2
  12. data/features/grammar/skip_operator.feature +1 -1
  13. data/features/grammar/symantic_action.feature +7 -7
  14. data/features/grammar/whitespace.feature +2 -2
  15. data/features/step_definitions/grammar_steps.rb +2 -2
  16. data/lib/rattler/back_end.rb +1 -0
  17. data/lib/rattler/back_end/compiler.rb +19 -20
  18. data/lib/rattler/back_end/optimizer.rb +100 -0
  19. data/lib/rattler/back_end/optimizer/composite_reducing.rb +18 -0
  20. data/lib/rattler/back_end/optimizer/flatten_choice.rb +31 -0
  21. data/lib/rattler/back_end/optimizer/flatten_sequence.rb +59 -0
  22. data/lib/rattler/back_end/optimizer/flattening.rb +17 -0
  23. data/lib/rattler/back_end/optimizer/inline_regular_rules.rb +46 -0
  24. data/lib/rattler/back_end/optimizer/join_match_capturing_sequence.rb +71 -0
  25. data/lib/rattler/back_end/optimizer/join_match_choice.rb +37 -0
  26. data/lib/rattler/back_end/optimizer/join_match_matching_sequence.rb +38 -0
  27. data/lib/rattler/back_end/optimizer/join_match_sequence.rb +17 -0
  28. data/lib/rattler/back_end/optimizer/join_predicate_bare_match.rb +68 -0
  29. data/lib/rattler/back_end/optimizer/join_predicate_match.rb +17 -0
  30. data/lib/rattler/back_end/optimizer/join_predicate_nested_match.rb +37 -0
  31. data/lib/rattler/back_end/optimizer/join_predicate_or_bare_match.rb +68 -0
  32. data/lib/rattler/back_end/optimizer/join_predicate_or_match.rb +17 -0
  33. data/lib/rattler/back_end/optimizer/join_predicate_or_nested_match.rb +36 -0
  34. data/lib/rattler/back_end/optimizer/match_joining.rb +60 -0
  35. data/lib/rattler/back_end/optimizer/optimization.rb +94 -0
  36. data/lib/rattler/back_end/optimizer/optimization_context.rb +72 -0
  37. data/lib/rattler/back_end/optimizer/optimization_sequence.rb +37 -0
  38. data/lib/rattler/back_end/optimizer/optimize_children.rb +46 -0
  39. data/lib/rattler/back_end/optimizer/reduce_repeat_match.rb +44 -0
  40. data/lib/rattler/back_end/optimizer/remove_meaningless_wrapper.rb +32 -0
  41. data/lib/rattler/back_end/optimizer/simplify_redundant_repeat.rb +43 -0
  42. data/lib/rattler/back_end/optimizer/simplify_token_match.rb +38 -0
  43. data/lib/rattler/back_end/parser_generator.rb +21 -14
  44. data/lib/rattler/back_end/parser_generator/apply_generator.rb +35 -35
  45. data/lib/rattler/back_end/parser_generator/assert_generator.rb +29 -30
  46. data/lib/rattler/back_end/parser_generator/back_reference_generator.rb +93 -0
  47. data/lib/rattler/back_end/parser_generator/choice_generator.rb +33 -49
  48. data/lib/rattler/back_end/parser_generator/direct_action_generator.rb +14 -14
  49. data/lib/rattler/back_end/parser_generator/disallow_generator.rb +29 -30
  50. data/lib/rattler/back_end/parser_generator/dispatch_action_generator.rb +11 -13
  51. data/lib/rattler/back_end/parser_generator/expr_generator.rb +36 -56
  52. data/lib/rattler/back_end/parser_generator/fail_generator.rb +18 -18
  53. data/lib/rattler/back_end/parser_generator/group_match.rb +18 -0
  54. data/lib/rattler/back_end/parser_generator/group_match_generator.rb +76 -0
  55. data/lib/rattler/back_end/parser_generator/label_generator.rb +25 -6
  56. data/lib/rattler/back_end/parser_generator/list1_generator.rb +7 -7
  57. data/lib/rattler/back_end/parser_generator/list_generating.rb +19 -20
  58. data/lib/rattler/back_end/parser_generator/list_generator.rb +5 -5
  59. data/lib/rattler/back_end/parser_generator/match_generator.rb +52 -52
  60. data/lib/rattler/back_end/parser_generator/one_or_more_generator.rb +6 -6
  61. data/lib/rattler/back_end/parser_generator/optional_generator.rb +30 -29
  62. data/lib/rattler/back_end/parser_generator/predicate_propogating.rb +8 -8
  63. data/lib/rattler/back_end/parser_generator/repeat_generating.rb +23 -25
  64. data/lib/rattler/back_end/parser_generator/rule_generator.rb +27 -79
  65. data/lib/rattler/back_end/parser_generator/rule_set_generator.rb +102 -0
  66. data/lib/rattler/back_end/parser_generator/sequence_generator.rb +49 -41
  67. data/lib/rattler/back_end/parser_generator/skip_generator.rb +14 -20
  68. data/lib/rattler/back_end/parser_generator/skip_propogating.rb +4 -4
  69. data/lib/rattler/back_end/parser_generator/sub_generating.rb +6 -0
  70. data/lib/rattler/back_end/parser_generator/token_generator.rb +12 -12
  71. data/lib/rattler/back_end/parser_generator/token_propogating.rb +2 -2
  72. data/lib/rattler/back_end/parser_generator/zero_or_more_generator.rb +4 -4
  73. data/lib/rattler/grammar.rb +4 -3
  74. data/lib/rattler/grammar/analysis.rb +91 -0
  75. data/lib/rattler/grammar/grammar.rb +37 -25
  76. data/lib/rattler/grammar/grammar_parser.rb +19 -11
  77. data/lib/rattler/grammar/metagrammar.rb +569 -800
  78. data/lib/rattler/grammar/rattler.rtlr +162 -144
  79. data/lib/rattler/parsers.rb +5 -1
  80. data/lib/rattler/parsers/action_code.rb +29 -15
  81. data/lib/rattler/parsers/apply.rb +5 -5
  82. data/lib/rattler/parsers/assert.rb +4 -18
  83. data/lib/rattler/parsers/back_reference.rb +46 -0
  84. data/lib/rattler/parsers/choice.rb +6 -39
  85. data/lib/rattler/parsers/combinator_parser.rb +32 -0
  86. data/lib/rattler/parsers/combining.rb +3 -29
  87. data/lib/rattler/parsers/direct_action.rb +27 -30
  88. data/lib/rattler/parsers/disallow.rb +4 -18
  89. data/lib/rattler/parsers/dispatch_action.rb +30 -25
  90. data/lib/rattler/parsers/label.rb +9 -18
  91. data/lib/rattler/parsers/list.rb +3 -34
  92. data/lib/rattler/parsers/list1.rb +4 -36
  93. data/lib/rattler/parsers/list_parser.rb +64 -0
  94. data/lib/rattler/parsers/match.rb +7 -42
  95. data/lib/rattler/parsers/node_code.rb +44 -0
  96. data/lib/rattler/parsers/one_or_more.rb +7 -27
  97. data/lib/rattler/parsers/optional.rb +5 -25
  98. data/lib/rattler/parsers/parser.rb +16 -44
  99. data/lib/rattler/parsers/parser_dsl.rb +13 -3
  100. data/lib/rattler/parsers/predicate.rb +4 -12
  101. data/lib/rattler/parsers/rule.rb +18 -19
  102. data/lib/rattler/parsers/rule_set.rb +63 -0
  103. data/lib/rattler/parsers/sequence.rb +12 -46
  104. data/lib/rattler/parsers/skip.rb +12 -26
  105. data/lib/rattler/parsers/token.rb +6 -21
  106. data/lib/rattler/parsers/zero_or_more.rb +6 -26
  107. data/lib/rattler/runner.rb +66 -28
  108. data/lib/rattler/runtime/extended_packrat_parser.rb +26 -20
  109. data/lib/rattler/runtime/packrat_parser.rb +17 -21
  110. data/lib/rattler/runtime/parser.rb +12 -2
  111. data/lib/rattler/runtime/recursive_descent_parser.rb +3 -11
  112. data/lib/rattler/util.rb +2 -1
  113. data/lib/rattler/util/graphviz.rb +29 -0
  114. data/lib/rattler/util/graphviz/digraph_builder.rb +71 -0
  115. data/lib/rattler/util/graphviz/node_builder.rb +84 -0
  116. data/lib/rattler/util/node.rb +37 -19
  117. data/lib/rattler/util/parser_spec_helper.rb +61 -35
  118. data/spec/rattler/back_end/compiler_spec.rb +6 -860
  119. data/spec/rattler/back_end/optimizer/flatten_choice_spec.rb +70 -0
  120. data/spec/rattler/back_end/optimizer/flatten_sequence_spec.rb +130 -0
  121. data/spec/rattler/back_end/optimizer/inline_regular_rules_spec.rb +80 -0
  122. data/spec/rattler/back_end/optimizer/join_match_capturing_sequence_spec.rb +241 -0
  123. data/spec/rattler/back_end/optimizer/join_match_choice_spec.rb +100 -0
  124. data/spec/rattler/back_end/optimizer/join_match_matching_sequence_spec.rb +112 -0
  125. data/spec/rattler/back_end/optimizer/join_predicate_bare_match_spec.rb +194 -0
  126. data/spec/rattler/back_end/optimizer/join_predicate_nested_match_spec.rb +180 -0
  127. data/spec/rattler/back_end/optimizer/join_predicate_or_bare_match_spec.rb +153 -0
  128. data/spec/rattler/back_end/optimizer/join_predicate_or_nested_match_spec.rb +153 -0
  129. data/spec/rattler/back_end/optimizer/reduce_repeat_match_spec.rb +98 -0
  130. data/spec/rattler/back_end/optimizer/simplify_redundant_repeat_spec.rb +226 -0
  131. data/spec/rattler/back_end/optimizer/simplify_token_match_spec.rb +85 -0
  132. data/spec/rattler/back_end/parser_generator/apply_generator_spec.rb +38 -33
  133. data/spec/rattler/back_end/parser_generator/assert_generator_spec.rb +38 -33
  134. data/spec/rattler/back_end/parser_generator/back_reference_generator_spec.rb +181 -0
  135. data/spec/rattler/back_end/parser_generator/choice_generator_spec.rb +38 -33
  136. data/spec/rattler/back_end/parser_generator/direct_action_generator_spec.rb +38 -33
  137. data/spec/rattler/back_end/parser_generator/disallow_generator_spec.rb +38 -33
  138. data/spec/rattler/back_end/parser_generator/dispatch_action_generator_spec.rb +38 -33
  139. data/spec/rattler/back_end/parser_generator/group_match_generator_spec.rb +185 -0
  140. data/spec/rattler/back_end/parser_generator/label_generator_spec.rb +38 -33
  141. data/spec/rattler/back_end/parser_generator/list1_generator_spec.rb +10 -5
  142. data/spec/rattler/back_end/parser_generator/list_generator_spec.rb +10 -5
  143. data/spec/rattler/back_end/parser_generator/match_generator_spec.rb +38 -33
  144. data/spec/rattler/back_end/parser_generator/one_or_more_generator_spec.rb +38 -33
  145. data/spec/rattler/back_end/parser_generator/optional_generator_spec.rb +38 -33
  146. data/spec/rattler/back_end/parser_generator/rule_generator_spec.rb +13 -46
  147. data/spec/rattler/back_end/parser_generator/rule_set_generator_spec.rb +97 -0
  148. data/spec/rattler/back_end/parser_generator/sequence_generator_spec.rb +38 -33
  149. data/spec/rattler/back_end/parser_generator/skip_generator_spec.rb +38 -33
  150. data/spec/rattler/back_end/parser_generator/token_generator_spec.rb +38 -33
  151. data/spec/rattler/back_end/parser_generator/zero_or_more_generator_spec.rb +39 -34
  152. data/spec/rattler/back_end/shared_compiler_examples.rb +885 -0
  153. data/spec/rattler/grammar/analysis_spec.rb +167 -0
  154. data/spec/rattler/grammar/grammar_parser_spec.rb +169 -179
  155. data/spec/rattler/grammar/grammar_spec.rb +24 -21
  156. data/spec/rattler/parsers/action_code_spec.rb +64 -19
  157. data/spec/rattler/parsers/apply_spec.rb +9 -9
  158. data/spec/rattler/parsers/back_reference_spec.rb +38 -0
  159. data/spec/rattler/parsers/combinator_parser_spec.rb +14 -0
  160. data/spec/rattler/parsers/direct_action_spec.rb +16 -2
  161. data/spec/rattler/parsers/dispatch_action_spec.rb +15 -32
  162. data/spec/rattler/parsers/fail_spec.rb +6 -4
  163. data/spec/rattler/parsers/label_spec.rb +10 -28
  164. data/spec/rattler/parsers/node_code_spec.rb +48 -0
  165. data/spec/rattler/parsers/parser_dsl_spec.rb +1 -1
  166. data/spec/rattler/parsers/rule_set_spec.rb +35 -0
  167. data/spec/rattler/parsers/sequence_spec.rb +15 -24
  168. data/spec/rattler/runtime/extended_packrat_parser_spec.rb +22 -17
  169. data/spec/rattler/runtime/packrat_parser_spec.rb +1 -1
  170. data/spec/rattler/runtime/parse_node_spec.rb +15 -19
  171. data/spec/rattler/runtime/recursive_descent_parser_spec.rb +1 -1
  172. data/spec/rattler/runtime/shared_parser_examples.rb +61 -28
  173. data/spec/rattler/util/graphviz/node_builder_spec.rb +84 -0
  174. data/spec/rattler/util/node_spec.rb +92 -65
  175. data/spec/rattler_spec.rb +16 -16
  176. data/spec/support/combinator_parser_spec_helper.rb +19 -18
  177. data/spec/support/compiler_spec_helper.rb +56 -87
  178. data/spec/support/runtime_parser_spec_helper.rb +6 -14
  179. metadata +117 -22
  180. data/features/grammar/regex.feature +0 -24
  181. data/lib/rattler/parsers/match_joining.rb +0 -67
  182. data/lib/rattler/parsers/rules.rb +0 -43
@@ -2,59 +2,92 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
2
 
3
3
  shared_examples_for 'a recursive descent parser' do
4
4
  include RuntimeParserSpecHelper
5
-
5
+
6
+ describe '#parse' do
7
+
8
+ let(:grammar) { define_grammar do
9
+ rule(:word) { match(/\w+/) }
10
+ end }
11
+
12
+ it 'applies the start rule to the source' do
13
+ parsing('Hello World!').should result_in('Hello').at(5)
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ shared_examples_for 'a generated recursive descent parser' do
20
+ include RuntimeParserSpecHelper
21
+
22
+ it_behaves_like 'a recursive descent parser'
23
+
24
+ let(:parser_class) do
25
+ Rattler::BackEnd::Compiler.compile_parser described_class, grammar
26
+ end
27
+
6
28
  describe '#match' do
7
-
29
+
30
+ let(:grammar) { define_grammar do
31
+ rule(:word) { match(/\w+/) }
32
+ end }
33
+
8
34
  it 'dispatches to the correct match method' do
9
- given_rules { rule(:foo) { match(/\w+/) } }.
10
- parsing('Hello World!').as(:foo).should result_in('Hello').at(5)
35
+ matching('Hello World!').as(:word).should result_in('Hello').at(5)
11
36
  end
12
-
37
+
13
38
  it 'registers parse errors' do
14
- given_rules { rule(:foo) { match(/d+/) } }.
15
- parsing('Hello World!').as(:foo).
16
- should fail.at(0).with_message('foo expected')
39
+ matching('@').as(:word).
40
+ should fail.at(0).with_message('word expected')
17
41
  end
18
-
19
- it 'supports recursive rules' do
20
- given_rules do
42
+
43
+ context 'given recursive rules' do
44
+
45
+ let(:grammar) { define_grammar do
21
46
  rule :a do
22
47
  ( match(/\d/) & match(:a) \
23
48
  | match(/\d/) )
24
49
  end
25
- end.
26
- parsing('451a').as(:a).should result_in(['4', ['5', '1']]).at(3)
50
+ end }
51
+
52
+ it 'applies the rules recursively' do
53
+ matching('1234abc').as(:a).should result_in(['1', ['2', ['3', '4']]]).at(4)
54
+ end
27
55
  end
28
-
29
56
  end
30
-
57
+
31
58
  end
32
59
 
33
- shared_examples_for 'a packrat parser' do
60
+ shared_examples_for 'a generated packrat parser' do
34
61
  include RuntimeParserSpecHelper
35
62
 
36
- it_behaves_like 'a recursive descent parser'
63
+ it_behaves_like 'a generated recursive descent parser'
64
+
65
+ let(:parser_class) do
66
+ Rattler::BackEnd::Compiler.compile_parser described_class, grammar
67
+ end
37
68
 
38
69
  describe '#match' do
39
70
 
40
- it 'memoizes parse results' do
41
- example = given_rules do
71
+ context 'given recursive rules' do
72
+
73
+ let(:grammar) { define_grammar do
42
74
  rule :a do
43
75
  ( match(:b) & match('a') \
44
- | match(:b) & match('b') )
76
+ | match(:b) & eof )
45
77
  end
46
78
  rule :b do
47
- match('b')
79
+ match('b') & disallow(:a)
48
80
  end
49
- end.
50
- parsing('bb').as(:a)
51
- example.parser.should_receive(:match_b).and_return do
52
- example.parser.pos = example.parser.pos + 1
53
- 'b'
81
+ end }
82
+
83
+ let(:example) { matching('bag').as(:a) }
84
+
85
+ it 'memoizes parse results' do
86
+ example.parser.should_receive(:match_b!).
87
+ and_return { example.parser.pos += 1; 'b' }
88
+ example.should result_in(['b', 'a']).at(2)
54
89
  end
55
- example.should result_in(['b', 'b']).at(2)
56
90
  end
57
-
58
91
  end
59
92
 
60
93
  end
@@ -0,0 +1,84 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper')
2
+ require 'set'
3
+
4
+ describe Rattler::Util::GraphViz::NodeBuilder do
5
+
6
+ describe '#array_like?' do
7
+
8
+ context 'given an array' do
9
+ it 'returns true' do
10
+ subject.array_like?(['a']).should be_true
11
+ end
12
+ end
13
+
14
+ context 'given a hash' do
15
+ it 'returns true' do
16
+ subject.array_like?({:a => 'a'}).should be_true
17
+ end
18
+ end
19
+
20
+ context 'given a string' do
21
+ it 'returns false' do
22
+ subject.array_like?('a').should be_false
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#record_like?' do
28
+
29
+ context 'given a hash of simple values' do
30
+ it 'returns true' do
31
+ subject.record_like?({:a => 'a', :b => 'b'}).should be_true
32
+ end
33
+ end
34
+
35
+ context 'given a hash with compound values' do
36
+ it 'returns false' do
37
+ subject.record_like?({:a => ['a1', 'a2'], :b => 'b'}).should be_false
38
+ end
39
+ end
40
+
41
+ context 'given an array' do
42
+ it 'returns false' do
43
+ subject.record_like?(['a']).should be_false
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#each_child_of' do
49
+
50
+ context 'given an array' do
51
+
52
+ let(:object) { ['a', 'b', 'c'] }
53
+
54
+ it 'iterates over the members' do
55
+ children = []
56
+ subject.each_child_of(object) {|_| children << _ }
57
+ children.should == ['a', 'b', 'c']
58
+ end
59
+ end
60
+
61
+ context 'given a hash with compound values' do
62
+
63
+ let(:object) { {:a => ['a1', 'a2'], :b => 'b'} }
64
+
65
+ it 'iterates over the pairs' do
66
+ children = Set[]
67
+ subject.each_child_of(object) {|_| children << _ }
68
+ children.should == Set[[:a, ['a1', 'a2']], [:b, 'b']]
69
+ end
70
+ end
71
+
72
+ context 'given a hash with simple values' do
73
+
74
+ let(:object) { {:a => 'a', :b => 'b'} }
75
+
76
+ it 'does nothing' do
77
+ children = Set[]
78
+ subject.each_child_of(object) {|_| children << _}
79
+ children.should be_empty
80
+ end
81
+ end
82
+ end
83
+
84
+ end
@@ -3,100 +3,100 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
3
  include Rattler::Util
4
4
 
5
5
  describe Node do
6
-
6
+
7
7
  describe '#children' do
8
-
8
+
9
9
  context 'when the Node has children' do
10
10
  subject do
11
11
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'))
12
12
  end
13
-
13
+
14
14
  it 'returns the children' do
15
15
  subject.children.
16
16
  should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
17
17
  end
18
18
  end
19
-
19
+
20
20
  context 'when the Node has no children' do
21
-
21
+
22
22
  subject { Node.new }
23
-
23
+
24
24
  it 'returns an empty array' do
25
25
  subject.children.should == []
26
26
  end
27
27
  end
28
-
28
+
29
29
  context 'when the Node has children and attributes' do
30
30
  subject do
31
31
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'), :foo => 'bar')
32
32
  end
33
-
33
+
34
34
  it 'returns the children' do
35
35
  subject.children.
36
36
  should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
37
37
  end
38
38
  end
39
-
39
+
40
40
  context 'when the Node has only attributes' do
41
-
41
+
42
42
  subject { Node.new(:foo => 'bar', :bar => nil) }
43
-
43
+
44
44
  it 'returns an empty array' do
45
45
  subject.children.should == []
46
46
  end
47
47
  end
48
48
  end
49
-
49
+
50
50
  describe '#attrs' do
51
51
  context 'when the Node has attributes' do
52
-
52
+
53
53
  subject { Node.new(:foo => 'bar', :bar => nil) }
54
-
54
+
55
55
  it 'returns a Hash of the attributes' do
56
56
  subject.attrs.should == { :foo => 'bar', :bar => nil }
57
57
  end
58
58
  end
59
-
59
+
60
60
  context 'when the Node has no attributes' do
61
-
61
+
62
62
  subject { Node.new }
63
-
63
+
64
64
  it 'returns an empty Hash' do
65
65
  subject.attrs.should == {}
66
66
  end
67
67
  end
68
-
68
+
69
69
  context 'when the Node has children and attributes' do
70
70
  subject do
71
71
  Node.new(Node.new(:name => 'foo'), :foo => 'bar', :bar => nil)
72
72
  end
73
-
73
+
74
74
  it 'returns a Hash of the attributes' do
75
75
  subject.attrs.should == { :foo => 'bar', :bar => nil }
76
76
  end
77
77
  end
78
-
78
+
79
79
  context 'when the Node has only children' do
80
80
  subject do
81
81
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'))
82
82
  end
83
-
83
+
84
84
  it 'returns an empty Hash' do
85
85
  subject.attrs.should == {}
86
86
  end
87
87
  end
88
88
  end
89
-
89
+
90
90
  describe '#child' do
91
91
  context 'when the Node has children' do
92
92
  subject do
93
93
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'))
94
94
  end
95
-
95
+
96
96
  it 'returns the first child' do
97
97
  subject.child.should == Node.new(:name => 'foo')
98
98
  end
99
-
99
+
100
100
  context 'given an index' do
101
101
  it 'returns the child at the index' do
102
102
  subject.child(0).should == Node.new(:name => 'foo')
@@ -104,63 +104,63 @@ describe Node do
104
104
  end
105
105
  end
106
106
  end
107
-
107
+
108
108
  context 'when the Node has no children' do
109
-
109
+
110
110
  subject { Node.new }
111
-
111
+
112
112
  it 'returns nil' do
113
113
  subject.child.should be_nil
114
114
  end
115
115
  end
116
116
  end
117
-
117
+
118
118
  describe '#name' do
119
119
  context 'when the Node as a name attribute' do
120
-
120
+
121
121
  subject { Node.new(:name => 'foo' ) }
122
-
122
+
123
123
  it 'uses the name attribute as name' do
124
124
  subject.name.should == 'foo'
125
125
  end
126
126
  end
127
-
127
+
128
128
  context 'when the Node has no name attribute' do
129
-
129
+
130
130
  subject { Node.new }
131
-
131
+
132
132
  it 'uses the class name as name' do
133
133
  subject.name.should == 'Rattler::Util::Node'
134
134
  end
135
135
  end
136
136
  end
137
-
137
+
138
138
  describe '#[]' do
139
139
  context 'when the Node has children' do
140
140
  subject do
141
141
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'))
142
142
  end
143
-
143
+
144
144
  context 'given a single index' do
145
145
  it 'returns the child at the index' do
146
146
  subject[0].should == Node.new(:name => 'foo')
147
147
  subject[1].should == Node.new(:name => 'bar')
148
148
  end
149
149
  end
150
-
150
+
151
151
  context 'given a negative index' do
152
152
  it 'counts backward from the last child' do
153
153
  subject[-1].should == Node.new(:name => 'bar')
154
154
  subject[-2].should == Node.new(:name => 'foo')
155
155
  end
156
156
  end
157
-
157
+
158
158
  context 'given an out-of-range index' do
159
159
  it 'returns nil' do
160
160
  subject[3].should be_nil
161
161
  end
162
162
  end
163
-
163
+
164
164
  context 'given an index and a length' do
165
165
  it 'returns an array of children starting at the index' do
166
166
  subject[0, 2].should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
@@ -168,7 +168,7 @@ describe Node do
168
168
  subject[1, 1].should == [Node.new(:name => 'bar')]
169
169
  end
170
170
  end
171
-
171
+
172
172
  context 'given a negative index and a length' do
173
173
  it 'counts backward from the last child' do
174
174
  subject[-2, 2].should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
@@ -176,19 +176,19 @@ describe Node do
176
176
  subject[-1, 1].should == [Node.new(:name => 'bar')]
177
177
  end
178
178
  end
179
-
179
+
180
180
  context 'given an out-of-range index and a length' do
181
181
  it 'returns nil' do
182
182
  subject[3].should be_nil
183
183
  end
184
184
  end
185
-
185
+
186
186
  context 'given an index exactly one past the last child and a length' do
187
187
  it 'returns an empty array' do
188
188
  subject[2, 2].should == []
189
189
  end
190
190
  end
191
-
191
+
192
192
  context 'given a range' do
193
193
  it 'return an array of children indexed by the range' do
194
194
  subject[0..1].should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
@@ -196,7 +196,7 @@ describe Node do
196
196
  subject[1..1].should == [Node.new(:name => 'bar')]
197
197
  end
198
198
  end
199
-
199
+
200
200
  context 'given a range with a negative index' do
201
201
  it 'counts backward from the last child' do
202
202
  subject[-2..-1].should == [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
@@ -204,13 +204,13 @@ describe Node do
204
204
  subject[-1..-1].should == [Node.new(:name => 'bar')]
205
205
  end
206
206
  end
207
-
207
+
208
208
  context 'given a range with an out-of-range index' do
209
209
  it 'returns nil' do
210
210
  subject[3..3].should be_nil
211
211
  end
212
212
  end
213
-
213
+
214
214
  context 'given a range starting exactly one past the last child' do
215
215
  it 'returns an empty array' do
216
216
  subject[2..2].should == []
@@ -218,101 +218,128 @@ describe Node do
218
218
  end
219
219
  end
220
220
  end
221
-
221
+
222
222
  describe '#count' do
223
223
  context 'when the Node has children' do
224
224
  subject do
225
225
  Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar'))
226
226
  end
227
-
227
+
228
228
  it 'returns the number of children' do
229
229
  subject.count.should == 2
230
230
  end
231
231
  end
232
-
232
+
233
233
  context 'when the Node has no children' do
234
234
  subject { Node.new }
235
-
235
+
236
236
  it 'returns 0' do
237
237
  subject.count.should == 0
238
238
  end
239
239
  end
240
240
  end
241
-
241
+
242
242
  describe '#==' do
243
243
  subject { Node.new(Node.new(:name => 'foo'), :foo => 'bar') }
244
-
244
+
245
245
  context 'given a node with the same children and attributes' do
246
246
  it 'returns true' do
247
247
  subject.should == Node.new(Node.new(:name => 'foo'), :foo => 'bar')
248
248
  end
249
249
  end
250
-
250
+
251
251
  context 'given a node with different children' do
252
252
  it 'returns false' do
253
253
  subject.should_not == Node.new(Node.new(:name => 'boo'), :foo => 'bar')
254
254
  end
255
255
  end
256
-
256
+
257
257
  context 'given a node with extra children' do
258
258
  it 'returns false' do
259
259
  subject.should_not ==
260
260
  Node.new(Node.new(:name => 'boo'), Node.new, :foo => 'bar')
261
261
  end
262
262
  end
263
-
263
+
264
264
  context 'given a node with no children' do
265
265
  it 'returns false' do
266
266
  subject.should_not == Node.new(:foo => 'bar')
267
267
  end
268
268
  end
269
-
269
+
270
270
  context 'given a node with the same children and different attributes' do
271
271
  it 'returns false' do
272
272
  subject.should_not == Node.new(Node.new(:name => 'foo'), :foo => 'baz')
273
273
  end
274
274
  end
275
-
275
+
276
276
  context 'given a node with the same children and extra attributes' do
277
277
  it 'returns false' do
278
278
  subject.should_not ==
279
279
  Node.new(Node.new(:name => 'foo'), :foo => 'bar', :bar => 'baz')
280
280
  end
281
281
  end
282
-
282
+
283
283
  context 'given a node with the same children but no attributes' do
284
284
  it 'returns false' do
285
285
  subject.should_not == Node.new(Node.new(:name => 'foo'))
286
286
  end
287
287
  end
288
288
  end
289
-
289
+
290
+ describe '#each' do
291
+
292
+ subject { Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar')) }
293
+
294
+ let(:expected_items) do
295
+ [Node.new(:name => 'foo'), Node.new(:name => 'bar')]
296
+ end
297
+
298
+ it 'iterates over #children' do
299
+ subject.each {|_| _.should == expected_items.shift }
300
+ end
301
+ end
302
+
303
+ describe '#with_children' do
304
+
305
+ subject { Node.new(Node.new(:name => 'foo'), Node.new(:name => 'bar')) }
306
+
307
+ let :new_children do
308
+ [Node.new(:name => 'boo'), Node.new(:name => 'baz')]
309
+ end
310
+
311
+ it 'returns a new node with the children replaced' do
312
+ subject.with_children(new_children).should ==
313
+ Node.new(Node.new(:name => 'boo'), Node.new(:name => 'baz'))
314
+ end
315
+ end
316
+
290
317
  describe '#method_missing' do
291
-
318
+
292
319
  subject { Node.new(:foo => 'bar', :bar => nil) }
293
-
320
+
294
321
  it 'provides accessor methods for attributes' do
295
322
  subject.foo.should == 'bar'
296
323
  subject.bar.should == nil
297
324
  end
298
325
  end
299
-
326
+
300
327
  describe '#respond_to?' do
301
-
328
+
302
329
  subject { Node.new(:foo => 'bar', :bar => nil) }
303
-
330
+
304
331
  context 'given an attribute name' do
305
332
  it 'returns true' do
306
333
  subject.should respond_to(:foo)
307
334
  subject.should respond_to(:bar)
308
335
  end
309
336
  end
310
-
337
+
311
338
  context 'given a meaningless name' do
312
339
  it 'returns false' do
313
340
  subject.should_not respond_to(:fred)
314
341
  end
315
342
  end
316
343
  end
317
-
344
+
318
345
  end