citrus 1.0.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/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
|