citrus 2.2.2 → 2.3.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.
data/lib/citrus/file.rb CHANGED
@@ -28,34 +28,36 @@ module Citrus
28
28
 
29
29
  rule :file do
30
30
  all(:space, zero_or_more(any(:require, :grammar))) {
31
- find(:require).each {|r| require r.value }
32
- find(:grammar).map {|g| g.value }
31
+ if captures[:require]
32
+ captures[:require].each {|r| require r.value }
33
+ end
34
+
35
+ (captures[:grammar] || []).map {|g| g.value }
33
36
  }
34
37
  end
35
38
 
36
39
  rule :grammar do
37
- all(:grammar_keyword, :module_name, :grammar_body, :end_keyword) {
40
+ mod all(:grammar_keyword, :module_name, :grammar_body, :end_keyword) do
38
41
  include ModuleNameHelpers
39
42
 
40
43
  def value
41
44
  module_namespace.const_set(module_basename, grammar_body.value)
42
45
  end
43
- }
46
+ end
44
47
  end
45
48
 
46
49
  rule :grammar_body do
47
50
  zero_or_more(any(:include, :root, :rule)) {
48
51
  grammar = Grammar.new
49
52
 
50
- find(:include).map do |inc|
51
- grammar.include(inc.value)
53
+ if captures[:include]
54
+ captures[:include].each {|inc| grammar.include(inc.value) }
52
55
  end
53
56
 
54
- root = find(:root).last
55
57
  grammar.root(root.value) if root
56
58
 
57
- find(:rule).each do |r|
58
- grammar.rule(r.rule_name.value, r.value)
59
+ if captures[:rule]
60
+ captures[:rule].each {|r| grammar.rule(r.rule_name.value, r.value) }
59
61
  end
60
62
 
61
63
  grammar
@@ -71,42 +73,45 @@ module Citrus
71
73
  rule :rule_body do
72
74
  zero_or_one(:choice) {
73
75
  # An empty rule definition matches the empty string.
74
- matches.length > 0 ? choice.value : Rule.new('')
76
+ choice ? choice.value : Rule.for('')
75
77
  }
76
78
  end
77
79
 
78
80
  rule :choice do
79
- all(:sequence, zero_or_more([ :bar, :sequence ])) {
81
+ mod all(:sequence, zero_or_more([ :bar, :sequence ])) do
80
82
  def rules
81
- @rules ||= begin
82
- [ sequence.value ] + matches[1].matches.map do |m|
83
- m.matches[1].value
84
- end
85
- end
83
+ @rules ||= captures[:sequence].map {|s| s.value }
86
84
  end
87
85
 
88
86
  def value
89
87
  rules.length > 1 ? Choice.new(rules) : rules.first
90
88
  end
91
- }
89
+ end
92
90
  end
93
91
 
94
92
  rule :sequence do
95
- one_or_more(:expression) {
93
+ mod one_or_more(:label_expression) do
96
94
  def rules
97
- @rules ||= matches.map {|m| m.value }
95
+ @rules ||= captures[:label_expression].map {|e| e.value }
98
96
  end
99
97
 
100
98
  def value
101
99
  rules.length > 1 ? Sequence.new(rules) : rules.first
102
100
  end
101
+ end
102
+ end
103
+
104
+ rule :label_expression do
105
+ all(zero_or_one(:label), :expression) {
106
+ rule = expression.value
107
+ rule.label = label.value if label
108
+ rule
103
109
  }
104
110
  end
105
111
 
106
112
  rule :expression do
107
113
  all(:prefix, zero_or_one(:extension)) {
108
114
  rule = prefix.value
109
- extension = matches[1].first
110
115
  rule.extension = extension.value if extension
111
116
  rule
112
117
  }
@@ -115,7 +120,6 @@ module Citrus
115
120
  rule :prefix do
116
121
  all(zero_or_one(:predicate), :suffix) {
117
122
  rule = suffix.value
118
- predicate = matches[0].first
119
123
  rule = predicate.value(rule) if predicate
120
124
  rule
121
125
  }
@@ -124,44 +128,51 @@ module Citrus
124
128
  rule :suffix do
125
129
  all(:primary, zero_or_one(:repeat)) {
126
130
  rule = primary.value
127
- repeat = matches[1].first
128
131
  rule = repeat.value(rule) if repeat
129
132
  rule
130
133
  }
131
134
  end
132
135
 
133
136
  rule :primary do
134
- any(:grouping, :proxy, :string_terminal, :terminal)
137
+ any(:grouping, :proxy, :terminal)
135
138
  end
136
139
 
137
140
  rule :grouping do
138
- all(:lparen, :rule_body, :rparen) { rule_body.value }
141
+ all(:lparen, :rule_body, :rparen) {
142
+ rule_body.value
143
+ }
139
144
  end
140
145
 
141
146
  ## Lexical syntax
142
147
 
143
148
  rule :require do
144
- all(:require_keyword, :quoted_string) { quoted_string.value }
149
+ all(:require_keyword, :quoted_string) {
150
+ quoted_string.value
151
+ }
145
152
  end
146
153
 
147
154
  rule :include do
148
- all(:include_keyword, :module_name) {
155
+ mod all(:include_keyword, :module_name) do
149
156
  include ModuleNameHelpers
150
157
 
151
158
  def value
152
159
  module_namespace.const_get(module_basename)
153
160
  end
154
- }
161
+ end
155
162
  end
156
163
 
157
164
  rule :root do
158
- all(:root_keyword, :rule_name) { rule_name.value }
165
+ all(:root_keyword, :rule_name) {
166
+ rule_name.value
167
+ }
159
168
  end
160
169
 
161
170
  # Rule names may contain letters, numbers, underscores, and dashes. They
162
171
  # MUST start with a letter.
163
172
  rule :rule_name do
164
- all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) { first.to_s }
173
+ all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) {
174
+ first.to_s
175
+ }
165
176
  end
