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
@@ -0,0 +1,167 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ include Rattler::Parsers
4
+
5
+ describe Rattler::Grammar::Analysis do
6
+
7
+ subject { described_class.new(grammar.rules) }
8
+
9
+ let(:grammar) { Rattler::Grammar::Grammar[rule_set] }
10
+
11
+ describe '#recursive?' do
12
+
13
+ context 'given a directly recursive rule' do
14
+
15
+ let(:rule_set) { RuleSet[
16
+ Rule[:a, Sequence[Match[/a/], Apply[:a]]]
17
+ ] }
18
+
19
+ it 'returns true' do
20
+ subject.recursive?(:a).should be_true
21
+ end
22
+ end
23
+
24
+ context 'given an indirectly recursive rule' do
25
+
26
+ let(:rule_set) { RuleSet[
27
+ Rule[:a, Sequence[Match[/a/], Apply[:b]]],
28
+ Rule[:b, Sequence[Match[/b/], Apply[:c]]],
29
+ Rule[:c, Sequence[Match[/c/], Apply[:a]]]
30
+ ] }
31
+
32
+ it 'returns true' do
33
+ subject.recursive?(:a).should be_true
34
+ end
35
+ end
36
+
37
+ context 'given a non-recursive rule' do
38
+
39
+ let(:rule_set) { RuleSet[
40
+ Rule[:a, Sequence[Match[/a/], Apply[:b]]],
41
+ Rule[:b, Sequence[Match[/b/], Apply[:c]]],
42
+ Rule[:c, Sequence[Match[/c/], Apply[:b]]]
43
+ ] }
44
+
45
+ it 'returns false' do
46
+ subject.recursive?(:a).should be_false
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '#left_recursive?' do
52
+
53
+ context 'given a directly left-recursive rule' do
54
+
55
+ let(:rule_set) { RuleSet[
56
+ Rule[:a, Apply[:a]]
57
+ ] }
58
+
59
+ it 'returns true' do
60
+ subject.left_recursive?(:a).should be_true
61
+ end
62
+ end
63
+
64
+ context 'given an indirectly left-recursive rule' do
65
+
66
+ let(:rule_set) { RuleSet[
67
+ Rule[:a, Apply[:b]],
68
+ Rule[:b, Apply[:c]],
69
+ Rule[:c, Apply[:a]]
70
+ ] }
71
+
72
+ it 'returns true' do
73
+ subject.left_recursive?(:a).should be_true
74
+ end
75
+ end
76
+
77
+ context 'given a non-recursive rule' do
78
+
79
+ let(:rule_set) { RuleSet[
80
+ Rule[:a, Apply[:b]],
81
+ Rule[:b, Apply[:c]],
82
+ Rule[:c, Apply[:b]]
83
+ ] }
84
+
85
+ it 'returns false' do
86
+ subject.left_recursive?(:a).should be_false
87
+ end
88
+ end
89
+
90
+ context 'given a recursive but not left-recursive rule' do
91
+
92
+ let(:rule_set) { RuleSet[
93
+ Rule[:a, Sequence[Match[/a/], Apply[:a]]]
94
+ ] }
95
+
96
+ it 'returns false' do
97
+ subject.left_recursive?(:a).should be_false
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#referenced_from?' do
103
+
104
+ context 'with non-recursive rules' do
105
+
106
+ let(:rule_set) { RuleSet[
107
+ Rule[:a, Apply[:b]],
108
+ Rule[:b, Apply[:c]],
109
+ Rule[:c, Match[/a/]],
110
+ Rule[:d, Match[/b/]]
111
+ ] }
112
+
113
+ context 'given a rule name and itself' do
114
+ it 'returns false' do
115
+ subject.referenced_from?(:a, :a).should be_false
116
+ end
117
+ end
118
+
119
+ context 'given a directly referenced rule name' do
120
+ it 'returns true' do
121
+ subject.referenced_from?(:a, :b).should be_true
122
+ end
123
+ end
124
+
125
+ context 'given an indirectly referenced rule name' do
126
+ it 'returns true' do
127
+ subject.referenced_from?(:a, :c).should be_true
128
+ end
129
+ end
130
+
131
+ context 'given a non-referenced rule name' do
132
+ it 'returns false' do
133
+ subject.referenced_from?(:a, :d).should be_false
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'with recursive rules' do
139
+
140
+ let(:rule_set) { RuleSet[
141
+ Rule[:a, Apply[:a]],
142
+ Rule[:b, Apply[:c]],
143
+ Rule[:c, Apply[:b]]
144
+ ] }
145
+
146
+ context 'given a directly recursive rule name and itself' do
147
+ it 'returns true' do
148
+ subject.referenced_from?(:a, :a).should be_true
149
+ end
150
+ end
151
+
152
+ context 'given an indirectly recursive rule name and itself' do
153
+ it 'returns true' do
154
+ subject.referenced_from?(:b, :b).should be_true
155
+ end
156
+ end
157
+
158
+ context 'given a referenced rule name' do
159
+ it 'returns true' do
160
+ subject.referenced_from?(:b, :c).should be_true
161
+ end
162
+ end
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -6,282 +6,272 @@ describe Rattler::Grammar::GrammarParser do
6
6
  include Rattler::Util::ParserSpecHelper
