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