citrus 2.2.2 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +160 -98
- data/doc/background.markdown +16 -17
- data/doc/example.markdown +86 -46
- data/doc/syntax.markdown +59 -36
- data/examples/calc.citrus +9 -3
- data/examples/calc.rb +9 -3
- data/examples/ip.rb +2 -0
- data/lib/citrus.rb +576 -473
- data/lib/citrus/file.rb +80 -71
- data/test/alias_test.rb +8 -2
- data/test/and_predicate_test.rb +13 -2
- data/test/but_predicate_test.rb +9 -3
- data/test/calc_file_test.rb +9 -4
- data/test/calc_test.rb +4 -4
- data/test/choice_test.rb +11 -5
- data/test/extension_test.rb +2 -12
- data/test/file_test.rb +215 -175
- data/test/grammar_test.rb +1 -1
- data/test/helper.rb +2 -2
- data/test/input_test.rb +44 -48
- data/test/label_test.rb +14 -17
- data/test/match_test.rb +21 -63
- data/test/not_predicate_test.rb +13 -2
- data/test/repeat_test.rb +20 -20
- data/test/sequence_test.rb +22 -8
- data/test/string_terminal_test.rb +10 -5
- data/test/super_test.rb +19 -16
- data/test/terminal_test.rb +7 -2
- metadata +21 -12
- data/benchmark/after.dat +0 -192
- data/benchmark/before.dat +0 -192
- data/test/_files/grammar3.citrus +0 -112
data/test/grammar_test.rb
CHANGED
@@ -63,7 +63,7 @@ class GrammarTest < Test::Unit::TestCase
|
|
63
63
|
grammar = Grammar.new {
|
64
64
|
rule(:num) { all(1, 2, 3) }
|
65
65
|
}
|
66
|
-
match = grammar.parse('1234')
|
66
|
+
match = grammar.parse('1234', :consume => false)
|
67
67
|
assert(match)
|
68
68
|
assert_equal('123', match)
|
69
69
|
assert_equal(3, match.length)
|
data/test/helper.rb
CHANGED
@@ -48,8 +48,8 @@ class Test::Unit::TestCase
|
|
48
48
|
module CalcTestMethods
|
49
49
|
# A helper method that tests the successful parsing and evaluation of the
|
50
50
|
# given mathematical expression.
|
51
|
-
def do_test(expr)
|
52
|
-
match =
|
51
|
+
def do_test(expr, grammar)
|
52
|
+
match = grammar.parse(expr)
|
53
53
|
assert(match)
|
54
54
|
assert_equal(expr, match)
|
55
55
|
assert_equal(expr.length, match.length)
|
data/test/input_test.rb
CHANGED
@@ -2,9 +2,8 @@ require File.expand_path('../helper', __FILE__)
|
|
2
2
|
|
3
3
|
class InputTest < Test::Unit::TestCase
|
4
4
|
def test_memoized?
|
5
|
-
|
6
|
-
|
7
|
-
assert(input.memoized?)
|
5
|
+
assert !Input.new('').memoized?
|
6
|
+
assert MemoizingInput.new('').memoized?
|
8
7
|
end
|
9
8
|
|
10
9
|
def test_offsets_new
|
@@ -25,31 +24,31 @@ class InputTest < Test::Unit::TestCase
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def test_events
|
28
|
-
a = Rule.
|
29
|
-
b = Rule.
|
30
|
-
c = Rule.
|
31
|
-
s = Rule.
|
27
|
+
a = Rule.for('a')
|
28
|
+
b = Rule.for('b')
|
29
|
+
c = Rule.for('c')
|
30
|
+
s = Rule.for([ a, b, c ])
|
32
31
|
r = Repeat.new(s, 0, Infinity)
|
33
32
|
|
34
33
|
input = Input.new("abcabcabc")
|
35
34
|
events = input.exec(r)
|
36
35
|
|
37
36
|
expected_events = [
|
38
|
-
r
|
39
|
-
s
|
40
|
-
a
|
41
|
-
b
|
42
|
-
c
|
37
|
+
r,
|
38
|
+
s,
|
39
|
+
a, CLOSE, 1,
|
40
|
+
b, CLOSE, 1,
|
41
|
+
c, CLOSE, 1,
|
43
42
|
CLOSE, 3,
|
44
|
-
s
|
45
|
-
a
|
46
|
-
b
|
47
|
-
c
|
43
|
+
s,
|
44
|
+
a, CLOSE, 1,
|
45
|
+
b, CLOSE, 1,
|
46
|
+
c, CLOSE, 1,
|
48
47
|
CLOSE, 3,
|
49
|
-
s
|
50
|
-
a
|
51
|
-
b
|
52
|
-
c
|
48
|
+
s,
|
49
|
+
a, CLOSE, 1,
|
50
|
+
b, CLOSE, 1,
|
51
|
+
c, CLOSE, 1,
|
53
52
|
CLOSE, 3,
|
54
53
|
CLOSE, 9
|
55
54
|
]
|
@@ -58,42 +57,42 @@ class InputTest < Test::Unit::TestCase
|
|
58
57
|
end
|
59
58
|
|
60
59
|
def test_events2
|
61
|
-
a = Rule.
|
62
|
-
b = Rule.
|
60
|
+
a = Rule.for('a')
|
61
|
+
b = Rule.for('b')
|
63
62
|
c = Choice.new([ a, b ])
|
64
63
|
r = Repeat.new(c, 0, Infinity)
|
65
|
-
s = Rule.
|
64
|
+
s = Rule.for([ a, r ])
|
66
65
|
|
67
66
|
input = Input.new('abbababba')
|
68
67
|
events = input.exec(s)
|
69
68
|
|
70
69
|
expected_events = [
|
71
|
-
s
|
72
|
-
a
|
73
|
-
r
|
74
|
-
c
|
75
|
-
b
|
70
|
+
s,
|
71
|
+
a, CLOSE, 1,
|
72
|
+
r,
|
73
|
+
c,
|
74
|
+
b, CLOSE, 1,
|
76
75
|
CLOSE, 1,
|
77
|
-
c
|
78
|
-
b
|
76
|
+
c,
|
77
|
+
b, CLOSE, 1,
|
79
78
|
CLOSE, 1,
|
80
|
-
c
|
81
|
-
a
|
79
|
+
c,
|
80
|
+
a, CLOSE, 1,
|
82
81
|
CLOSE, 1,
|
83
|
-
c
|
84
|
-
b
|
82
|
+
c,
|
83
|
+
b, CLOSE, 1,
|
85
84
|
CLOSE, 1,
|
86
|
-
c
|
87
|
-
a
|
85
|
+
c,
|
86
|
+
a, CLOSE, 1,
|
88
87
|
CLOSE, 1,
|
89
|
-
c
|
90
|
-
b
|
88
|
+
c,
|
89
|
+
b, CLOSE, 1,
|
91
90
|
CLOSE, 1,
|
92
|
-
c
|
93
|
-
b
|
91
|
+
c,
|
92
|
+
b, CLOSE, 1,
|
94
93
|
CLOSE, 1,
|
95
|
-
c
|
96
|
-
a
|
94
|
+
c,
|
95
|
+
a, CLOSE, 1,
|
97
96
|
CLOSE, 1,
|
98
97
|
CLOSE, 8,
|
99
98
|
CLOSE, 9
|
@@ -121,22 +120,19 @@ class InputTest < Test::Unit::TestCase
|
|
121
120
|
end
|
122
121
|
|
123
122
|
def test_cache_hits1
|
124
|
-
input =
|
125
|
-
input.memoize!
|
123
|
+
input = MemoizingInput.new('a')
|
126
124
|
input.exec(LetterA.rule(:top))
|
127
125
|
assert_equal(3, input.cache_hits)
|
128
126
|
end
|
129
127
|
|
130
128
|
def test_cache_hits2
|
131
|
-
input =
|
132
|
-
input.memoize!
|
129
|
+
input = MemoizingInput.new('aa')
|
133
130
|
input.exec(LetterA.rule(:top))
|
134
131
|
assert_equal(2, input.cache_hits)
|
135
132
|
end
|
136
133
|
|
137
134
|
def test_cache_hits3
|
138
|
-
input =
|
139
|
-
input.memoize!
|
135
|
+
input = MemoizingInput.new('aaa')
|
140
136
|
input.exec(LetterA.rule(:top))
|
141
137
|
assert_equal(0, input.cache_hits)
|
142
138
|
end
|
data/test/label_test.rb
CHANGED
@@ -1,26 +1,23 @@
|
|
1
1
|
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
class LabelTest < Test::Unit::TestCase
|
4
|
-
def
|
5
|
-
rule =
|
6
|
-
|
4
|
+
def test_to_s
|
5
|
+
rule = Rule.for('a')
|
6
|
+
rule.label = 'a_label'
|
7
|
+
assert_equal('a_label:"a"', rule.to_s)
|
7
8
|
end
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
label.name = 'label'
|
14
|
-
match = label.parse('abc')
|
15
|
-
assert(match)
|
16
|
-
assert_equal([:abc, :a_label, :label], match.names)
|
10
|
+
def test_to_s_sequence
|
11
|
+
rule = Sequence.new(%w< a b >)
|
12
|
+
rule.label = 's_label'
|
13
|
+
assert_equal('s_label:("a" "b")', rule.to_s)
|
17
14
|
end
|
18
15
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
rule =
|
24
|
-
assert_equal('
|
16
|
+
def test_to_s_embedded
|
17
|
+
a = Rule.for('a')
|
18
|
+
a.label = 'a_label'
|
19
|
+
rule = Sequence.new([ a, 'b' ])
|
20
|
+
rule.label = 's_label'
|
21
|
+
assert_equal('s_label:(a_label:"a" "b")', rule.to_s)
|
25
22
|
end
|
26
23
|
end
|
data/test/match_test.rb
CHANGED
@@ -20,71 +20,30 @@ class MatchTest < Test::Unit::TestCase
|
|
20
20
|
assert_equal(false, match2 == match1)
|
21
21
|
end
|
22
22
|
|
23
|
-
def test_names
|
24
|
-
a = Rule.new('a')
|
25
|
-
a.name = 'a'
|
26
|
-
b = Rule.new('b')
|
27
|
-
b.name = 'b'
|
28
|
-
c = Rule.new('c')
|
29
|
-
c.name = 'c'
|
30
|
-
s = Rule.new([ a, b, c ])
|
31
|
-
s.name = 's'
|
32
|
-
r = Repeat.new(s, 0, Infinity)
|
33
|
-
r.name = 'r'
|
34
|
-
|
35
|
-
events = [
|
36
|
-
r.id,
|
37
|
-
s.id,
|
38
|
-
a.id, CLOSE, 1,
|
39
|
-
b.id, CLOSE, 1,
|
40
|
-
c.id, CLOSE, 1,
|
41
|
-
CLOSE, 3,
|
42
|
-
s.id,
|
43
|
-
a.id, CLOSE, 1,
|
44
|
-
b.id, CLOSE, 1,
|
45
|
-
c.id, CLOSE, 1,
|
46
|
-
CLOSE, 3,
|
47
|
-
s.id,
|
48
|
-
a.id, CLOSE, 1,
|
49
|
-
b.id, CLOSE, 1,
|
50
|
-
c.id, CLOSE, 1,
|
51
|
-
CLOSE, 3,
|
52
|
-
CLOSE, 9
|
53
|
-
]
|
54
|
-
|
55
|
-
match = Match.new("abcabcabc", events)
|
56
|
-
assert(match.names)
|
57
|
-
assert_equal([:r], match.names)
|
58
|
-
|
59
|
-
match.matches.each do |m|
|
60
|
-
assert_equal([:s], m.names)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
23
|
def test_matches
|
65
|
-
a = Rule.
|
66
|
-
b = Rule.
|
67
|
-
c = Rule.
|
68
|
-
s = Rule.
|
24
|
+
a = Rule.for('a')
|
25
|
+
b = Rule.for('b')
|
26
|
+
c = Rule.for('c')
|
27
|
+
s = Rule.for([ a, b, c ])
|
69
28
|
s.name = 's'
|
70
29
|
r = Repeat.new(s, 0, Infinity)
|
71
30
|
|
72
31
|
events = [
|
73
|
-
r
|
74
|
-
s
|
75
|
-
a
|
76
|
-
b
|
77
|
-
c
|
32
|
+
r,
|
33
|
+
s,
|
34
|
+
a, CLOSE, 1,
|
35
|
+
b, CLOSE, 1,
|
36
|
+
c, CLOSE, 1,
|
78
37
|
CLOSE, 3,
|
79
|
-
s
|
80
|
-
a
|
81
|
-
b
|
82
|
-
c
|
38
|
+
s,
|
39
|
+
a, CLOSE, 1,
|
40
|
+
b, CLOSE, 1,
|
41
|
+
c, CLOSE, 1,
|
83
42
|
CLOSE, 3,
|
84
|
-
s
|
85
|
-
a
|
86
|
-
b
|
87
|
-
c
|
43
|
+
s,
|
44
|
+
a, CLOSE, 1,
|
45
|
+
b, CLOSE, 1,
|
46
|
+
c, CLOSE, 1,
|
88
47
|
CLOSE, 3,
|
89
48
|
CLOSE, 9
|
90
49
|
]
|
@@ -94,16 +53,15 @@ class MatchTest < Test::Unit::TestCase
|
|
94
53
|
assert_equal(3, match.matches.length)
|
95
54
|
|
96
55
|
sub_events = [
|
97
|
-
s
|
98
|
-
a
|
99
|
-
b
|
100
|
-
c
|
56
|
+
s,
|
57
|
+
a, CLOSE, 1,
|
58
|
+
b, CLOSE, 1,
|
59
|
+
c, CLOSE, 1,
|
101
60
|
CLOSE, 3
|
102
61
|
]
|
103
62
|
|
104
63
|
match.matches.each do |m|
|
105
64
|
assert_equal(sub_events, m.events)
|
106
|
-
assert_equal(:s, m.name)
|
107
65
|
assert_equal("abc", m)
|
108
66
|
assert(m.matches)
|
109
67
|
assert_equal(3, m.matches.length)
|
data/test/not_predicate_test.rb
CHANGED
@@ -9,7 +9,7 @@ class NotPredicateTest < Test::Unit::TestCase
|
|
9
9
|
def test_exec
|
10
10
|
rule = NotPredicate.new('abc')
|
11
11
|
events = rule.exec(Input.new('def'))
|
12
|
-
assert_equal([rule
|
12
|
+
assert_equal([rule, CLOSE, 0], events)
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_exec_miss
|
@@ -19,7 +19,12 @@ class NotPredicateTest < Test::Unit::TestCase
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_consumption
|
22
|
-
rule = NotPredicate.new('
|
22
|
+
rule = NotPredicate.new(Sequence.new(['a', 'b', 'c']))
|
23
|
+
|
24
|
+
input = Input.new('abc')
|
25
|
+
events = rule.exec(input)
|
26
|
+
assert_equal(0, input.pos)
|
27
|
+
|
23
28
|
input = Input.new('def')
|
24
29
|
events = rule.exec(input)
|
25
30
|
assert_equal(0, input.pos)
|
@@ -29,4 +34,10 @@ class NotPredicateTest < Test::Unit::TestCase
|
|
29
34
|
rule = NotPredicate.new('a')
|
30
35
|
assert_equal('!"a"', rule.to_s)
|
31
36
|
end
|
37
|
+
|
38
|
+
def test_to_s_with_label
|
39
|
+
rule = NotPredicate.new('a')
|
40
|
+
rule.label = 'a_label'
|
41
|
+
assert_equal('a_label:!"a"', rule.to_s)
|
42
|
+
end
|
32
43
|
end
|
data/test/repeat_test.rb
CHANGED
@@ -7,34 +7,34 @@ class RepeatTest < Test::Unit::TestCase
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def test_exec_zero_or_one
|
10
|
-
abc = Rule.
|
10
|
+
abc = Rule.for('abc')
|
11
11
|
rule = Repeat.new(abc, 0, 1)
|
12
12
|
|
13
13
|
events = rule.exec(Input.new(''))
|
14
|
-
assert_equal([rule
|
14
|
+
assert_equal([rule, CLOSE, 0], events)
|
15
15
|
|
16
16
|
events = rule.exec(Input.new('abc'))
|
17
|
-
assert_equal([rule
|
17
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
18
18
|
|
19
19
|
events = rule.exec(Input.new('abc' * 3))
|
20
|
-
assert_equal([rule
|
20
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_exec_zero_or_more
|
24
|
-
abc = Rule.
|
24
|
+
abc = Rule.for('abc')
|
25
25
|
rule = Repeat.new(abc, 0, Infinity)
|
26
26
|
|
27
27
|
events = rule.exec(Input.new(''))
|
28
|
-
assert_equal([rule
|
28
|
+
assert_equal([rule, CLOSE, 0], events)
|
29
29
|
|
30
30
|
events = rule.exec(Input.new('abc'))
|
31
|
-
assert_equal([rule
|
31
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
32
32
|
|
33
33
|
expected_events = [
|
34
|
-
rule
|
35
|
-
abc
|
36
|
-
abc
|
37
|
-
abc
|
34
|
+
rule,
|
35
|
+
abc, CLOSE, 3,
|
36
|
+
abc, CLOSE, 3,
|
37
|
+
abc, CLOSE, 3,
|
38
38
|
CLOSE, 9
|
39
39
|
]
|
40
40
|
|
@@ -43,20 +43,20 @@ class RepeatTest < Test::Unit::TestCase
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_exec_one_or_more
|
46
|
-
abc = Rule.
|
46
|
+
abc = Rule.for('abc')
|
47
47
|
rule = Repeat.new(abc, 1, Infinity)
|
48
48
|
|
49
49
|
events = rule.exec(Input.new(''))
|
50
50
|
assert_equal([], events)
|
51
51
|
|
52
52
|
events = rule.exec(Input.new('abc'))
|
53
|
-
assert_equal([rule
|
53
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
54
54
|
|
55
55
|
expected_events = [
|
56
|
-
rule
|
57
|
-
abc
|
58
|
-
abc
|
59
|
-
abc
|
56
|
+
rule,
|
57
|
+
abc, CLOSE, 3,
|
58
|
+
abc, CLOSE, 3,
|
59
|
+
abc, CLOSE, 3,
|
60
60
|
CLOSE, 9
|
61
61
|
]
|
62
62
|
|
@@ -65,17 +65,17 @@ class RepeatTest < Test::Unit::TestCase
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_exec_one
|
68
|
-
abc = Rule.
|
68
|
+
abc = Rule.for('abc')
|
69
69
|
rule = Repeat.new(abc, 1, 1)
|
70
70
|
|
71
71
|
events = rule.exec(Input.new(''))
|
72
72
|
assert_equal([], events)
|
73
73
|
|
74
74
|
events = rule.exec(Input.new('abc'))
|
75
|
-
assert_equal([rule
|
75
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
76
76
|
|
77
77
|
events = rule.exec(Input.new('abc' * 3))
|
78
|
-
assert_equal([rule
|
78
|
+
assert_equal([rule, abc, CLOSE, 3, CLOSE, 3], events)
|
79
79
|
end
|
80
80
|
|
81
81
|
def test_operator
|