7
7
 
8
8
  let :posix_names do
9
- %w{alnum alpha blank cntrl digit graph lower print punct space upper xdigit word}
9
+ %w{alnum alpha blank cntrl digit graph lower print punct space upper xdigit}
10
10
  end
11
11
 
12
- it 'skips normal whitespace' do
13
- parsing(" \n\t foo").as(:identifier).should result_in('foo').at(8)
14
- end
15
-
16
- it 'skips comments' do
17
- parsing("\n# a comment\n\t foo").as(:identifier).
18
- should result_in('foo').at(18)
19
- end
20
-
21
- describe '#match(:identifier)' do
22
- it 'recognizes identifiers' do
23
- parsing(' fooBar ').as(:identifier).should result_in('fooBar').at(7)
24
- parsing(' FooBar ').as(:identifier).should result_in('FooBar').at(7)
25
- parsing(' EO ').as(:identifier).should result_in('EO').at(3)
26
- parsing(' EOFA ').as(:identifier).should result_in('EOFA').at(5)
27
- end
12
+ describe '#match(:expression)' do
28
13
 
29
- it 'does not recognize EOF as identifier' do
30
- parsing('EOF').as(:identifier).should fail
31
- end
32
- end
14
+ context 'given a string literal' do
33
15
 
34
- describe '#match(:eol)' do
35
- it 'recognizes end-of-line before end-of-file' do
36
- parsing("foo\nbar").from(3).as(:eol).should result_in(true).at(4)
37
- parsing("foobar").from(3).as(:eol).should fail
38
- end
16
+ context 'delimited by double quotes' do
17
+ it 'parses as a regex match' do
18
+ matching(%{ "a string" }).as(:expression).
19
+ should result_in(Match[/a\ string/]).at(11)
20
+ end
21
+ end
39
22
 
40
- it 'recognizes end-of-file as end-of-line' do
41
- parsing("foo\nbar").from(7).as(:eol).should result_in(true).at(7)
42
- end
43
- end
23
+ context 'delimited by single quotes' do
24
+ it 'parses as a regex match' do
25
+ matching(%{ 'a string' }).as(:expression).
26
+ should result_in(Match[/a\ string/]).at(11)
27
+ end
28
+ end
44
29
 
45
- describe '#match(:var_name)' do
46
- it 'recognizes variable names' do
47
- parsing(' fooBar ').as(:var_name).should result_in('fooBar').at(7)
48
- parsing(' FooBar ').as(:var_name).should fail
49
- end
50
- end
30
+ context 'using "general delimited text" syntax' do
31
+
32
+ context 'with "(" and ")"' do
33
+ it 'parses as a regex match' do
34
+ matching(' %(a string) ').as(:expression).
35
+ should result_in(Match[/a\ string/]).at(12)
36
+ end
37
+ end
38
+
39
+ context 'with "{" and "}"' do
40
+ it 'parses as a regex match' do
41
+ matching(' %{a string} ').as(:expression).
42
+ should result_in(Match[/a\ string/]).at(12)
43
+ end
44
+ end
45
+
46
+ context 'with "[" and "]"' do
47
+ it 'parses as a regex match' do
48
+ matching(' %[a string] ').as(:expression).
49
+ should result_in(Match[/a\ string/]).at(12)
50
+ end
51
+ end
52
+
53
+ context 'with "<" and ">"' do
54
+ it 'parses as a regex match' do
55
+ matching(' %<a string> ').as(:expression).
56
+ should result_in(Match[/a\ string/]).at(12)
57
+ end
58
+ end
59
+
60
+ context 'with arbitrary delimiters' do
61
+ it 'parses as a regex match' do
62
+ matching(' %!a string! ').as(:expression).
63
+ should result_in(Match[/a\ string/]).at(12)
64
+ end
65
+ end
66
+ end
51
67
 