166
177
 
167
178
  rule :proxy do
@@ -180,6 +191,10 @@ module Citrus
180
191
  }
181
192
  end
182
193
 
194
+ rule :terminal do
195
+ any(:string_terminal, :regular_expression, :character_class, :dot)
196
+ end
197
+
183
198
  rule :string_terminal do
184
199
  any(:quoted_string, :case_insensitive_string) {
185
200
  StringTerminal.new(super(), flags)
@@ -187,50 +202,50 @@ module Citrus
187
202
  end
188
203
 
189
204
  rule :quoted_string do
190
- all(/(["'])(?:\\?.)*?\1/, :space) {
205
+ mod all(/(["'])(?:\\?.)*?\1/, :space) do
191
206
  def value
192
- eval(first)
207
+ eval(first.to_s)
193
208
  end
194
209
 
195
210
  def flags
196
211
  0
197
212
  end
198
- }
213
+ end
199
214
  end
200
215
 
201
216
  rule :case_insensitive_string do
202
- all(/`(?:\\?.)*?`/, :space) {
217
+ mod all(/`(?:\\?.)*?`/, :space) do
203
218
  def value
204
- eval(first.gsub(/^`|`$/, '"'))
219
+ eval(first.to_s.gsub(/^`|`$/, '"'))
205
220
  end
206
221
 
207
222
  def flags
208
223
  Regexp::IGNORECASE
209
224
  end
210
- }
211
- end
212
-
213
- rule :terminal do
214
- any(:regular_expression, :character_class, :dot) {
215
- Terminal.new(super())
216
- }
225
+ end
217
226
  end
218
227
 
219
228
  rule :regular_expression do
220
229
  all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
221
- eval(first)
230
+ Terminal.new(eval(first.to_s))
222
231
  }
223
232
  end
224
233
 
225
234
  rule :character_class do
226
235
  all(/\[(?:\\?.)*?\]/, :space) {
227
- Regexp.new('\A' + first, nil, 'n')
236
+ Terminal.new(Regexp.new(first.to_s, nil, 'n'))
228
237
  }
229
238
  end
230
239
 
231
240
  rule :dot do
232
241
  all('.', :space) {
233
- DOT
242
+ Terminal.new(DOT)
243
+ }
244
+ end
245
+
246
+ rule :label do
247
+ all(/[a-zA-Z0-9_]+/, :space, ':', :space) {
248
+ first.to_sym
234
249
  }
235
250
  end
236
251
 
@@ -239,23 +254,31 @@ module Citrus
239
254
  end
240
255
 
241
256
  rule :tag do
242
- all(:lt, :module_name, :gt) {
257
+ mod all(:lt, :module_name, :gt) do
243
258
  include ModuleNameHelpers
244
259
 
245
260
  def value
246
261
  module_namespace.const_get(module_basename)
247
262
  end
248
- }
263
+ end
249
264
  end
250
265
 
251
266
  rule :block do
252
267
  all(:lcurly, zero_or_more(any(:block, /[^{}]+/)), :rcurly) {
253
- eval('Proc.new ' + to_s, TOPLEVEL_BINDING)
268
+ proc = eval("Proc.new #{to_s}", TOPLEVEL_BINDING)
269
+
270
+ # Attempt to detect if this is a module block using some
271
+ # extremely simple heuristics.
272
+ if to_s =~ /\b(def|include) /
273
+ Module.new(&proc)
274
+ else
275
+ proc
276
+ end
254
277
  }
255
278
  end
256
279
 
257
280
  rule :predicate do
258
- any(:and, :not, :but, :label)
281
+ any(:and, :not, :but)
259
282
  end
260
283
 
261
284
  rule :and do
@@ -276,41 +299,27 @@ module Citrus
276
299
  }
277
300
  end
278
301
 
279
- rule :label do
280
- all(/[a-zA-Z0-9_]+/, :space, ':', :space) { |rule|
281
- Label.new(rule, first.to_s)
282
- }
283
- end
284
-
285
302
  rule :repeat do
286
- any(:question, :plus, :star) { |rule|
287
- Repeat.new(rule, min, max)
288
- }
303
+ any(:question, :plus, :star)
289
304
  end
290
305
 
291
306
  rule :question do
292
- all('?', :space) {
293
- def min; 0 end
294
- def max; 1 end
307
+ all('?', :space) { |rule|
308
+ Repeat.new(rule, 0, 1)
295
309
  }
296
310
  end
297
311
 
298
312
  rule :plus do
299
- all('+', :space) {
300
- def min; 1 end
301
- def max; Infinity end
313
+ all('+', :space) { |rule|
314
+ Repeat.new(rule, 1, Infinity)
302
315
  }
303
316
  end
304
317
 
305
318
  rule :star do
306
- all(/[0-9]*/, '*', /[0-9]*/, :space) {
307
- def min
308
- matches[0] == '' ? 0 : matches[0].to_i
309
- end
310
-
311
- def max
312
- matches[2] == '' ? Infinity : matches[2].to_i
313
- end
319
+ all(/[0-9]*/, '*', /[0-9]*/, :space) { |rule|
320
+ min = captures[0] == '' ? 0 : captures[0].to_i
321
+ max = captures[2] == '' ? Infinity : captures[2].to_i
322
+ Repeat.new(rule, min, max)
314
323
  }
315
324
  end
316
325
 
data/test/alias_test.rb CHANGED
@@ -14,7 +14,7 @@ class AliasTest < Test::Unit::TestCase
14
14
  rule_a = grammar.rule(:a)
15
15
  rule_b = grammar.rule(:b)
16
16
  events = rule_a.exec(Input.new('abc'))
17
- assert_equal([rule_a.id, rule_b.id, CLOSE, 3, CLOSE, 3], events)
17
+ assert_equal([rule_a, CLOSE, 3], events)
18
18
  end
19
19
 
20
20
  def test_exec_miss
@@ -38,11 +38,17 @@ class AliasTest < Test::Unit::TestCase
38
38
  rule_b2 = grammar2.rule(:b)
39
39
  rule_a1 = grammar1.rule(:a)
40
40
  events = rule_b2.exec(Input.new('abc'))
41
- assert_equal([rule_b2.id, rule_a1.id, CLOSE, 3, CLOSE, 3], events)
41
+ assert_equal([rule_b2, CLOSE, 3], events)
42
42
  end
43
43
 
44
44
  def test_to_s
45
45
  rule = Alias.new(:alpha)
46
46
  assert_equal('alpha', rule.to_s)
47
47
  end
48
+
49
+ def test_to_s
50
+ rule = Alias.new(:alpha)
51
+ rule.label = 'a_label'
52
+ assert_equal('a_label:alpha', rule.to_s)
53
+ end
48
54
  end
@@ -9,7 +9,7 @@ class AndPredicateTest < Test::Unit::TestCase
9
9
  def test_exec
10
10
  rule = AndPredicate.new('abc')
11
11
  events = rule.exec(Input.new('abc'))
12
- assert_equal([rule.id, CLOSE, 0], events)
12
+ assert_equal([rule, CLOSE, 0], events)
13
13
  end
14
14
 
15
15
  def test_exec_miss
@@ -19,14 +19,25 @@ class AndPredicateTest < Test::Unit::TestCase
19
19
  end
20
20
 
21
21
  def test_consumption
22
- rule = AndPredicate.new('abc')
22
+ rule = AndPredicate.new(Sequence.new(['a', 'b', 'c']))
23
+
23
24
  input = Input.new('abc')
24
25
  events = rule.exec(input)
25
26
  assert_equal(0, input.pos)
27
+
28
+ input = Input.new('def')
29
+ events = rule.exec(input)
30
+ assert_equal(0, input.pos)
26
31
  end
27
32
 
28
33
  def test_to_s
29
34
  rule = AndPredicate.new('a')
30
35
  assert_equal('&"a"', rule.to_s)
31
36
  end
37
+
38
+ def test_to_s_with_label
39
+ rule = AndPredicate.new('a')
40
+ rule.label = 'a_label'
41
+ assert_equal('a_label:&"a"', rule.to_s)
42
+ end
32
43
  end
@@ -10,10 +10,10 @@ class ButPredicateTest < Test::Unit::TestCase
10
10
  rule = ButPredicate.new('abc')
11
11
 
12
12
  events = rule.exec(Input.new('def'))
13
- assert_equal([rule.id, CLOSE, 3], events)
13
+ assert_equal([rule, CLOSE, 3], events)
14
14
 
15
15
  events = rule.exec(Input.new('defabc'))
16
- assert_equal([rule.id, CLOSE, 3], events)
16
+ assert_equal([rule, CLOSE, 3], events)
17
17
  end
18
18
 
19
19
  def test_exec_miss
@@ -23,7 +23,7 @@ class ButPredicateTest < Test::Unit::TestCase
23
23
  end
24
24
 
25
25
  def test_consumption
26
- rule = ButPredicate.new('abc')
26
+ rule = ButPredicate.new(Sequence.new(['a', 'b', 'c']))
27
27
 
28
28
  input = Input.new('def')
29
29
  events = rule.exec(input)
@@ -38,4 +38,10 @@ class ButPredicateTest < Test::Unit::TestCase
38
38
  rule = ButPredicate.new('a')
39
39
  assert_equal('~"a"', rule.to_s)
40
40
  end
41
+
42
+ def test_to_s_with_label
43
+ rule = ButPredicate.new('a')
44
+ rule.label = 'a_label'
45
+ assert_equal('a_label:~"a"', rule.to_s)
46
+ end
41
47
  end
@@ -1,11 +1,16 @@
1
1
  require File.expand_path('../helper', __FILE__)
2
2
 
3
- if defined?(Calc)
4
- Object.__send__(:remove_const, :Calc)
5
- end
6
-
7
3
  Citrus.load File.expand_path('../../examples/calc', __FILE__)
8
4
 
9
5
  class CalcFileTest < Test::Unit::TestCase
10
6
  include CalcTestMethods
7
+
8
+ # It's a bit hacky, but since this test runs before calc_test.rb we can
9
+ # avoid getting the "already defined constant" error by renaming the Calc
10
+ # constant here.
11
+ CalcFile = Object.__send__(:remove_const, :Calc)
12
+
13
+ def do_test(expr)
14
+ super(expr, CalcFile)
15
+ end
11
16
  end