fixed parsing of numbers after line continuations
[voodoo-lang.git] / test / test_parser.rb
blob93c2192bbaa0555b0ca228a59f01090ef2a3b7ad
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.kind_of?(Voodoo::Parser::Error) ?
94                    "#{error.start_line}: #{error.text}\n" : '') +
95                   error.backtrace.join("\n"))
96       end
97     else
98       # Expecting exception.
99       if error == nil
100         # Got no exception.
101         fail_test "expected exception of class #{exception.class}, " +
102           "but got none"
103       elsif error.class != exception.class
104         # Got exception, but of wrong type.
105         fail_test("expected exception of class #{exception.class}, " +
106                   "but got one of type #{error.class}",
107                   "#{error.class}:#{error.message}\n" +
108                   (error.kind_of?(Voodoo::Parser::Error) ?
109                    "#{error.start_line}: #{error.text}\n" : '') +
110                   error.backtrace.join("\n"))
111       else
112         # Got exception of expected type.
113         pass_test
114       end
115     end
116   end
119 # Runs a test on the parser and verifies that the parser
120 # throws an exception of the expected type, with the expected
121 # line and column information.
123 # +name+        Name of the test
124 # +input+       Input to the test
125 # +error_type+  Expected error type
126 # +line+        Expected line number
127 # +column+      Expected column number
128 # +text+        Expected text from the source code
129 def parser_test_error name, input, error_type, line, column, text
130   # Test I/O error on the first character.
131   parser_test_ex(name, input) do |result, error|
132     if error.class != error_type
133       fail_test("expecting exception of class #{error_type}, " +
134                 "but got #{error.class}",
135                 "#{error.class}:#{error.message}\n" +
136                 error.backtrace.join("\n"))
137     elsif error.start_line != line || error.start_column != column
138       fail_test("wrong position. Expected line #{line}, column #{column}" +
139                 "; got line #{error.start_line}, column #{error.start_column}")
140     elsif error.text != text
141       fail_test("wrong text.",
142                 "===== ACTUAL =====\n#{error.text}\n" +
143                 "===== EXPECTED =====\n#{text}\n")
144       $stderr.puts error.backtrace
145     else
146       pass_test
147     end
148   end
151 def test_parser
152   parser_test "empty", "", []
154   parser_test "newline", "\n", []
156   parser_test "comment", "# only a comment", []
158   parser_test "comment_newline", "# only a comment\n", []
160   parser_test "multiline_comment", "# only a comment\n# multiline", []
162   parser_test "multiline_comment_newline",
163     "# only a comment\n# multiline\n", []
165   parser_test "section", "section functions", [[:section, :functions]]
167   parser_test "section_newline", "section functions\n",
168     [[:section, :functions]]
170   parser_test "section_comment", "section functions # functions section",
171     [[:section, :functions]]
173   parser_test "function_noargs", "function\nreturn 0\nend function\n",
174     [[:function, [], [:return, 0]]]
176   parser_test "function_onearg", "function x\nreturn x\nend function\n",
177     [[:function, [:x], [:return, :x]]]
179   parser_test "function_twoargs",
180     "function x y\nreturn add x y\nend function\n",
181     [[:function, [:x, :y], [:return, :add, :x, :y]]]
183   parser_test "continued", "call foo \\\n    bar", [[:call, :foo, :bar]]
185   parser_test "continued_number", "call foo \\\n    42", [[:call, :foo, 42]]
187   parser_test_error("io_error_start",
188                     BrokenStringReader.new("section data", 0),
189                     Voodoo::Parser::ParserInternalError,
190                     1, 0,
191                     '')
193   parser_test_error("io_error_first_char",
194                     BrokenStringReader.new("section data", 1),
195                     Voodoo::Parser::ParserInternalError,
196                     1, 1,
197                     's')
199   parser_test_error("io_error_second_line",
200                     BrokenStringReader.new("section data\nfoo:", 13),
201                     Voodoo::Parser::ParserInternalError,
202                     2, 0,
203                     '')
205   parser_test_ex("multiple_errors", "block\nlet\nset\n") do |result,error|
206     expected_message = <<EOT
207 Multiple errors:
209 2: let requires a symbol and an expression
211   let
212   
213 3: set requires a symbol or at-expression followed by an expression
215   set
216   
217 4: End of input while inside block
219     expected_text = <<EOT
226     if !error.kind_of? Voodoo::Parser::MultipleErrors
227       fail_test "Expected MultipleErrors, but got #{error.class}"
228     elsif error.errors.length != 3
229       fail_test "Expected 3 errors, but got #{error.errors.length}"
230     elsif error.errors[0].start_line != 2
231       fail_test "Expected first error on line 2" +
232         ", but got it on line #{error.errors[0].start_line}"
233     elsif error.errors[1].start_line != 3
234       fail_test "Expected second error on line 3" +
235         ", but got it on line #{error.errors[1].start_line}"
236     elsif error.errors[2].start_line != 4
237       fail_test "Expected third error on line 4" +
238         ", but got it on line #{error.errors[2].start_line}"
239     elsif error.message != expected_message
240       fail_test("Error message differs from expected",
241                 "===== ACTUAL =====\n#{error.message}\n" +
242                 "===== EXPECTED =====\n#{expected_message}\n")
243     elsif error.text != expected_text
244       fail_test("Error text differs from expected",
245                 "===== ACTUAL =====\n#{error.text}\n" +
246                 "===== EXPECTED =====\n#{expected_text}\n")
247     else
248       pass_test
249     end
250   end
253 if $0 == __FILE__
254   test_parser
255   exit report_test_results