52
- describe '#match(:const_name)' do
53
- it 'recognizes constant names' do
54
- parsing(' FooBar ').as(:const_name).should result_in('FooBar').at(7)
55
- parsing(' Foo::Bar ').as(:const_name).should result_in('Foo').at(4)
56
- parsing(' fooBar ').as(:const_name).should fail
57
- end
58
- end
68
+ context 'delimited by backquotes' do
69
+ it 'parses as a word literal' do
70
+ matching(%{ `where` }).as(:expression).should result_in(
71
+ Token[Sequence[Match[/where/], Disallow[Match[/[[:alnum:]_]/]]]]
72
+ ).at(8)
73
+ end
74
+ end
59
75
 
60
- describe '#match(:constant)' do
61
- it 'recognizes constants' do
62
- parsing(' Foo::Bar ').as(:constant).should result_in('Foo::Bar').at(9)
63
- parsing(' FooBar ').as(:constant).should result_in('FooBar').at(7)
76
+ context 'with special characters' do
77
+ it 'escapes the special characters' do
78
+ matching(%{ "[...]" }).as(:expression).
79
+ should result_in(Match[/\[\.\.\.\]/]).at(8)
80
+ end
81
+ end
64
82
  end
65
- end
66
83
 
67
- describe '#match(:literal)' do
68
- it 'recognizes string literals' do
69
- parsing(%{ "a string" }).as(:literal).should result_in(%{"a string"}).at(11)
70
- parsing(%{ 'a string' }).as(:literal).should result_in(%{'a string'}).at(11)
71
- parsing(%q{ "a \"string\"" }).as(:literal).
72
- should result_in(%q{"a \"string\""}).at(15)
73
- end
74
- end
84
+ context 'given a character class expression' do
75
85
 
76
- describe '#match(:word_literal)' do
77
- it 'recognizes word literals' do
78
- parsing(' `then` ').as(:word_literal).should result_in("`then`").at(7)
79
- end
80
- end
86
+ context 'with a simple list of characters' do
87
+ it 'parses as a regex match' do
88
+ matching(' [abc123] ').as(:expression).
89
+ should result_in(Match[/[abc123]/]).at(9)
90
+ end
91
+ end
81
92
 
82
- describe '#match(:class_char)' do
83
- it 'recognizes normal characters as class characters' do
84
- parsing('ab').as(:class_char).should result_in('a').at(1)
85
- end
93
+ context 'with octal codes' do
94
+ it 'parses as a regex match' do
95
+ matching(' [\010\012\015] ').as(:expression).
96
+ should result_in(Match[/[\010\012\015]/]).at(15)
97
+ end
98
+ end
86
99
 
87
- it 'recognizes octal codes as class characters' do
88
- parsing('\\247').as(:class_char).should result_in('\\247').at(4)
89
- parsing('\\2474').as(:class_char).should result_in('\\247').at(4)
90
- end
100
+ context 'with hex codes' do
101
+ it 'parses as a regex match' do
102
+ matching(' [\x08\x0A\x0D] ').as(:expression).
103
+ should result_in(Match[/[\x08\x0A\x0D]/]).at(15)
104
+ end
105
+ end
91
106
 
92
- it 'recognizes hex codes as class characters' do
93
- parsing('\\x7e').as(:class_char).should result_in('\\x7e').at(4)
94
- parsing('\\x7ee').as(:class_char).should result_in('\\x7e').at(4)
95
- end
107
+ context 'with a range of characters' do
108
+ it 'parses as a regex match' do
109
+ matching(' [A-F] ').as(:expression).
110
+ should result_in(Match[/[A-F]/]).at(6)
111
+ end
112
+ end
96
113
 
97
- it 'recognizes the "any" character as a class character' do
98
- parsing('.').as(:class_char).should result_in('.').at(1)
99
- end
114
+ context 'with a range of octal codes' do
115
+ it 'parses as a regex match' do
116
+ matching(' [\000-\177] ').as(:expression).
117
+ should result_in(Match[/[\000-\177]/]).at(12)
118
+ end
119
+ end
100
120
 
