llip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +4 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +45 -0
  4. data/README.txt +148 -0
  5. data/Rakefile +66 -0
  6. data/examples/ariteval/ariteval.rb +132 -0
  7. data/examples/ariteval/evaluator.rb +61 -0
  8. data/examples/ariteval/exp.rb +104 -0
  9. data/lib/llip.rb +6 -0
  10. data/lib/llip/abstract_parser.rb +170 -0
  11. data/lib/llip/abstract_scanner.rb +83 -0
  12. data/lib/llip/buffer.rb +35 -0
  13. data/lib/llip/llip_error.rb +43 -0
  14. data/lib/llip/parser.rb +93 -0
  15. data/lib/llip/production_compiler.rb +168 -0
  16. data/lib/llip/production_specification.rb +79 -0
  17. data/lib/llip/recursive_production_compiler.rb +35 -0
  18. data/lib/llip/regexp_abstract_scanner.rb +116 -0
  19. data/lib/llip/regexp_parser.rb +197 -0
  20. data/lib/llip/regexp_scanner.rb +33 -0
  21. data/lib/llip/regexp_specification.rb +210 -0
  22. data/lib/llip/token.rb +47 -0
  23. data/lib/llip/visitable.rb +37 -0
  24. data/spec/ariteval/ariteval_spec.rb +111 -0
  25. data/spec/ariteval/evaluator_spec.rb +106 -0
  26. data/spec/ariteval/exp_spec.rb +232 -0
  27. data/spec/llip/abstract_parser_spec.rb +273 -0
  28. data/spec/llip/abstract_scanner_spec.rb +152 -0
  29. data/spec/llip/buffer_spec.rb +60 -0
  30. data/spec/llip/llip_error_spec.rb +77 -0
  31. data/spec/llip/parser_spec.rb +163 -0
  32. data/spec/llip/production_compiler_spec.rb +271 -0
  33. data/spec/llip/production_specification_spec.rb +75 -0
  34. data/spec/llip/recursive_production_compiler_spec.rb +86 -0
  35. data/spec/llip/regexp_abstract_scanner_spec.rb +320 -0
  36. data/spec/llip/regexp_parser_spec.rb +265 -0
  37. data/spec/llip/regexp_scanner_spec.rb +40 -0
  38. data/spec/llip/regexp_specification_spec.rb +734 -0
  39. data/spec/llip/token_spec.rb +70 -0
  40. data/spec/llip/visitable_spec.rb +38 -0
  41. data/spec/spec_helper.rb +10 -0
  42. 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