rubypeg 0.0.2

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.
@@ -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