101
- it 'recognizes escaped characters as class characters' do
102
- parsing('\\]').as(:class_char).should result_in('\\]').at(2)
103
- parsing('\\\\').as(:class_char).should result_in('\\\\').at(2)
121
+ context 'with a range of hex codes' do
122
+ it 'parses as a regex match' do
123
+ matching(' [\x00-\x7F] ').as(:expression).
124
+ should result_in(Match[/[\x00-\x7F]/]).at(12)
125
+ end
126
+ end
104
127
  end
105
- end
106
128
 
107
- describe '#match(:posix_name)' do
108
- it 'recognizes posix names' do
109
- for name in posix_names
110
- parsing(name).as(:posix_name).should result_in(name).at(name.length)
111
- parsing(name + 'a').as(:posix_name).should fail
112
- parsing(name + '0').as(:posix_name).should fail
113
- parsing(name + '_').as(:posix_name).should fail
129
+ context 'given a "."' do
130
+ it 'parses as Match[/./]' do
131
+ matching(' . ').as(:expression).should result_in(Match[/./]).at(2)
114
132
  end
115
133
  end
116
- end
117
134
 
118
- describe '#match(:range)' do
119
- it 'recognizes posix character classes as class ranges' do
120
- for name in posix_names
121
- parsing("[:#{name}:] ").as(:range).should result_in("[:#{name}:]").
122
- at(name.length + 4)
135
+ context 'given a lower-case word' do
136
+ it 'parses as an Apply' do
137
+ matching(' fooBar ').as(:expression).should result_in(Apply[:fooBar]).at(7)
123
138
  end
124
- parsing('[:foo:]').as(:range).should result_in('[').at(1)
125
139
  end
126
140
 
127
- it 'recognizes normal character ranges as class ranges' do
128
- parsing('A-Z ').as(:range).should result_in('A-Z').at(3)
141
+ context 'given a upper-case word' do
142
+ it 'parses it as an Apply' do
143
+ matching(' FooBar ').as(:expression).should result_in(Apply[:FooBar]).at(7)
144
+ end
129
145
  end
130
146
 
131
- it 'recognizes class characters as class ranges' do
132
- parsing('ab').as(:range).should result_in('a').at(1)
133
- end
134
- end
135
-
136
- describe '#match(:class)' do
137
- it 'recognizes character classes' do
138
- parsing(' [A-Za-z_] ').as(:class).should result_in('[A-Za-z_]').at(10)
147
+ context 'given the EOF keyword' do
148
+ it 'parses as Eof[]' do
149
+ matching(' EOF ').as(:expression).should result_in(Eof[]).at(4)
150
+ end
139
151
  end
140
- end
141
152
 
142
- describe '#match(:regexp)' do
143
- it 'recognizes regexps' do
144
- parsing(' /\\d+(?:\\.\\d+)?/ ').as(:regexp).
145
- should result_in('/\\d+(?:\\.\\d+)?/').at(16)
153
+ context 'given an upper-case POSIX class name' do
154
+ it 'parses as a Match of a POSIX character class' do
155
+ for name in posix_names
156
+ matching(" #{name.upcase} ").as(:expression).
157
+ should result_in(Match[Regexp.new("[[:#{name}:]]")]).
158
+ at(name.length + 1)
159
+ end
160
+ end
146
161
  end
147
- end
148
162
 
149
- describe '#match(:atom)' do
150
- it 'recognizes EOF as an eof atom' do
151
- parsing(' EOF ').as(:atom).should result_in(Eof[]).at(4)
163
+ context 'given the non-POSIX WORD class name' do
164
+ it 'parses as syntactic sugar for [[:alnum:]_]' do
165
+ matching(' WORD ').as(:expression).
166
+ should result_in(Match[/[[:alnum:]_]/]).at(5)
167
+ end
152
168
  end
153
169
 
154
- it 'recognizes "." as a regexp atom' do
155
- parsing(' . ').as(:atom).should result_in(Match[/./]).at(2)
170
+ it 'parses dispatch-action-attributed expressions' do
171
+ matching(' expr <Expr> ').as(:expression).
172
+ should result_in(DispatchAction[Apply[:expr], {:target => 'Expr', :method => 'parsed'}]).
173
+ at(12)
156
174
  end
157
175
 
