citrus 2.1.2 → 2.2.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
@@ -3,7 +3,7 @@ require 'citrus'
3
3
  module Citrus
4
4
  # Some helper methods for rules that alias +module_name+ and don't want to
5
5
  # use +Kernel#eval+ to retrieve Module objects.
6
- module ModuleHelpers #:nodoc:
6
+ module ModuleNameHelpers #:nodoc:
7
7
  def module_segments
8
8
  @module_segments ||= module_name.value.split('::')
9
9
  end
@@ -35,7 +35,7 @@ module Citrus
35
35
 
36
36
  rule :grammar do
37
37
  all(:grammar_keyword, :module_name, :grammar_body, :end_keyword) {
38
- include ModuleHelpers
38
+ include ModuleNameHelpers
39
39
 
40
40
  def value
41
41
  module_namespace.const_set(module_basename, grammar_body.value)
@@ -78,7 +78,11 @@ module Citrus
78
78
  rule :choice do
79
79
  all(:sequence, zero_or_more([ :bar, :sequence ])) {
80
80
  def rules
81
- @rules ||= [ sequence.value ] + matches[1].matches.map {|m| m.matches[1].value }
81
+ @rules ||= begin
82
+ [ sequence.value ] + matches[1].matches.map do |m|
83
+ m.matches[1].value
84
+ end
85
+ end
82
86
  end
83
87
 
84
88
  def value
@@ -127,7 +131,7 @@ module Citrus
127
131
  end
128
132
 
129
133
  rule :primary do
130
- any(:grouping, :proxy, :terminal)
134
+ any(:grouping, :proxy, :string_terminal, :terminal)
131
135
  end
132
136
 
133
137
  rule :grouping do
@@ -142,7 +146,7 @@ module Citrus
142
146
 
143
147
  rule :include do
144
148
  all(:include_keyword, :module_name) {
145
- include ModuleHelpers
149
+ include ModuleNameHelpers
146
150
 
147
151
  def value
148
152
  module_namespace.const_get(module_basename)
@@ -176,14 +180,44 @@ module Citrus
176
180
  }
177
181
  end
178
182
 
179
- rule :terminal do
180
- any(:quoted_string, :character_class, :dot, :regular_expression) {
181
- Rule.new(super())
183
+ rule :string_terminal do
184
+ any(:quoted_string, :case_insensitive_string) {
185
+ StringTerminal.new(super(), flags)
182
186
  }
183
187
  end
184
188
 
185
189
  rule :quoted_string do
186
190
  all(/(["'])(?:\\?.)*?\1/, :space) {
191
+ def value
192
+ eval(first)
193
+ end
194
+
195
+ def flags
196
+ 0
197
+ end
198
+ }
199
+ end
200
+
201
+ rule :case_insensitive_string do
202
+ all(/`(?:\\?.)*?`/, :space) {
203
+ def value
204
+ eval(first.gsub(/^`|`$/, '"'))
205
+ end
206
+
207
+ def flags
208
+ Regexp::IGNORECASE
209
+ end
210
+ }
211
+ end
212
+
213
+ rule :terminal do
214
+ any(:regular_expression, :character_class, :dot) {
215
+ Terminal.new(super())
216
+ }
217
+ end
218
+
219
+ rule :regular_expression do
220
+ all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
187
221
  eval(first)
188
222
  }
189
223
  end
@@ -200,9 +234,23 @@ module Citrus
200
234
  }
201
235
  end
202
236
 
203
- rule :regular_expression do
204
- all(/\/(?:\\?.)*?\/[imxouesn]*/, :space) {
205
- eval(first)
237
+ rule :extension do
238
+ any(:tag, :block)
239
+ end
240
+
241
+ rule :tag do
242
+ all(:lt, :module_name, :gt) {
243
+ include ModuleNameHelpers
244
+
245
+ def value
246
+ module_namespace.const_get(module_basename)
247
+ end
248
+ }
249
+ end
250
+
251
+ rule :block do
252
+ all(:lcurly, zero_or_more(any(:block, /[^{}]+/)), :rcurly) {
253
+ eval('Proc.new ' + to_s, TOPLEVEL_BINDING)
206
254
  }
207
255
  end
208
256
 
@@ -234,26 +282,6 @@ module Citrus
234
282
  }
235
283
  end
236
284
 
237
- rule :extension do
238
- any(:tag, :block)
239
- end
240
-
241
- rule :tag do
242
- all(:lt, :module_name, :gt) {
243
- include ModuleHelpers
244
-
245
- def value
246
- module_namespace.const_get(module_basename)
247
- end
248
- }
249
- end
250
-
251
- rule :block do
252
- all(:lcurly, zero_or_more(any(:block, /[^{}]+/)), :rcurly) {
253
- eval('Proc.new ' + to_s)
254
- }
255
- end
256
-
257
285
  rule :repeat do
258
286
  any(:question, :plus, :star) { |rule|
259
287
  Repeat.new(rule, min, max)
@@ -276,8 +304,13 @@ module Citrus
276
304
 
277
305
  rule :star do
278
306
  all(/[0-9]*/, '*', /[0-9]*/, :space) {
279
- def min; matches[0] == '' ? 0 : matches[0].to_i end
280
- def max; matches[2] == '' ? Infinity : matches[2].to_i end
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
281
314
  }
282
315
  end
283
316
 
@@ -4,7 +4,7 @@ grammar SuperOne
4
4
  end
5
5
  end
6
6
 
7
- grammar SuperTwo
7
+ grammar SuperOneSub
8
8
  include SuperOne
9
9
 
10
10
  rule num
@@ -0,0 +1,13 @@
1
+ grammar SuperTwo
2
+ rule root
3
+ (one | two) { super() * 1000 }
4
+ end
5
+
6
+ rule one
7
+ "1" { 1 }
8
+ end
9
+
10
+ rule two
11
+ "2" { 2 }
12
+ end
13
+ end
data/test/alias_test.rb CHANGED
@@ -1,64 +1,48 @@
1
1
  require File.expand_path('../helper', __FILE__)
2
- Citrus.load(File.dirname(__FILE__) + '/_files/alias')
3
2
 
4
3
  class AliasTest < Test::Unit::TestCase
5
-
6
4
  def test_terminal?
7
5
  rule = Alias.new
8
6
  assert_equal(false, rule.terminal?)
9
7
  end
10
8
 
11
- def test_match
9
+ def test_exec
12
10
  grammar = Grammar.new {
13
11
  rule :a, :b
14
- rule :b, 'b'
12
+ rule :b, 'abc'
15
13
  }
16
-
17
- match = grammar.parse('b')
18
- assert(match)
19
- assert_equal('b', match)
20
- assert_equal(1, match.length)
14
+ rule = grammar.rule(:a)
15
+ rule_b = grammar.rule(:b)
16
+ events = rule.exec(Input.new('abc'))
17
+ assert_equal([rule_b.id, CLOSE, 3], events)
21
18
  end
22
19
 
23
- def test_match_renamed
20
+ def test_exec_miss
24
21
  grammar = Grammar.new {
25
- rule :a, ext(:b) {
26
- 'a' + to_s
27
- }
28
- rule :b, 'b'
22
+ rule :a, :b
23
+ rule :b, 'abc'
29
24
  }
30
-
31
- match = grammar.parse('b')
32
- assert(match)
33
- assert('ab', match.value)
34
-
35
- assert_raise NoMatchError do
36
- match.b
37
- end
25
+ rule = grammar.rule(:a)
26
+ events = rule.exec(Input.new('def'))
27
+ assert_equal([], events)
38
28
  end
39
29
 
40
- def test_peg
41
- match = AliasOne.parse('a')
42
- assert(match)
43
- end
44
-
45
- def test_included
30
+ def test_exec_included
46
31
  grammar1 = Grammar.new {
47
- rule :a, 'a'
32
+ rule :a, 'abc'
48
33
  }
49
-
50
34
  grammar2 = Grammar.new {
51
35
  include grammar1
52
36
  rule :b, :a
53
37
  }
54
-
55
- match = grammar2.parse('a')
56
- assert(match)
38
+ rule = grammar2.rule(:b)
39
+ rule_a = grammar1.rule(:a)
40
+ events = rule.exec(Input.new('abc'))
41
+ assert_equal([rule_a.id, CLOSE, 3], events)
57
42
  end
58
43
 
59
44
  def test_to_s
60
45
  rule = Alias.new(:alpha)
61
46
  assert_equal('alpha', rule.to_s)
62
47
  end
63
-
64
48
  end
@@ -1,27 +1,32 @@
1
1
  require File.expand_path('../helper', __FILE__)
2
2
 
3
3
  class AndPredicateTest < Test::Unit::TestCase
4
-
5
4
  def test_terminal?
6
5
  rule = AndPredicate.new
7
6
  assert_equal(false, rule.terminal?)
8
7
  end
9
8
 
10
- def test_match
11
- rule = AndPredicate.new('a')
9
+ def test_exec
10
+ rule = AndPredicate.new('abc')
11
+ events = rule.exec(Input.new('abc'))
12
+ assert_equal([rule.id, CLOSE, 0], events)
13
+ end
12
14
 
13
- match = rule.match(input('b'))
14
- assert_equal(nil, match)
15
+ def test_exec_miss
16
+ rule = AndPredicate.new('def')
17
+ events = rule.exec(Input.new('abc'))
18
+ assert_equal([], events)
19
+ end
15
20
 
16
- match = rule.match(input('a'))
17
- assert(match)
18
- assert_equal('', match)
19
- assert_equal(0, match.length)
21
+ def test_consumption
22
+ rule = AndPredicate.new('abc')
23
+ input = Input.new('abc')
24
+ events = rule.exec(input)
25
+ assert_equal(0, input.pos)
20
26
  end
21
27
 
22
28
  def test_to_s
23
29
  rule = AndPredicate.new('a')
24
30
  assert_equal('&"a"', rule.to_s)
25
31
  end
26
-
27
32
  end
@@ -1,36 +1,41 @@
1
1
  require File.expand_path('../helper', __FILE__)
2
2
 
3
3
  class ButPredicateTest < Test::Unit::TestCase
4
-
5
4
  def test_terminal?
6
5
  rule = ButPredicate.new
7
6
  assert_equal(false, rule.terminal?)
8
7
  end
9
8
 
10
- def test_match
11
- rule = ButPredicate.new('a')
9
+ def test_exec
10
+ rule = ButPredicate.new('abc')
12
11
 
13
- match = rule.match(input('b'))
14
- assert(match)
15
- assert_equal('b', match)
16
- assert_equal(1, match.length)
12
+ events = rule.exec(Input.new('def'))
13
+ assert_equal([rule.id, CLOSE, 3], events)
17
14
 
18
- match = rule.match(input('bbba'))
19
- assert(match)
20
- assert_equal('bbb', match)
21
- assert_equal(3, match.length)
15
+ events = rule.exec(Input.new('defabc'))
16
+ assert_equal([rule.id, CLOSE, 3], events)
17
+ end
22
18
 
23
- match = rule.match(input('a'))
24
- assert_equal(nil, match)
19
+ def test_exec_miss
20
+ rule = ButPredicate.new('abc')
21
+ events = rule.exec(Input.new('abc'))
22
+ assert_equal([], events)
23
+ end
25
24
 
26
- # ButPredicate must match at least one character.
27
- match = rule.match(input(''))
28
- assert_equal(nil, match)
25
+ def test_consumption
26
+ rule = ButPredicate.new('abc')
27
+
28
+ input = Input.new('def')
29
+ events = rule.exec(input)
30
+ assert_equal(3, input.pos)
31
+
32
+ input = Input.new('defabc')
33
+ events = rule.exec(input)
34
+ assert_equal(3, input.pos)
29
35
  end
30
36
 
31
37
  def test_to_s
32
38
  rule = ButPredicate.new('a')
33
39
  assert_equal('~"a"', rule.to_s)
34
40
  end
35
-
36
41
  end
@@ -4,7 +4,7 @@ if defined?(Calc)
4
4
  Object.__send__(:remove_const, :Calc)
5
5
  end
6
6
 
7
- Citrus.load(File.dirname(__FILE__) + '/../examples/calc')
7
+ Citrus.load File.expand_path('../../examples/calc', __FILE__)
8
8
 
9
9
  class CalcFileTest < Test::Unit::TestCase
10
10
  include CalcTestMethods
data/test/choice_test.rb CHANGED
@@ -1,50 +1,26 @@
1
1
  require File.expand_path('../helper', __FILE__)
2
2
 
3
3
  class ChoiceTest < Test::Unit::TestCase
4
-
5
4
  def test_terminal?
6
5
  rule = Choice.new
7
6
  assert_equal(false, rule.terminal?)
8
7
  end
9
8
 
10
- def test_match
11
- rule = Choice.new(%w<a b>)
12
-
13
- match = rule.match(input(''))
14
- assert_equal(nil, match)
9
+ def test_exec
10
+ a = Rule.new('a')
11
+ b = Rule.new('b')
12
+ rule = Choice.new([ a, b ])
15
13
 
16
- match = rule.match(input('a'))
17
- assert(match)
18
- assert_equal('a', match)
19
- assert_equal(1, match.length)
20
- end
21
-
22
- def test_match_multi
23
- rule = Choice.new(%w<a b>)
14
+ events = rule.exec(Input.new(''))
15
+ assert_equal([], events)
24
16
 
25
- match = rule.match(input('ab'))
26
- assert(match)
27
- assert_equal('a', match)
28
- assert_equal(1, match.length)
17
+ events = rule.exec(Input.new('a'))
18
+ assert(events)
19
+ assert_equal([rule.id, a.id, CLOSE, 1, CLOSE, 1], events)
29
20
 
30
- match = rule.match(input('ba'))
31
- assert(match)
32
- assert_equal('b', match)
33
- assert_equal(1, match.length)
34
- end
35
-
36
- def test_match_embed
37
- rule = Choice.new([ /\d+/, Choice.new(%w<+ ->) ])
38
-
39
- match = rule.match(input('1+'))
40
- assert(match)
41
- assert_equal('1', match)
42
- assert_equal(1, match.length)
43
-
44
- match = rule.match(input('+1'))
45
- assert(match)
46
- assert_equal('+', match)
47
- assert_equal(1, match.length)
21
+ events = rule.exec(Input.new('b'))
22
+ assert(events)
23
+ assert_equal([rule.id, b.id, CLOSE, 1, CLOSE, 1], events)
48
24
  end
49
25
 
50
26
  def test_to_s
@@ -58,5 +34,4 @@ class ChoiceTest < Test::Unit::TestCase
58
34
  rule = Choice.new([rule1, rule2])
59
35
  assert_equal('("a" | "b") | ("c" | "d")', rule.to_s)
60
36
  end
61
-
62
37
  end