rubypeg 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,101 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class TestNegativeLookahead < RubyPeg
5
+ def root
6
+ node :root do
7
+ one_or_more { negative || word || space }
8
+ end
9
+ end
10
+
11
+ def negative
12
+ node :negative do
13
+ terminal('!') && not_followed_by { terminal('!') }
14
+ end
15
+ end
16
+
17
+ def word
18
+ node :word do
19
+ terminal(/\S+/)
20
+ end
21
+ end
22
+
23
+ def space
24
+ ignore { terminal(/\s+/) }
25
+ end
26
+
27
+ end
28
+
29
+ describe TestNegativeLookahead do
30
+
31
+ it "matches normally" do
32
+ TestNegativeLookahead.parse("one two three").to_ast.should == [:root,[:word,"one"],[:word,"two"],[:word,"three"]]
33
+ end
34
+
35
+ it "inverts normally" do
36
+ TestNegativeLookahead.parse("one !two three").to_ast.should == [:root,[:word,"one"],[:negative,"!"],[:word,"two"],[:word,"three"]]
37
+ end
38
+
39
+ it "looks ahead so as to interpret double negatives" do
40
+ TestNegativeLookahead.parse("one !!two three").to_ast.should == [:root,[:word,"one"],[:word,"!!two"],[:word,"three"]]
41
+ end
42
+ end
43
+
44
+ class TestPositiveLookahead < RubyPeg
45
+ def root
46
+ node :root do
47
+ one_or_more { noun || verb || adjective || adverb || word || space }
48
+ end
49
+ end
50
+
51
+ def adjective
52
+ node :adjective do
53
+ terminal(/\S+/) && followed_by { space && noun }
54
+ end
55
+ end
56
+
57
+ def adverb
58
+ node :adverb do
59
+ terminal(/\S+/) && followed_by { space && verb }
60
+ end
61
+ end
62
+
63
+ def word
64
+ node :word do
65
+ terminal(/\S+/)
66
+ end
67
+ end
68
+
69
+ def noun
70
+ node :noun do
71
+ terminal('dog') || terminal('fox')
72
+ end
73
+ end
74
+
75
+ def verb
76
+ node :verb do
77
+ terminal('jumped') || terminal('ran')
78
+ end
79
+ end
80
+
81
+ def space
82
+ ignore { terminal(/\s+/) }
83
+ end
84
+
85
+ end
86
+
87
+
88
+ describe TestPositiveLookahead do
89
+
90
+ it "matches normally" do
91
+ TestPositiveLookahead.parse("black blinking").to_ast.should == [:root,[:word,"black"],[:word,"blinking"]]
92
+ end
93
+
94
+ it "matches when looking ahead" do
95
+ TestPositiveLookahead.parse("brown dog").to_ast.should == [:root,[:adjective,"brown"],[:noun,"dog"]]
96
+ end
97
+
98
+ it "matches in slighlty more complicated squences" do
99
+ TestPositiveLookahead.parse("brown dog jumped as it lazily ran").to_ast.should == [:root,[:adjective,"brown"],[:noun,"dog"],[:verb,"jumped"],[:word,'as'],[:word,'it'],[:adverb,'lazily'],[:verb,'ran']]
100
+ end
101
+ end
@@ -0,0 +1,229 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class NonTerminalNodeTest < RubyPeg
5
+ def root
6
+ node :one do
7
+ terminal("one")
8
+ end
9
+ end
10
+ end
11
+
12
+ class ChildlessNonTerminalNodeTest < RubyPeg
13
+ def root
14
+ node :one do
15
+ ignore { terminal("one") }
16
+ end
17
+ end
18
+ end
19
+
20
+ class MultipleChildNonTerminalNodeTest < RubyPeg
21
+ def root
22
+ node :one do
23
+ terminal("one") && terminal("two")
24
+ end
25
+ end
26
+ end
27
+
28
+ # class CustomNode
29
+ # def initialize(children)
30
+ # # ignored
31
+ # end
32
+ # end
33
+ #
34
+ # class CustomNodeClassNonTerminalNodeTest < RubyPeg
35
+ # def root
36
+ # node CustomNode do
37
+ # terminal("one")
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # module CustomNodeA; end
43
+ # module CustomNodeB; end
44
+ #
45
+ # class CustomNodeModuleNonTerminalNodeTest < RubyPeg
46
+ # def root
47
+ # node CustomNodeA, CustomNodeB do
48
+ # terminal("one")
49
+ # end
50
+ # end
51
+ # end
52
+
53
+ class CreateNonTerminalNodeTest < NonTerminalNodeTest
54
+
55
+ def create_non_terminal_node(type,children)
56
+ [type,*children.map(&:to_s)]
57
+ end
58
+ end
59
+
60
+ describe NonTerminalNodeTest do
61
+
62
+ def parse(text)
63
+ NonTerminalNodeTest.parse(text)
64
+ end
65
+
66
+ it "non terminals are only created if the terminals that they contain match" do
67
+ parse("two").should == nil
68
+ end
69
+
70
+ it "if a symbol is passed to the node method then non terminals are, by default, instances of Array that have been extended with a NonTerminalNode moduoe" do
71
+ parse("one").class.should == Array
72
+ parse("one").should be_kind_of(NonTerminalNode)
73
+ end
74
+
75
+ it "NonTerminalNode instances have a type attribute that returns the symbol used as an argument to the node call" do
76
+ parse("one").type.should == :one
77
+ end
78
+
79
+ it "NonTerminalNode instances are an array of child nodes" do
80
+ parse("one").should be_kind_of(Array)
81
+ parse("one").first.should be_kind_of(TerminalNode)
82
+ parse("one").first.to_s.should == "one"
83
+ end
84
+
85
+ it "if there are no children, it is an empty array" do
86
+ ChildlessNonTerminalNodeTest.parse("one").should == []
87
+ end
88
+
89
+ it "NonTerminalNode instances respond to to_ast by returning [:type,*children]" do
90
+ parse("one").to_ast.should be_kind_of(Array)
91
+ parse("one").to_ast.should == [:one,"one"]
92
+ MultipleChildNonTerminalNodeTest.parse("onetwo").to_ast.should == [:one,"one","two"]
93
+ end
94
+
95
+ it "NonTerminalNode instances respond to to_ast by returning [:type] if there are no children" do
96
+ ChildlessNonTerminalNodeTest.parse("one").to_ast.should be_kind_of(Array)
97
+ ChildlessNonTerminalNodeTest.parse("one").to_ast.should == [:one]
98
+ end
99
+
100
+ it "NonTerminalNode instances respond to visit(builder) by trying to call a method with the same name on the builder and with its children as arguments" do
101
+ builder = mock(:TestBuilder)
102
+ builder.should_receive(:one).with {|a| a.kind_of?(TerminalNode) && a.to_s == "one"}.and_return(1)
103
+ parse("one").visit(builder).should == 1
104
+ end
105
+
106
+ it "If the builder doesn't have a method with its name, then it calls visit(builder) on its children, returning a string if there is only one child" do
107
+ builder = mock(:TestBuilder)
108
+ parse("one").visit(builder).should == "one"
109
+ end
110
+
111
+ it "If the builder doesn't have a method with its name, then it calls visit(builder) on its children, returning a an array of strings if there is more than one child" do
112
+ builder = mock(:TestBuilder)
113
+ MultipleChildNonTerminalNodeTest.parse("onetwo").visit(builder).should == ["one","two"]
114
+ end
115
+
116
+ it "the class of the non terminal can be altered by overriding the create_non_terminal_node(type,children) method" do
117
+ result = CreateNonTerminalNodeTest.parse("one")
118
+ result.should be_kind_of(Array)
119
+ result.first.should == :one
120
+ result.last.should == "one"
121
+ end
122
+
123
+ # it "if a class is passed to the node method then a class of that type is created as the non-terminal. Its initializer must take an array of children as its argument" do
124
+ # CustomNodeClassNonTerminalNodeTest.parse("one").class.should == CustomNode
125
+ # end
126
+ #
127
+ # it "if one or more modules are passed to the node method then they are used to extend the non-terminal array" do
128
+ # CustomNodeModuleNonTerminalNodeTest.parse("one").should be_kind_of(Array)
129
+ # CustomNodeModuleNonTerminalNodeTest.parse("one").should be_kind_of(CustomNodeA)
130
+ # CustomNodeModuleNonTerminalNodeTest.parse("one").should be_kind_of(CustomNodeB)
131
+ # end
132
+
133
+ end
134
+
135
+ class BasketPeg < RubyPeg
136
+ def root
137
+ node :basket do
138
+ one_or_more { items }
139
+ end
140
+ end
141
+
142
+ def items
143
+ node :item do
144
+ number && optional_space && fruit && optional_space
145
+ end
146
+ end
147
+
148
+ def number
149
+ terminal(/\d+/)
150
+ end
151
+
152
+ def fruit
153
+ node :fruit do
154
+ (terminal("apple") || terminal("pear")) && ignore{ optional{ terminal("s") } }
155
+ end
156
+ end
157
+
158
+ def optional_space
159
+ ignore{ optional{ terminal(" ") }}
160
+ end
161
+ end
162
+
163
+ describe BasketPeg do
164
+
165
+ it "Illustrates NonTerminalNode" do
166
+ BasketPeg.parse("1 apple 2 apples 3 pears").should be_kind_of(NonTerminalNode)
167
+ end
168
+
169
+ it "Illustrates NonTerminalNode#type" do
170
+ BasketPeg.parse("1 apple 2 apples 3 pears").type.should == :basket
171
+ end
172
+
173
+ it "Illustrates NonTerminalNode#to_ast" do
174
+ BasketPeg.parse("1 apple 2 apples 3 pears").to_ast.should == [:basket, [:item, "1", [:fruit, "apple"]], [:item, "2", [:fruit, "apple"]], [:item, "3", [:fruit, "pear"]]]
175
+ end
176
+
177
+ it "Illustrates NonTerminalNode#to_s" do
178
+ BasketPeg.parse("1 apple 2 apples 3 pears").to_s.should == "1apple2apple3pear"
179
+ end
180
+
181
+ it "Illustrates NonTerminalNode#inspect" do
182
+ BasketPeg.parse("1 apple 2 apples 3 pears").inspect.should == '[:basket, [:item, "1", [:fruit, "apple"]], [:item, "2", [:fruit, "apple"]], [:item, "3", [:fruit, "pear"]]]'
183
+ end
184
+
185
+ it "Illustrates NonTerminalNode#build" do
186
+ BasketPeg.parse("1 apple 2 apples 3 pears").visit.should == [["1", "apple"], ["2", "apple"], ["3", "pear"]]
187
+ class BasketPegBuilderExample
188
+ attr_accessor :total
189
+
190
+ def initialize
191
+ @total = 0
192
+ end
193
+
194
+ def item(number,kind)
195
+ @total = @total + (number.to_f * kind.visit(self).to_f)
196
+ end
197
+
198
+ def fruit(kind_of_fruit)
199
+ case kind_of_fruit
200
+ when "apple"; 3.0
201
+ when "pear"; 1.0
202
+ else 10.0
203
+ end
204
+ end
205
+ end
206
+ counter = BasketPegBuilderExample.new
207
+ BasketPeg.parse("1 apple 2 apples 3 pears").visit(counter)
208
+ counter.total.should == 12.0
209
+ end
210
+
211
+ it "Illustrates NonTerminalNode#children" do
212
+ basket = BasketPeg.parse("1 apple 2 apples 3 pears")
213
+ basket.class.should == Array
214
+ basket.size.should == 3
215
+ basket.first.should be_kind_of(NonTerminalNode)
216
+ basket.first.type.should == :item
217
+ basket.first.class.should == Array
218
+ basket.first.size.should == 2
219
+ basket.first.first.should be_kind_of(TerminalNode)
220
+ basket.first.first.should == "1"
221
+ basket.first.last.should be_kind_of(NonTerminalNode)
222
+ basket.first.last.type == :fruit
223
+ basket.first.last.class.should == Array
224
+ basket.first.last.size.should == 1
225
+ basket.first.last.first.should be_kind_of(TerminalNode)
226
+ basket.first.last.first.should == "apple"
227
+ end
228
+
229
+ end
@@ -0,0 +1,63 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class TestLeg1 < RubyPeg
5
+ def root
6
+ node :grammar do
7
+ optional_indent && grammar_keyword && space && grammar_name && newline && ignore {one_or_more { terminal('end') || terminal('END')}}
8
+ end
9
+ end
10
+
11
+ def optional_indent
12
+ ignore { terminal(/\s*/) }
13
+ end
14
+
15
+ def grammar_keyword
16
+ ignore { terminal('grammar') }
17
+ end
18
+
19
+ def space
20
+ ignore { terminal(/ +/) }
21
+ end
22
+
23
+ def newline
24
+ ignore { terminal(/\n+\s*/m) }
25
+ end
26
+
27
+ def grammar_name
28
+ terminal(/[a-zA-Z]+/)
29
+ end
30
+ end
31
+
32
+
33
+ describe TestLeg1 do
34
+
35
+ it "works with simple sequence" do
36
+ grammar = <<-EOF
37
+ grammar TestGrammar
38
+ end
39
+ EOF
40
+ TestLeg1.parse(grammar).to_ast.should == [:grammar, "TestGrammar"]
41
+ end
42
+
43
+ it "works with simple alternatives" do
44
+ grammar = <<-EOF
45
+ grammar TestGrammar
46
+ END
47
+ EOF
48
+ TestLeg1.parse(grammar).to_ast.should == [:grammar, "TestGrammar"]
49
+ end
50
+
51
+ it "works with one or more" do
52
+ grammar = <<-EOF
53
+ grammar TestGrammar
54
+ endEND
55
+ EOF
56
+ TestLeg1.parse(grammar).to_ast.should == [:grammar, "TestGrammar"]
57
+ grammar = <<-EOF
58
+ grammar TestGrammar
59
+
60
+ EOF
61
+ TestLeg1.parse(grammar).should == nil
62
+ end
63
+ end
@@ -0,0 +1,74 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class OneOrMore < RubyPeg
5
+ def root
6
+ node :root do
7
+ one_or_more { terminal('x') }
8
+ end
9
+ end
10
+ end
11
+
12
+ describe OneOrMore do
13
+
14
+ it "matches one" do
15
+ OneOrMore.parse("x.").to_ast.should == [:root,'x']
16
+ end
17
+
18
+ it "matches more than one" do
19
+ OneOrMore.parse("xx.").to_ast.should == [:root,'x','x']
20
+ end
21
+
22
+ it "doesn't match none" do
23
+ OneOrMore.parse(".").should == nil
24
+ end
25
+
26
+ end
27
+
28
+ class ZeroOrMore < RubyPeg
29
+ def root
30
+ node :root do
31
+ any_number_of { terminal('x') }
32
+ end
33
+ end
34
+ end
35
+
36
+ describe ZeroOrMore do
37
+
38
+ it "matches one" do
39
+ ZeroOrMore.parse("x.").to_ast.should == [:root,'x']
40
+ end
41
+
42
+ it "matches more than one" do
43
+ ZeroOrMore.parse("xx.").to_ast.should == [:root,'x','x']
44
+ end
45
+
46
+ it "matches none" do
47
+ ZeroOrMore.parse(".").to_ast.should == [:root]
48
+ end
49
+
50
+ end
51
+
52
+ class ZeroOrOne < RubyPeg
53
+ def root
54
+ node :root do
55
+ optional { terminal('x') }
56
+ end
57
+ end
58
+ end
59
+
60
+ describe ZeroOrOne do
61
+
62
+ it "matches one" do
63
+ ZeroOrOne.parse("x.").to_ast.should == [:root,'x']
64
+ end
65
+
66
+ it "doesn't match more than one" do
67
+ ZeroOrOne.parse("xx.").to_ast.should == [:root,'x']
68
+ end
69
+
70
+ it "matches none" do
71
+ ZeroOrOne.parse(".").to_ast.should == [:root]
72
+ end
73
+
74
+ end
@@ -0,0 +1,50 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class SimpleSequence < RubyPeg
5
+ def root
6
+ node :root do
7
+ terminal('x') && terminal('y') && terminal('z')
8
+ end
9
+ end
10
+ end
11
+
12
+ describe SimpleSequence do
13
+
14
+ it "matches the sequence" do
15
+ SimpleSequence.parse("xyz").to_ast.should == [:root,'x','y','z']
16
+ end
17
+
18
+ it "doesn't matches anything else" do
19
+ SimpleSequence.parse("xy.").should == nil
20
+ end
21
+
22
+ end
23
+
24
+ class RepeatedSequence < RubyPeg
25
+ def root
26
+ node :root do
27
+ any_number_of { terminal('1') && ignore { terminal('2') } && terminal('3') } && one_or_more { terminal('x') && terminal('y') && terminal('z') } && terminal('z')
28
+ end
29
+ end
30
+ end
31
+
32
+ describe RepeatedSequence do
33
+
34
+ it "matches the sequence" do
35
+ RepeatedSequence.parse("xyzz").to_ast.should == [:root,'x','y','z','z']
36
+ end
37
+
38
+ it "matches a repeated sequence" do
39
+ RepeatedSequence.parse("xyzxyzz").to_ast.should == [:root,'x','y','z','x','y','z','z']
40
+ end
41
+
42
+ it "matches an optional repeated sequence" do
43
+ RepeatedSequence.parse("123123xyzxyzz").to_ast.should == [:root,'1','3','1','3','x','y','z','x','y','z','z']
44
+ end
45
+
46
+ it "doesn't matches anything else" do
47
+ RepeatedSequence.parse("xyxy.").should == nil
48
+ end
49
+
50
+ end
@@ -0,0 +1,118 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+ require 'rubypeg'
3
+
4
+ class TerminalNodeTest < RubyPeg
5
+ def root
6
+ terminal "one"
7
+ end
8
+ end
9
+
10
+ describe TerminalNodeTest do
11
+
12
+ def parse(text)
13
+ TerminalNodeTest.parse(text)
14
+ end
15
+
16
+ it "terminals are, by default, Strings" do
17
+ parse("one").should == "one"
18
+ end
19
+
20
+ it "they have been, by default, extended with the module TerminalNode" do
21
+ parse("one").should be_kind_of(TerminalNode)
22
+ end
23
+
24
+ it "TerminalNode responds to to_ast by returning itself" do
25
+ parse("one").to_ast.should be_kind_of(String)
26
+ parse("one").to_ast.should == "one"
27
+ end
28
+
29
+ it "TerminalNode responds to visit(builder) by returning itself" do
30
+ class TestBuilder; end
31
+ parse("one").visit(TestBuilder.new).should be_kind_of(String)
32
+ parse("one").visit(TestBuilder.new).should == "one"
33
+ end
34
+
35
+ end
36
+
37
+ class CreateTerminalNodeTest < RubyPeg
38
+ def root
39
+ terminal "one"
40
+ end
41
+
42
+ def create_terminal_node(string)
43
+ /#{string}/
44
+ end
45
+ end
46
+
47
+ describe CreateTerminalNodeTest do
48
+
49
+ def parse(text)
50
+ CreateTerminalNodeTest.parse(text)
51
+ end
52
+
53
+ it "the class of the terminal can be altered by overriding the create_terminal_node(string) method" do
54
+ parse("one").should be_kind_of(Regexp)
55
+ end
56
+
57
+ end
58
+
59
+ class StringTerminalTest < RubyPeg
60
+ def root
61
+ terminal "one"
62
+ end
63
+ end
64
+
65
+ describe StringTerminalTest do
66
+
67
+ def parse(text)
68
+ StringTerminalTest.parse(text)
69
+ end
70
+
71
+ it "if given a string, matches that string but nothing else" do
72
+ parse("one").to_ast.should == 'one'
73
+ parse("two").should == nil
74
+ parse("onetwo").to_ast.should == 'one'
75
+ end
76
+
77
+ end
78
+
79
+ class RegexpTerminalTest < RubyPeg
80
+ def root
81
+ terminal /one|Two/i
82
+ end
83
+ end
84
+
85
+ describe RegexpTerminalTest do
86
+
87
+ def parse(text)
88
+ RegexpTerminalTest.parse(text)
89
+ end
90
+
91
+ it "if given a regular expression, matches that expression but nothing else" do
92
+ parse("one").to_ast.should == 'one'
93
+ parse("two").to_ast.should == 'two'
94
+ parse("onetwo").to_ast.should == 'one'
95
+ parse("three").should == nil
96
+ end
97
+
98
+ end
99
+
100
+ class AnythingTerminalTest < RubyPeg
101
+ def root
102
+ terminal 1.0
103
+ end
104
+ end
105
+
106
+ describe AnythingTerminalTest do
107
+
108
+ def parse(text)
109
+ AnythingTerminalTest.parse(text)
110
+ end
111
+
112
+ it "if given anything else, converts it to astring and tries to match that" do
113
+ parse("1.0").to_ast.should == '1.0'
114
+ parse("1").should == nil
115
+ parse("1.011").to_ast.should == '1.0'
116
+ end
117
+
118
+ end