citrus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +86 -0
- data/Rakefile +67 -0
- data/citrus.gemspec +29 -0
- data/examples/calc.citrus +103 -0
- data/examples/calc.rb +95 -0
- data/examples/calc_sugar.rb +94 -0
- data/lib/citrus.rb +904 -0
- data/lib/citrus/debug.rb +37 -0
- data/lib/citrus/peg.rb +375 -0
- data/lib/citrus/sugar.rb +25 -0
- data/test/alias_test.rb +66 -0
- data/test/and_predicate_test.rb +27 -0
- data/test/calc_peg_test.rb +6 -0
- data/test/calc_sugar_test.rb +6 -0
- data/test/calc_test.rb +6 -0
- data/test/choice_test.rb +62 -0
- data/test/expression_test.rb +29 -0
- data/test/fixed_width_test.rb +37 -0
- data/test/grammar_test.rb +129 -0
- data/test/helper.rb +143 -0
- data/test/label_test.rb +26 -0
- data/test/match_test.rb +76 -0
- data/test/not_predicate_test.rb +27 -0
- data/test/peg_test.rb +663 -0
- data/test/repeat_test.rb +93 -0
- data/test/rule_test.rb +49 -0
- data/test/sequence_test.rb +53 -0
- data/test/super_test.rb +66 -0
- metadata +133 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class AndPredicateTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_terminal?
|
6
|
+
rule = AndPredicate.new
|
7
|
+
assert_equal(false, rule.terminal?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_match
|
11
|
+
rule = AndPredicate.new('a')
|
12
|
+
|
13
|
+
match = rule.match(input('b'))
|
14
|
+
assert_equal(nil, match)
|
15
|
+
|
16
|
+
match = rule.match(input('a'))
|
17
|
+
assert(match)
|
18
|
+
assert_equal('', match.text)
|
19
|
+
assert_equal(0, match.length)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_to_s
|
23
|
+
rule = AndPredicate.new('a')
|
24
|
+
assert_equal('&"a"', rule.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/test/calc_test.rb
ADDED
data/test/choice_test.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class ChoiceTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_terminal?
|
6
|
+
rule = Choice.new
|
7
|
+
assert_equal(false, rule.terminal?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_match
|
11
|
+
rule = Choice.new(%w<a b>)
|
12
|
+
|
13
|
+
match = rule.match(input(''))
|
14
|
+
assert_equal(nil, match)
|
15
|
+
|
16
|
+
match = rule.match(input('a'))
|
17
|
+
assert(match)
|
18
|
+
assert_equal('a', match.text)
|
19
|
+
assert_equal(1, match.length)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_match_multi
|
23
|
+
rule = Choice.new(%w<a b>)
|
24
|
+
|
25
|
+
match = rule.match(input('ab'))
|
26
|
+
assert(match)
|
27
|
+
assert_equal('a', match.text)
|
28
|
+
assert_equal(1, match.length)
|
29
|
+
|
30
|
+
match = rule.match(input('ba'))
|
31
|
+
assert(match)
|
32
|
+
assert_equal('b', match.text)
|
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.text)
|
42
|
+
assert_equal(1, match.length)
|
43
|
+
|
44
|
+
match = rule.match(input('+1'))
|
45
|
+
assert(match)
|
46
|
+
assert_equal('+', match.text)
|
47
|
+
assert_equal(1, match.length)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_to_s
|
51
|
+
rule = Choice.new(%w<a b>)
|
52
|
+
assert_equal('"a" | "b"', rule.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_to_s_embed
|
56
|
+
rule1 = Choice.new(%w<a b>)
|
57
|
+
rule2 = Choice.new(%w<c d>)
|
58
|
+
rule = Choice.new([rule1, rule2])
|
59
|
+
assert_equal('("a" | "b") | ("c" | "d")', rule.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class ExpressionTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_terminal?
|
6
|
+
rule = Expression.new
|
7
|
+
assert(rule.terminal?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_match
|
11
|
+
rule = Expression.new(/\d+/)
|
12
|
+
match = rule.match(input('123 456'))
|
13
|
+
assert(match)
|
14
|
+
assert_equal('123', match.text)
|
15
|
+
assert_equal(3, match.length)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_match_failure
|
19
|
+
rule = Expression.new(/\d+/)
|
20
|
+
match = rule.match(input(' 456'))
|
21
|
+
assert_equal(nil, match)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_to_s
|
25
|
+
rule = Expression.new(/\d+/)
|
26
|
+
assert_equal('/\\d+/', rule.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class FixedWidthTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_terminal?
|
6
|
+
rule = FixedWidth.new
|
7
|
+
assert(rule.terminal?)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_match
|
11
|
+
rule = FixedWidth.new('abc')
|
12
|
+
match = rule.match(input('abc'))
|
13
|
+
assert(match)
|
14
|
+
assert_equal('abc', match.text)
|
15
|
+
assert_equal(3, match.length)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_match_short
|
19
|
+
rule = FixedWidth.new('abc')
|
20
|
+
match = rule.match(input('ab'))
|
21
|
+
assert_equal(nil, match)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_match_long
|
25
|
+
rule = FixedWidth.new('abc')
|
26
|
+
match = rule.match(input('abcd'))
|
27
|
+
assert(match)
|
28
|
+
assert_equal('abc', match.text)
|
29
|
+
assert_equal(3, match.length)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_to_s
|
33
|
+
rule = FixedWidth.new('abc')
|
34
|
+
assert_equal('"abc"', rule.to_s)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
class GrammarTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_new
|
6
|
+
Grammar.new do |g|
|
7
|
+
assert_kind_of(Module, g)
|
8
|
+
assert(g.include?(Grammar))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_name
|
13
|
+
assert_equal("Test::Unit::TestCase::TestGrammar", TestGrammar.name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_no_name
|
17
|
+
grammar = Grammar.new
|
18
|
+
assert_equal('', grammar.name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_rule_names
|
22
|
+
assert_equal([:alpha, :num, :alphanum], TestGrammar.rule_names)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_has_name
|
26
|
+
assert(TestGrammar.has_rule?('alpha'))
|
27
|
+
assert(TestGrammar.has_rule?(:alpha))
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_doesnt_have_name
|
31
|
+
assert_equal(false, TestGrammar.has_rule?(:value))
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_parse_fixed_width
|
35
|
+
grammar = Grammar.new {
|
36
|
+
rule(:abc) { 'abc' }
|
37
|
+
}
|
38
|
+
match = grammar.parse('abc')
|
39
|
+
assert(match)
|
40
|
+
assert_equal('abc', match.text)
|
41
|
+
assert_equal(3, match.length)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_parse_expression
|
45
|
+
grammar = Grammar.new {
|
46
|
+
rule(:alpha) { /[a-z]+/i }
|
47
|
+
}
|
48
|
+
match = grammar.parse('abc')
|
49
|
+
assert(match)
|
50
|
+
assert_equal('abc', match.text)
|
51
|
+
assert_equal(3, match.length)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_parse_sequence
|
55
|
+
grammar = Grammar.new {
|
56
|
+
rule(:num) { all(1, 2, 3) }
|
57
|
+
}
|
58
|
+
match = grammar.parse('123')
|
59
|
+
assert(match)
|
60
|
+
assert_equal('123', match.text)
|
61
|
+
assert_equal(3, match.length)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_parse_sequence_long
|
65
|
+
grammar = Grammar.new {
|
66
|
+
rule(:num) { all(1, 2, 3) }
|
67
|
+
}
|
68
|
+
assert_raise ParseError do
|
69
|
+
match = grammar.parse('1234')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_parse_sequence_short
|
74
|
+
grammar = Grammar.new {
|
75
|
+
rule(:num) { all(1, 2, 3) }
|
76
|
+
}
|
77
|
+
assert_raise ParseError do
|
78
|
+
match = grammar.parse('12')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_parse_choice
|
83
|
+
grammar = Grammar.new {
|
84
|
+
rule(:alphanum) { any(/[a-z]/i, 0..9) }
|
85
|
+
}
|
86
|
+
|
87
|
+
match = grammar.parse('a')
|
88
|
+
assert(match)
|
89
|
+
assert_equal('a', match.text)
|
90
|
+
assert_equal(1, match.length)
|
91
|
+
|
92
|
+
match = grammar.parse('1')
|
93
|
+
assert(match)
|
94
|
+
assert_equal('1', match.text)
|
95
|
+
assert_equal(1, match.length)
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_parse_choice_miss
|
99
|
+
grammar = Grammar.new {
|
100
|
+
rule(:alphanum) { any(/[a-z]/, 0..9) }
|
101
|
+
}
|
102
|
+
assert_raise ParseError do
|
103
|
+
match = grammar.parse('A')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_parse_recurs
|
108
|
+
grammar = Grammar.new {
|
109
|
+
rule(:paren) { any(['(', :paren, ')'], /[a-z]/) }
|
110
|
+
}
|
111
|
+
|
112
|
+
match = grammar.parse('a')
|
113
|
+
assert(match)
|
114
|
+
assert_equal('a', match.text)
|
115
|
+
assert_equal(1, match.length)
|
116
|
+
|
117
|
+
match = grammar.parse('((a))')
|
118
|
+
assert(match)
|
119
|
+
assert('((a))', match.text)
|
120
|
+
assert(5, match.length)
|
121
|
+
|
122
|
+
str = ('(' * 200) + 'a' + (')' * 200)
|
123
|
+
match = grammar.parse(str)
|
124
|
+
assert(match)
|
125
|
+
assert(str, match.text)
|
126
|
+
assert(str.length, match.length)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
lib = File.expand_path('../../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'test/unit'
|
5
|
+
require 'citrus/debug'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
include Citrus
|
9
|
+
|
10
|
+
def input(str='')
|
11
|
+
Input.new(str)
|
12
|
+
end
|
13
|
+
|
14
|
+
module TestGrammar
|
15
|
+
include Citrus::Grammar
|
16
|
+
|
17
|
+
rule :alpha do
|
18
|
+
/[a-zA-Z]/
|
19
|
+
end
|
20
|
+
|
21
|
+
rule :num do
|
22
|
+
ext(/[0-9]/) {
|
23
|
+
def value
|
24
|
+
text.to_i
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
rule :alphanum do
|
30
|
+
any(:alpha, :num)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class EqualRule
|
35
|
+
include Citrus::Rule
|
36
|
+
|
37
|
+
def initialize(value)
|
38
|
+
@value = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def match(input, offset=0)
|
42
|
+
create_match(@value.to_s.dup, offset) if @value.to_s == input.string
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module CalcTests
|
47
|
+
def test_int
|
48
|
+
match = Calc.parse('3')
|
49
|
+
assert(match)
|
50
|
+
assert_equal('3', match.text)
|
51
|
+
assert_equal(1, match.length)
|
52
|
+
assert_equal(3, match.value)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_float
|
56
|
+
match = Calc.parse('1.5')
|
57
|
+
assert(match)
|
58
|
+
assert_equal('1.5', match.text)
|
59
|
+
assert_equal(3, match.length)
|
60
|
+
assert_equal(1.5, match.value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_addition
|
64
|
+
match = Calc.parse('1+2')
|
65
|
+
assert(match)
|
66
|
+
assert_equal('1+2', match.text)
|
67
|
+
assert_equal(3, match.length)
|
68
|
+
assert_equal(3, match.value)
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_addition_multi
|
72
|
+
match = Calc.parse('1+2+3')
|
73
|
+
assert(match)
|
74
|
+
assert_equal('1+2+3', match.text)
|
75
|
+
assert_equal(5, match.length)
|
76
|
+
assert_equal(6, match.value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_addition_float
|
80
|
+
match = Calc.parse('1.5+3')
|
81
|
+
assert(match)
|
82
|
+
assert_equal('1.5+3', match.text)
|
83
|
+
assert_equal(5, match.length)
|
84
|
+
assert_equal(4.5, match.value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_subtraction
|
88
|
+
match = Calc.parse('3-2')
|
89
|
+
assert(match)
|
90
|
+
assert_equal(1, match.value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_subtraction_float
|
94
|
+
match = Calc.parse('4.5-3')
|
95
|
+
assert(match)
|
96
|
+
assert_equal('4.5-3', match.text)
|
97
|
+
assert_equal(5, match.length)
|
98
|
+
assert_equal(1.5, match.value)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_multiplication
|
102
|
+
match = Calc.parse('2*5')
|
103
|
+
assert(match)
|
104
|
+
assert_equal(10, match.value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_multiplication_float
|
108
|
+
match = Calc.parse('1.5*3')
|
109
|
+
assert(match)
|
110
|
+
assert_equal('1.5*3', match.text)
|
111
|
+
assert_equal(5, match.length)
|
112
|
+
assert_equal(4.5, match.value)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_division
|
116
|
+
match = Calc.parse('20/5')
|
117
|
+
assert(match)
|
118
|
+
assert_equal(4, match.value)
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_division_float
|
122
|
+
match = Calc.parse('4.5/3')
|
123
|
+
assert(match)
|
124
|
+
assert_equal('4.5/3', match.text)
|
125
|
+
assert_equal(5, match.length)
|
126
|
+
assert_equal(1.5, match.value)
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_complex
|
130
|
+
match = Calc.parse('7*4+3.5*(4.5/3)')
|
131
|
+
assert(match)
|
132
|
+
assert_equal('7*4+3.5*(4.5/3)', match.text)
|
133
|
+
assert_equal(33.25, match.value)
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_complex_spaced
|
137
|
+
match = Calc.parse('7 * 4 + 3.5 * (4.5 / 3)')
|
138
|
+
assert(match)
|
139
|
+
assert_equal(33.25, match.value)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|