Made parser able to report multiple errors per call, using new Voodoo::Parser::Multip...
[voodoo-lang.git] / test / test_parser.rb
bloba300258205997d9faa80e51465a3d92716cda8c3
1 #! /usr/bin/env ruby
3 require File.dirname(__FILE__) + '/test'
4 require 'voodoo/parser'
6 # Class that provides a getc method that reads from a string.
7 class StringReader
8   def initialize str
9     @str = str
10     @pos = 0
11   end
13   def getc
14     if @pos < @str.length
15       x = @str[@pos]
16       @pos = @pos + 1
17       x
18     else
19       nil
20     end
21   end
22 end
24 # Like StringReader, but will throw an error after n characters.
25 class BrokenStringReader < StringReader
26   def initialize str, n
27     super str
28     @n = n
29   end
31   def getc
32     if @pos == @n
33       raise StandardError, "Error generated by BrokenStringReader"
34     end
35     super
36   end
37 end
39 # Runs a test on the parser.
41 # +name+      Name of the test
42 # +input+     Input to the test
44 # If given a block, returns the result of yielding to the block.
45 # The block receives two arguments:
46 # +result+    Result from the parser (nil if an error was raised)
47 # +error+     Error that was raised (nil if no error was raised)
48 def parser_test_ex name, input
49   print "#{name}..."
50   result = []
51   error = nil
53   begin
54     if input.respond_to? :getc
55       reader = input
56     else
57       reader = StringReader.new input
58     end
59     parser = Voodoo::Parser.new reader
60     while (x = parser.parse_top_level) != nil
61       result << x
62     end
63   rescue => e
64     result = nil
65     error = e
66   end
68   yield result, error
69 end
71 # Runs a test on the parser.
73 # +name+      Name of the test
74 # +input+     Input to the test
75 # +expected+  Expected return value from the parser
76 # +exception+ Expected exception class, or nil if no exception expected
77 def parser_test name, input, expected, exception = nil
78   parser_test_ex(name, input) do |result, error|
79     if exception == nil
80       # Not expecting exception.
81       if error == nil
82         # Got no exception.
83         if result == expected
84           pass_test
85         else
86           fail_test("result did not match expected result",
87                     "Got:\n#{result.inspect}\nExpected:\n#{expected.inspect}")
88         end
89       else
90         # Wasnt't expecting exception, but got one.
91         fail_test("unexpected exception of type #{error.class}",
92                   "#{error.class}:#{error.message}\n" +
93                   error.backtrace.join("\n"))
94       end
95     else
96       # Expecting exception.
97       if error == nil
98         # Got no exception.
99         fail_test "expected exception of class #{exception.class}, " +
100           "but got none"
101       elsif error.class != exception.class
102         # Got exception, but of wrong type.
103         fail_test("expected exception of class #{exception.class}, " +
104                   "but got one of type #{error.class}",
105                   "#{error.class}:#{error.message}\n" +
106                   error.backtrace.join("\n"))
107       else
108         # Got exception of expected type.
109         pass_test
110       end
111     end
112   end
115 # Runs a test on the parser and verifies that the parser
116 # throws an exception of the expected type, with the expected
117 # line and column information.
119 # +name+        Name of the test
120 # +input+       Input to the test
121 # +error_type+  Expected error type
122 # +line+        Expected line number
123 # +column+      Expected column number
124 def parser_test_error name, input, error_type, line, column
125   # Test I/O error on the first character.
126   parser_test_ex(name, input) do |result, error|
127     if error.class != error_type
128       fail_test("expecting exception of class #{error_type}, " +
129                 "but got #{error.class}",
130                 "#{error.class}:#{error.message}\n" +
131                 error.backtrace.join("\n"))
132     elsif error.start_line != line || error.start_column != column
133       fail_test("wrong position. Expected line #{line}, column #{column}" +
134                 "; got line #{error.start_line}, column #{error.start_column}")
135     else
136       pass_test
137     end
138   end
141 def test_parser
142   parser_test "empty", "", []
144   parser_test "newline", "\n", []
146   parser_test "comment", "# only a comment", []
148   parser_test "comment_newline", "# only a comment\n", []
150   parser_test "multiline_comment", "# only a comment\n# multiline", []
152   parser_test "multiline_comment_newline",
153     "# only a comment\n# multiline\n", []
155   parser_test "section", "section functions", [[:section, :functions]]
157   parser_test "section_newline", "section functions\n",
158     [[:section, :functions]]
160   parser_test "section_comment", "section functions # functions section",
161     [[:section, :functions]]
163   parser_test "function_noargs", "function\nreturn 0\nend function\n",
164     [[:function, [], [:return, 0]]]
166   parser_test "function_onearg", "function x\nreturn x\nend function\n",
167     [[:function, [:x], [:return, :x]]]
169   parser_test "function_twoargs",
170     "function x y\nreturn add x y\nend function\n",
171     [[:function, [:x, :y], [:return, :add, :x, :y]]]
173   parser_test_error("io_error_start",
174                     BrokenStringReader.new("section data", 0),
175                     Voodoo::Parser::ParserInternalError,
176                     1, 0)
178   parser_test_error("io_error_first_char",
179                     BrokenStringReader.new("section data", 1),
180                     Voodoo::Parser::ParserInternalError,
181                     1, 1)
183   parser_test_error("io_error_second_line",
184                     BrokenStringReader.new("section data\nfoo:", 13),
185                     Voodoo::Parser::ParserInternalError,
186                     2, 0)
188   parser_test_ex("multiple_errors", "block\nlet\nset\n") do |result,error|
189     expected_message = <<EOT
190 Multiple errors:
192 2: let requires a symbol and an expression
194   let
195   
196 3: set requires a symbol and an expression
198   set
199   
200 4: End of input while inside block
202     expected_text = <<EOT
209     if !error.kind_of? Voodoo::Parser::MultipleErrors
210       fail_test "Expected MultipleErrors, but got #{error.class}"
211     elsif error.errors.length != 3
212       fail_test "Expected 3 errors, but got #{error.errors.length}"
213     elsif error.errors[0].start_line != 2
214       fail_test "Expected first error on line 2" +
215         ", but got it on line #{error.errors[0].start_line}"
216     elsif error.errors[1].start_line != 3
217       fail_test "Expected second error on line 3" +
218         ", but got it on line #{error.errors[1].start_line}"
219     elsif error.errors[2].start_line != 4
220       fail_test "Expected third error on line 4" +
221         ", but got it on line #{error.errors[2].start_line}"
222     elsif error.message != expected_message
223       fail_test("Error message differs from expected",
224                 "===== ACTUAL =====\n#{error.message}\n" +
225                 "===== EXPECTED =====\n#{expected_message}\n")
226     elsif error.text != expected_text
227       fail_test("Error text differs from expected",
228                 "===== ACTUAL =====\n#{error.text}\n" +
229                 "===== EXPECTED =====\n#{expected_text}\n")
230     else
231       pass_test
232     end
233   end
236 if $0 == __FILE__
237   test_parser
238   exit report_test_results