llip 0.1.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/History.txt +4 -0
- data/MIT-LICENSE +21 -0
- data/Manifest.txt +45 -0
- data/README.txt +148 -0
- data/Rakefile +66 -0
- data/examples/ariteval/ariteval.rb +132 -0
- data/examples/ariteval/evaluator.rb +61 -0
- data/examples/ariteval/exp.rb +104 -0
- data/lib/llip.rb +6 -0
- data/lib/llip/abstract_parser.rb +170 -0
- data/lib/llip/abstract_scanner.rb +83 -0
- data/lib/llip/buffer.rb +35 -0
- data/lib/llip/llip_error.rb +43 -0
- data/lib/llip/parser.rb +93 -0
- data/lib/llip/production_compiler.rb +168 -0
- data/lib/llip/production_specification.rb +79 -0
- data/lib/llip/recursive_production_compiler.rb +35 -0
- data/lib/llip/regexp_abstract_scanner.rb +116 -0
- data/lib/llip/regexp_parser.rb +197 -0
- data/lib/llip/regexp_scanner.rb +33 -0
- data/lib/llip/regexp_specification.rb +210 -0
- data/lib/llip/token.rb +47 -0
- data/lib/llip/visitable.rb +37 -0
- data/spec/ariteval/ariteval_spec.rb +111 -0
- data/spec/ariteval/evaluator_spec.rb +106 -0
- data/spec/ariteval/exp_spec.rb +232 -0
- data/spec/llip/abstract_parser_spec.rb +273 -0
- data/spec/llip/abstract_scanner_spec.rb +152 -0
- data/spec/llip/buffer_spec.rb +60 -0
- data/spec/llip/llip_error_spec.rb +77 -0
- data/spec/llip/parser_spec.rb +163 -0
- data/spec/llip/production_compiler_spec.rb +271 -0
- data/spec/llip/production_specification_spec.rb +75 -0
- data/spec/llip/recursive_production_compiler_spec.rb +86 -0
- data/spec/llip/regexp_abstract_scanner_spec.rb +320 -0
- data/spec/llip/regexp_parser_spec.rb +265 -0
- data/spec/llip/regexp_scanner_spec.rb +40 -0
- data/spec/llip/regexp_specification_spec.rb +734 -0
- data/spec/llip/token_spec.rb +70 -0
- data/spec/llip/visitable_spec.rb +38 -0
- data/spec/spec_helper.rb +10 -0
- metadata +110 -0
@@ -0,0 +1,152 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'abstract_scanner'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe "An AbstractScanner" do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@instance = AbstractScanner.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be able to scan a string" do
|
12
|
+
@instance.should respond_to(:scan)
|
13
|
+
@instance.should_receive(:read_next).once
|
14
|
+
lambda { @instance.scan("this is a string") }.should_not raise_error
|
15
|
+
@instance.source.should be_kind_of(StringIO)
|
16
|
+
@instance.source.string.should == "this is a string"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be able to scan an IO" do
|
20
|
+
@instance.should respond_to(:scan)
|
21
|
+
@instance.should_receive(:read_next).once
|
22
|
+
io = StringIO.new("this is a string")
|
23
|
+
lambda { @instance.scan(io) }.should_not raise_error
|
24
|
+
@instance.source.should == io
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a source attribute" do
|
28
|
+
@instance.should respond_to(:source)
|
29
|
+
@instance.source.should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should call scan when initialized with an argument" do
|
33
|
+
lambda { @instance = AbstractScanner.new("this is a string") }.should_not raise_error
|
34
|
+
@instance.source.string.should == "this is a string"
|
35
|
+
io = StringIO.new("this is another string")
|
36
|
+
lambda { @instance = AbstractScanner.new(io) }.should_not raise_error
|
37
|
+
@instance.source.should == io
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should increments :current_line and reset :current_char when called :scan" do
|
41
|
+
@instance.should_receive(:read_next).once
|
42
|
+
@instance.instance_variable_set(:@current_char,5)
|
43
|
+
@instance.scan("b")
|
44
|
+
@instance.current_line.should == 0
|
45
|
+
@instance.current_char.should == -1
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should initialize current to a Token" do
|
49
|
+
@instance.should respond_to(:current)
|
50
|
+
@instance.current.should be_kind_of(Token)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have a current_line attribute which is initialized at -1" do
|
54
|
+
@instance.should respond_to(:current_line)
|
55
|
+
@instance.current_line.should == -1
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should have a current_char attribute which is initialized at -1" do
|
59
|
+
@instance.should respond_to(:current_char)
|
60
|
+
@instance.current_char.should == -1
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have a next method which raises a NotImplementedError" do
|
64
|
+
lambda { @instance.next }.should raise_error(NotImplementedError)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "An AbstractScanner with all its protected methods exposed should have a read_next method" do
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
@protected_methods = AbstractScanner.protected_instance_methods(false)
|
72
|
+
@protected_methods.each { |m| AbstractScanner.send(:public,m) }
|
73
|
+
@instance = AbstractScanner.new
|
74
|
+
@instance.should respond_to(:read_next)
|
75
|
+
end
|
76
|
+
|
77
|
+
after(:each) do
|
78
|
+
@protected_methods.each { |m| AbstractScanner.send(:protected,m) }
|
79
|
+
end
|
80
|
+
|
81
|
+
it "which reads a char from the source" do
|
82
|
+
@instance.scan("This is a string") # scan automatically calls read_next. See the previous context.
|
83
|
+
@instance.source.readchar.chr.should == "h"
|
84
|
+
@instance.read_next.should == "i"
|
85
|
+
@instance.source.readchar.chr.should == "s"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "which handles correctly eof" do
|
89
|
+
@instance.scan("a")
|
90
|
+
lambda { @instance.read_next }.should_not raise_error
|
91
|
+
@instance.source.should be_eof
|
92
|
+
@instance.instance_variable_get("@next_char").should be_nil
|
93
|
+
end
|
94
|
+
|
95
|
+
it "which store the read char into @next_char" do
|
96
|
+
@instance.scan("ab")
|
97
|
+
@instance.instance_variable_get("@next_char").should == "a"
|
98
|
+
@instance.read_next
|
99
|
+
@instance.instance_variable_get("@next_char").should == "b"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "which reads correctly multibyte chars" do
|
103
|
+
@instance.scan("éòù")
|
104
|
+
@instance.read_next.should == "ò"
|
105
|
+
@instance.read_next.should == "ù"
|
106
|
+
@instance.read_next.should be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "which increments :current_line for every line read" do
|
110
|
+
@instance.scan("cc\nb\ng")
|
111
|
+
@instance.read_next.should == "c"
|
112
|
+
@instance.current_line == 0
|
113
|
+
@instance.read_next.should == "\n"
|
114
|
+
@instance.current_line == 1
|
115
|
+
@instance.read_next.should == "b"
|
116
|
+
@instance.read_next.should == "\n"
|
117
|
+
@instance.current_line == 2
|
118
|
+
@instance.read_next.should == "g"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "which increments :current_char for every char read" do
|
122
|
+
@instance.scan("abcde")
|
123
|
+
@instance.current_char.should == 0
|
124
|
+
|
125
|
+
@instance.read_next.should == "b"
|
126
|
+
@instance.current_char.should == 1
|
127
|
+
|
128
|
+
@instance.read_next.should == "c"
|
129
|
+
@instance.current_char.should == 2
|
130
|
+
|
131
|
+
@instance.read_next.should == "d"
|
132
|
+
@instance.current_char.should == 3
|
133
|
+
|
134
|
+
@instance.read_next.should == "e"
|
135
|
+
@instance.current_char.should == 4
|
136
|
+
end
|
137
|
+
|
138
|
+
it "which reset :current_char for every line read" do
|
139
|
+
@instance.scan("cc\nb\ng")
|
140
|
+
@instance.read_next.should == "c"
|
141
|
+
|
142
|
+
@instance.read_next.should == "\n"
|
143
|
+
@instance.current_char.should == -1
|
144
|
+
@instance.current_line == 1
|
145
|
+
@instance.read_next.should == "b"
|
146
|
+
@instance.read_next.should == "\n"
|
147
|
+
@instance.current_char.should == -1
|
148
|
+
@instance.current_line == 2
|
149
|
+
@instance.read_next.should == "g"
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'buffer'
|
3
|
+
|
4
|
+
describe "A Buffer" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@scanner = mock 'Scanner'
|
8
|
+
@buf = Buffer.new(@scanner)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should wrap a scanner" do
|
12
|
+
@buf.should respond_to(:scanner)
|
13
|
+
@buf.should respond_to(:scanner=)
|
14
|
+
|
15
|
+
@buf.scanner.should == @scanner
|
16
|
+
|
17
|
+
@buf.scanner = nil
|
18
|
+
@buf.scanner.should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have a next method, which calls the scanner's next method" do
|
22
|
+
@buf.should respond_to(:next)
|
23
|
+
@scanner.should_receive(:next).and_return(:token)
|
24
|
+
@buf.next.should == :token
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have a scan method, which calls the scanner's scan method" do
|
28
|
+
@buf.should respond_to(:scan)
|
29
|
+
@scanner.should_receive(:scan).with("a string")
|
30
|
+
@buf.scan("a string").should == @buf
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a current method, which returns the last token read" do
|
34
|
+
@buf.should respond_to(:current)
|
35
|
+
@scanner.should_receive(:next).and_return(:token)
|
36
|
+
@buf.next
|
37
|
+
@buf.current.should == :token
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should allow lookahead" do
|
41
|
+
@buf.should respond_to(:lookahead)
|
42
|
+
@scanner.should_receive(:next).and_return(:first,:second,:third,nil)
|
43
|
+
@buf.next
|
44
|
+
@buf.current.should == :first
|
45
|
+
|
46
|
+
@buf.lookahead(1).should == :second
|
47
|
+
@buf.current.should == :first
|
48
|
+
|
49
|
+
@buf.lookahead(2).should == :third
|
50
|
+
@buf.lookahead(1).should == :second
|
51
|
+
@buf.current.should == :first
|
52
|
+
|
53
|
+
@buf.next
|
54
|
+
|
55
|
+
@buf.lookahead(2).should be_nil
|
56
|
+
@buf.lookahead(1).should == :third
|
57
|
+
@buf.current.should == :second
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'llip_error'
|
3
|
+
|
4
|
+
describe "An LLIPError" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@instance = LLIPError.new(:a_token)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have a Token and a optional message as initialization arguments" do
|
11
|
+
lambda { @instance = LLIPError.new }.should raise_error(ArgumentError)
|
12
|
+
lambda { @instance = LLIPError.new(:a_token,"a message") }.should_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a :token attribute" do
|
16
|
+
@instance.should respond_to(:token)
|
17
|
+
@instance.token.should == :a_token
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should add information about the line and the char from the token to the message" do
|
21
|
+
token = mock("Token")
|
22
|
+
@instance = LLIPError.new(token,"this is a fake error message.")
|
23
|
+
token.should_receive(:line).and_return(1)
|
24
|
+
token.should_receive(:char).and_return(5)
|
25
|
+
|
26
|
+
@instance.to_s.should == "At line 1 char 5 a LLIP::LLIPError occurred: this is a fake error message."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "An instance of a class descending from LLIPError" do
|
31
|
+
|
32
|
+
before(:each) do
|
33
|
+
@class = Class.new(LLIPError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have the right to_s method" do
|
37
|
+
@class.should_receive(:name).and_return("FakeLLIPError")
|
38
|
+
|
39
|
+
token = mock("Token")
|
40
|
+
@instance = @class.new(token,"this is a fake error message.")
|
41
|
+
token.should_receive(:line).and_return(1)
|
42
|
+
token.should_receive(:char).and_return(5)
|
43
|
+
|
44
|
+
|
45
|
+
@instance.to_s.should == "At line 1 char 5 a FakeLLIPError occurred: this is a fake error message."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "A UnvalidTokenError" do
|
50
|
+
|
51
|
+
it "should have a correct to_s method " do
|
52
|
+
token = mock("Token")
|
53
|
+
token.should_receive(:line).and_return(1)
|
54
|
+
token.should_receive(:char).and_return(5)
|
55
|
+
token.should_receive(:value).and_return("a")
|
56
|
+
token.should_receive(:name).and_return(:a_regexp)
|
57
|
+
|
58
|
+
@instance = UnvalidTokenError.new(token)
|
59
|
+
@instance.to_s.should == "At line 1 char 5 a LLIP::UnvalidTokenError occurred: the current token 'a' doesn't match with the regular expression a_regexp."
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "A NotAllowedTokenError" do
|
65
|
+
|
66
|
+
it "should have a correct to_s method " do
|
67
|
+
token = mock("Token")
|
68
|
+
token.should_receive(:line).and_return(1)
|
69
|
+
token.should_receive(:char).and_return(5)
|
70
|
+
token.should_receive(:value).and_return("a")
|
71
|
+
token.should_receive(:name).and_return(:a_regexp)
|
72
|
+
|
73
|
+
lambda { @instance = NotAllowedTokenError.new(token,:a_production) }.should_not raise_error
|
74
|
+
@instance.to_s.should == "At line 1 char 5 a LLIP::NotAllowedTokenError occurred: the token 'a' matched by the regexp 'a_regexp' isn't allowed in production a_production."
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'parser'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe "A class that descend from Parser" do
|
6
|
+
before(:each) do
|
7
|
+
@class = Class.new(Parser)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have an attribute parser which descends from AbstractParser" do
|
11
|
+
@class.should respond_to(:parser)
|
12
|
+
@class.parser.should be_kind_of(Class)
|
13
|
+
@class.parser.ancestors[1].should == AbstractParser
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have an attribute scanner which descends from RegexpAbstractScanner" do
|
17
|
+
@class.should respond_to(:scanner)
|
18
|
+
@class.scanner.should be_kind_of(Class)
|
19
|
+
@class.scanner.ancestors[1].should == RegexpAbstractScanner
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should redirect :production, :scope to the parser" do
|
23
|
+
@class.should respond_to(:production)
|
24
|
+
@class.should respond_to(:scope)
|
25
|
+
|
26
|
+
m = mock("parser")
|
27
|
+
m.should_receive(:production).and_return(:prod)
|
28
|
+
m.should_receive(:scope).and_return(:scop)
|
29
|
+
|
30
|
+
@class.instance_variable_set(:@parser,m)
|
31
|
+
|
32
|
+
@class.production
|
33
|
+
@class.scope
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should have a :regexp_parser and :regexp_scanner attributes" do
|
37
|
+
@class.should respond_to(:regexp_parser)
|
38
|
+
@class.should respond_to(:regexp_scanner)
|
39
|
+
|
40
|
+
@class.regexp_parser.should be_kind_of(RegexpParser)
|
41
|
+
@class.regexp_scanner.should be_kind_of(RegexpScanner)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have a token method which parse a regexp and calls :add_regexp to the scanner" do
|
45
|
+
|
46
|
+
@class.should respond_to(:token)
|
47
|
+
|
48
|
+
rs = mock("regexp_scanner")
|
49
|
+
rs.should_receive(:scan).with("a regexp").and_return(rs)
|
50
|
+
|
51
|
+
regexp = mock("regexp")
|
52
|
+
regexp.should_receive(:name=).with(:a_name)
|
53
|
+
|
54
|
+
rp = mock("regexp_parser")
|
55
|
+
rp.should_receive(:parse).with(rs).and_return(regexp)
|
56
|
+
|
57
|
+
m = mock("scanner")
|
58
|
+
m.should_receive(:add_regexp).with(regexp)
|
59
|
+
|
60
|
+
@class.instance_variable_set(:@regexp_scanner,rs)
|
61
|
+
@class.instance_variable_set(:@regexp_parser,rp)
|
62
|
+
|
63
|
+
@class.instance_variable_set(:@scanner,m)
|
64
|
+
@class.token(:a_name,"a regexp")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should have a lookahead method which allow to set the lookahead behaviour" do
|
68
|
+
@class.should respond_to(:lookahead)
|
69
|
+
@class.lookahead.should == false
|
70
|
+
@class.lookahead(true)
|
71
|
+
@class.lookahead.should == true
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "The instance of a class desceding from Parser" do
|
77
|
+
|
78
|
+
before(:each) do
|
79
|
+
@class = Class.new(Parser)
|
80
|
+
@instance = @class.new
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should have a parser which must be kind of its class parser" do
|
84
|
+
@instance.should respond_to(:parser)
|
85
|
+
@instance.parser.should be_kind_of(@class.parser)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should have a scanner which must be kind of its class scanner" do
|
89
|
+
@instance.should respond_to(:scanner)
|
90
|
+
@instance.scanner.should be_kind_of(@class.scanner)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "with lookahead(true) should have a buffer encapsuling the scanner instead of the scanner" do
|
94
|
+
@class.lookahead(true)
|
95
|
+
@instance = @class.new
|
96
|
+
|
97
|
+
@instance.scanner.should be_kind_of(Buffer)
|
98
|
+
s = mock("scanner")
|
99
|
+
s.should_receive(:scan).with("a string").and_return(s)
|
100
|
+
|
101
|
+
@instance.scanner.scanner= s
|
102
|
+
|
103
|
+
result = mock("result")
|
104
|
+
|
105
|
+
p = mock("parser")
|
106
|
+
p.should_receive(:parse).with(@instance.scanner).and_return(result)
|
107
|
+
|
108
|
+
@instance.instance_variable_set(:@parser,p)
|
109
|
+
|
110
|
+
@instance.parse("a string")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have a :parse method which accept a source and parse thrugh the parser and the scanner" do
|
114
|
+
@instance.should respond_to(:parse)
|
115
|
+
|
116
|
+
s = mock("scanner")
|
117
|
+
s.should_receive(:scan).with("a string").and_return(s)
|
118
|
+
|
119
|
+
result = mock("result")
|
120
|
+
|
121
|
+
p = mock("parser")
|
122
|
+
p.should_receive(:parse).with(s).and_return(result)
|
123
|
+
|
124
|
+
@instance.instance_variable_set(:@scanner,s)
|
125
|
+
@instance.instance_variable_set(:@parser,p)
|
126
|
+
|
127
|
+
@instance.parse("a string").should == result
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "The instance of a class descending from Parser with a simple grammar should be able to parse" do
|
132
|
+
|
133
|
+
before(:each) do
|
134
|
+
@class = Class.new(Parser)
|
135
|
+
@instance = @class.new
|
136
|
+
|
137
|
+
@class.token(:plus,"\\+")
|
138
|
+
@class.token(:number,("0".."9").to_a.join("|"))
|
139
|
+
|
140
|
+
@class.scope(:exp)
|
141
|
+
|
142
|
+
@class.production(:exp,:recursive) do |p|
|
143
|
+
p.default { |scanner,parser| parser.parse_num }
|
144
|
+
|
145
|
+
p.token(:plus) do |result,scanner,parser|
|
146
|
+
scanner.next
|
147
|
+
result + parser.parse_num
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
@class.production(:num,:single) do |p|
|
152
|
+
p.token(:number) do |result,scanner,parser|
|
153
|
+
result = scanner.current.value.to_i
|
154
|
+
scanner.next
|
155
|
+
result
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "'1+1'" do
|
161
|
+
@instance.parse("1+1").should == 2
|
162
|
+
end
|
163
|
+
end
|