158
- it 'recognizes uppercase posix character class names as regexp atoms' do
159
- for name in posix_names.map {|_| _.upcase } - ['WORD']
160
- parsing(" #{name} ").as(:atom).
161
- should result_in(Match[Regexp.compile("[[:#{name.downcase}:]]")]).
162
- at(name.length + 1)
163
- end
176
+ it 'parses direct-action-attributed expressions' do
177
+ matching(' digits {|_| _.to_i} ').as(:expression).
178
+ should result_in(DirectAction[Apply[:digits], '|_| _.to_i']).at(20)
164
179
  end
165
180
 
166
- it 'recognizes WORD as syntactic sugar for [[:alnum:]_]' do
167
- parsing(' WORD ').as(:atom).should result_in(Match[/[[:alnum:]_]/]).at(5)
181
+ it 'parses sequence expressions' do
182
+ matching(' name "=" value ').as(:expression).
183
+ should result_in(Sequence[Apply[:name], Match[%r{=}], Apply[:value]]).at(15)
168
184
  end
169
185
 
170
- it 'recognizes identifiers as apply atoms' do
171
- parsing(' expr ').as(:atom).should result_in(Apply[:expr]).at(5)
186
+ it 'parses attributed sequence expressions' do
187
+ matching(' name "=" value <Assign>').as(:expression).
188
+ should result_in(DispatchAction[
189
+ Sequence[Apply[:name], Match[%r{=}], Apply[:value]],
190
+ {:target => 'Assign', :method => 'parsed'}]).
191
+ at(24)
172
192
  end
173
193
 
174
- it 'recognizes string literals as match atoms' do
175
- parsing(%{ "a string" }).as(:atom).should result_in(Match[/a\ string/]).at(11)
194
+ it 'parses ordered choice expressions' do
195
+ matching(' string / number ').as(:expression).
196
+ should result_in(Choice[Apply[:string], Apply[:number]]).at(16)
176
197
  end
177
198
 
178
- it 'recognizes word literals as word atoms' do
179
- parsing(' `then` ').as(:atom).
180
- should result_in(Token[Sequence[Match[/then/], Disallow[Match[/[[:alnum:]_]/]]]]).at(7)
199
+ it 'skips normal whitespace' do
200
+ matching(" \n\t foo").as(:expression).should result_in(Apply[:foo]).at(8)
181
201
  end
182
202
 
183
- it 'recognizes character classes as match atoms' do
184
- parsing(' [A-Za-z_] ').as(:atom).should result_in(Match[/[A-Za-z_]/]).at(10)
203
+ it 'skips comments' do
204
+ matching("\n# a comment\n\t foo").as(:expression).
205
+ should result_in(Apply[:foo]).at(18)
185
206
  end
186
207
  end
187
208
 
188
209
  describe '#match(:attributed)' do
189
210
  it 'recognizes dispatch-action-attributed expressions' do
190
- parsing(' expr <Expr> ').as(:attributed).
211
+ matching(' expr <Expr> ').as(:attributed).
191
212
  should result_in(DispatchAction[Apply[:expr], {:target => 'Expr', :method => 'parsed'}]).
192
213
  at(12)
193
214
  end
194
215
 
195
216
  it 'recognizes direct-action-attributed expressions' do
196
- parsing(' digits {|_| _.to_i} ').as(:attributed).
217
+ matching(' digits {|_| _.to_i} ').as(:attributed).
197
218
  should result_in(DirectAction[Apply[:digits], '|_| _.to_i']).at(20)
198
219
  end
199
220
  end
200
221
 
201
222
  describe '#match(:term)' do
202
223
  it 'recognizes optional terms' do
203
- parsing(' expr? ').as(:term).should result_in(Optional[Apply[:expr]]).at(6)
224
+ matching(' expr? ').as(:term).should result_in(Optional[Apply[:expr]]).at(6)
204
225
  end
205
226
 
206
227
  it 'recognizes zero-or-more terms' do
207
- parsing(' expr* ').as(:term).should result_in(ZeroOrMore[Apply[:expr]]).at(6)
228
+ matching(' expr* ').as(:term).should result_in(ZeroOrMore[Apply[:expr]]).at(6)
208
229
  end
209
230
 
210
231
  it 'recognizes one-or-more terms' do
211
- parsing(' expr+ ').as(:term).should result_in(OneOrMore[Apply[:expr]]).at(6)
232
+ matching(' expr+ ').as(:term).should result_in(OneOrMore[Apply[:expr]]).at(6)
212
233
  end
213
234
 
214
235
  it 'recognizes assert terms' do
215
- parsing(' &expr ').as(:term).should result_in(Assert[Apply[:expr]]).at(6)
236
+ matching(' &expr ').as(:term).should result_in(Assert[Apply[:expr]]).at(6)
216
237
  end
217
238
 
218
239
  it 'recognizes disallow terms' do
219
- parsing(' !expr ').as(:term).should result_in(Disallow[Apply[:expr]]).at(6)
240
+ matching(' !expr ').as(:term).should result_in(Disallow[Apply[:expr]]).at(6)
220
241
  end
221
242
 
222
243
  it 'recognizes skip terms' do
223
- parsing(' ~expr ').as(:term).should result_in(Skip[Apply[:expr]]).at(6)
244
+ matching(' ~expr ').as(:term).should result_in(Skip[Apply[:expr]]).at(6)
224
245
  end
225
246
 
226
247
  it 'recognizes token terms' do
227
- parsing(' @expr ').as(:term).should result_in(Token[Apply[:expr]]).at(6)
248
+ matching(' @expr ').as(:term).should result_in(Token[Apply[:expr]]).at(6)
228
249
  end
229
250
 
230
251
  it 'recognizes labeled terms' do
231
- parsing(' val:expr ').as(:term).should result_in(Label[:val, Apply[:expr]]).at(9)
252
+ matching(' val:expr ').as(:term).should result_in(Label[:val, Apply[:expr]]).at(9)
232
253
  end
233
254
 
234
255
  it 'recognizes fail expressions' do
235
- parsing(' fail("bad!") ').as(:term).
256
+ matching(' fail("bad!") ').as(:term).
236
257
  should result_in(Fail[:expr, 'bad!']).at(13)
237
- parsing(' fail "bad!" ').as(:term).
258
+ matching(' fail "bad!" ').as(:term).
238
259
  should result_in(Fail[:expr, 'bad!']).at(12)
239
260
  end
240
261
 
241
262
  it 'recognizes fail_rule expressions' do
242
- parsing(' fail_rule("bad!") ').as(:term).
263
+ matching(' fail_rule("bad!") ').as(:term).
243
264
  should result_in(Fail[:rule, 'bad!']).at(18)
244
- parsing(' fail_rule "bad!" ').as(:term).
265
+ matching(' fail_rule "bad!" ').as(:term).
245
266
  should result_in(Fail[:rule, 'bad!']).at(17)
246
267
  end
247
268
 
248
269
  it 'recognizes fail_parse expressions' do
249
- parsing(' fail_parse("bad!") ').as(:term).
270
+ matching(' fail_parse("bad!") ').as(:term).
250
271
  should result_in(Fail[:parse, 'bad!']).at(19)
251
- parsing(' fail_parse "bad!" ').as(:term).
272
+ matching(' fail_parse "bad!" ').as(:term).
252
273
  should result_in(Fail[:parse, 'bad!']).at(18)
253
274
  end
254
275
  end
255
276
 
256
- describe '#match(:expression)' do
257
- it 'recognizes dispatch-action-attributed expressions' do
258
- parsing(' expr <Expr> ').as(:expression).
259
- should result_in(DispatchAction[Apply[:expr], {:target => 'Expr', :method => 'parsed'}]).
260
- at(12)
261
- end
262
-
263
- it 'recognizes direct-action-attributed expressions' do
264
- parsing(' digits {|_| _.to_i} ').as(:expression).
265
- should result_in(DirectAction[Apply[:digits], '|_| _.to_i']).at(20)
266
- end
267
-
268
- it 'recognizes sequence expressions' do
269
- parsing(' name "=" value ').as(:expression).
270
- should result_in(Sequence[Apply[:name], Match[%r{=}], Apply[:value]]).at(15)
271
- end
272
-
273
- it 'recognizes attributed sequence expressions' do
274
- parsing(' name "=" value <Assign>').as(:expression).
275
- should result_in(DispatchAction[
276
- Sequence[Apply[:name], Match[%r{=}], Apply[:value]],
277
- {:target => 'Assign', :method => 'parsed'}]).
278
- at(24)
279
- end
280
-
281
- it 'recognizes choice expressions' do
282
- parsing(' string | number ').as(:expression).
283
- should result_in(Choice[Apply[:string], Apply[:number]]).at(16)
284
- end
285
- end
286
-
287
277
  end