voodoo 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -54,13 +54,17 @@ module Voodoo
54
54
  @generator.add section, statement
55
55
  end
56
56
 
57
+ rescue Parser::MultipleErrors => e
58
+ errors.concat e.errors
59
+
57
60
  rescue Parser::ParseError => e
58
61
  errors << e
59
- if errors.length >= 100
60
- raise Error.new(errors)
61
- end
62
62
  end
63
63
 
64
+ if errors.length >= 100
65
+ # Too many errors, give up.
66
+ raise Error.new(errors)
67
+ end
64
68
  end
65
69
 
66
70
  if errors.empty?
data/lib/voodoo/config.rb CHANGED
@@ -2,7 +2,7 @@ module Voodoo
2
2
  # Methods to get and set configuration parameters
3
3
  module Config
4
4
  IMPLEMENTATION_NAME = 'Voodoo Compiler'
5
- IMPLEMENTATION_VERSION = '1.0.0'
5
+ IMPLEMENTATION_VERSION = '1.0.1'
6
6
 
7
7
  # Class that holds configuration parameters
8
8
  class Configuration
data/lib/voodoo/parser.rb CHANGED
@@ -33,16 +33,83 @@ module Voodoo
33
33
  @text = ''
34
34
  end
35
35
 
36
- class ParseError < StandardError
36
+ # Base class for errors reported from the parser.
37
+ # This provides methods to get the name of the input being processed,
38
+ # as well as the start_line, start_column, and text of the code
39
+ # that triggered the error.
40
+ class Error < StandardError
37
41
  def initialize message, input_name, start_line, start_column, text
38
- @message = message
42
+ super message
39
43
  @input_name = input_name
40
44
  @start_line = start_line
41
45
  @start_column = start_column
42
46
  @text = text
43
47
  end
44
48
 
45
- attr_reader :message, :input_name, :start_line, :start_column, :text
49
+ attr_reader :input_name, :start_line, :start_column, :text
50
+ end
51
+
52
+ # Class for parse errors.
53
+ # A ParseError indicates an error in the code being parsed.
54
+ # For other errors that the parser may raise, see ParserInternalError.
55
+ class ParseError < Parser::Error
56
+ def initialize message, input_name, start_line, start_column, text
57
+ super message, input_name, start_line, start_column, text
58
+ end
59
+ end
60
+
61
+ # Class for parser internal errors.
62
+ # A ParserInternalError indicates an error in the parser that is not
63
+ # flagged as an error in the code being parsed. Possible causes
64
+ # include I/O errors while reading code, as well as bugs in the
65
+ # parser.
66
+ #
67
+ # The +cause+ attribute indicates the initial cause for the error.
68
+ # The other attributes of ParserInternalError are inherited from
69
+ # Parser::Error and indicate the input that was being
70
+ # processed when the error occurred.
71
+ class ParserInternalError < Parser::Error
72
+ def initialize cause, input_name, start_line, start_column, text
73
+ super cause.message, input_name, start_line, start_column, text
74
+ @cause = cause
75
+ end
76
+
77
+ attr_reader :cause
78
+ end
79
+
80
+ # Class wrapping multiple Parser::Errors.
81
+ class MultipleErrors < Parser::Error
82
+ def initialize errors
83
+ @errors = errors
84
+ super(nil, errors[0].input_name, errors[0].start_line,
85
+ errors[0].start_column, nil)
86
+ end
87
+
88
+ attr_reader :errors
89
+
90
+ def message
91
+ if @message == nil
92
+ msg = "Multiple errors:\n\n"
93
+ @errors.each do |error|
94
+ msg << error.input_name << ":" if error.input_name
95
+ msg << "#{error.start_line}: " << error.message
96
+ if error.text != nil
97
+ msg << "\n\n #{error.text.gsub("\n", "\n ")}"
98
+ end
99
+ msg << "\n"
100
+ end
101
+ @message = msg
102
+ end
103
+ @message
104
+ end
105
+
106
+ def text
107
+ if @text == nil
108
+ texts = @errors.map {|error| error.text}
109
+ @text = texts.join "\n"
110
+ end
111
+ @text
112
+ end
46
113
  end
47
114
 
48
115
  # Parses a top-level element.
@@ -79,66 +146,73 @@ module Voodoo
79
146
  # # [:function, [:x, :y], [:let, :z, :add, :x, :y], [:return, :z]]
80
147
  #
81
148
  def parse_top_level
82
- # Skip whitespace, comments, and empty lines
83
- skip_to_next_top_level
149
+ wrap_exceptions do
150
+ @text = ''
151
+ # Skip whitespace, comments, and empty lines
152
+ skip_to_next_top_level
84
153
 
85
- validate_top_level do
86
- parse_top_level_nonvalidating
154
+ validate_top_level do
155
+ parse_top_level_nonvalidating
156
+ end
87
157
  end
88
158
  end
89
159
 
90
160
  # Parses a body for a function or a conditional
91
161
  def parse_body kind
92
- body = []
93
- error = nil
94
- case kind
95
- when :function
96
- kind_text = 'function definition'
97
- else
98
- kind_text = kind.to_s
99
- end
100
- done = false
101
- until done
102
- begin
103
- with_position do
104
- statement = parse_top_level_nonvalidating
105
- if statement == nil
106
- done = true
107
- parse_error "End of input while inside #{kind_text}", nil
108
- elsif statement[0] == :end
109
- # Done parsing body
110
- done = true
111
- elsif kind == :conditional && statement[0] == :else
112
- # Done parsing body, but there is another one coming up
113
- body << statement
114
- done = true
115
- else
116
- # Should be a normal statement. Validate it, then add it to body
117
- if statement[0] == :function
118
- parse_error "Function definitions are only allowed at top-level"
119
- end
120
- begin
121
- Validator.validate_statement statement
162
+ wrap_exceptions do
163
+ body = []
164
+ errors = []
165
+ case kind
166
+ when :function
167
+ kind_text = 'function definition'
168
+ else
169
+ kind_text = kind.to_s
170
+ end
171
+ done = false
172
+ until done
173
+ begin
174
+ with_position do
175
+ statement = parse_top_level_nonvalidating
176
+ if statement == nil
177
+ done = true
178
+ parse_error "End of input while inside #{kind_text}", nil
179
+
180
+ elsif statement[0] == :end
181
+ # Done parsing body
182
+ done = true
183
+ elsif kind == :conditional && statement[0] == :else
184
+ # Done parsing body, but there is another one coming up
122
185
  body << statement
123
- rescue Validator::ValidationError => e
124
- parse_error e.message
186
+ done = true
187
+ else
188
+ # Should be a normal statement. Validate it, then add it to body
189
+ if statement[0] == :function
190
+ parse_error "Function definitions are only allowed at top-level"
191
+ end
192
+ begin
193
+ Validator.validate_statement statement
194
+ body << statement
195
+ rescue Validator::ValidationError => e
196
+ parse_error e.message
197
+ end
125
198
  end
126
199
  end
127
- end
128
- rescue => e
129
- # Got some kind of error. Still try to parse the rest of the body.
130
- # Save the error if it was the first one.
131
- if error == nil
132
- error = e
200
+ rescue => e
201
+ # Got some kind of error. Still try to parse the rest of the body.
202
+ errors << e
133
203
  end
134
204
  end
135
- end
136
205
 
137
- if error != nil
138
- raise error
139
- end
206
+ # Raise error if we had just one.
207
+ # If we had more than one, raise a MultipleErrors instance.
208
+ if errors.length == 1
209
+ raise errors[0]
210
+ elsif errors.length > 1
211
+ raise MultipleErrors.new errors
212
+ end
140
213
 
141
- body
214
+ body
215
+ end
142
216
  end
143
217
 
144
218
  # Parses an escape sequence.
@@ -146,101 +220,109 @@ module Voodoo
146
220
  # character (backslash). It decodes the escape sequence and returns
147
221
  # the result as a string.
148
222
  def parse_escape
149
- result = nil
150
- consume
151
- case lookahead
152
- when :eof
153
- parse_error "Unexpected end of input in escape sequence", nil
154
- when "\\", "\"", " "
155
- result = lookahead
223
+ wrap_exceptions do
224
+ result = nil
156
225
  consume
157
- when "n"
158
- # \n is newline
159
- consume
160
- result = "\n"
161
- when "r"
162
- # \r is carriage return
163
- consume
164
- result = "\r"
165
- when "x"
166
- # \xXX is byte with hex value XX
167
- code = @input.read 2
168
- @column = @column + 2
169
- consume
170
- @text << code
171
- result = [code].pack('H2')
172
- when "\n"
173
- # \<newline> is line continuation character
174
- consume
175
- # Skip indentation of next line
176
- while lookahead =~ /\s/
226
+ case lookahead
227
+ when :eof
228
+ parse_error "Unexpected end of input in escape sequence", nil
229
+ when "\\", "\"", " "
230
+ result = lookahead
231
+ consume
232
+ when "n"
233
+ # \n is newline
234
+ consume
235
+ result = "\n"
236
+ when "r"
237
+ # \r is carriage return
238
+ consume
239
+ result = "\r"
240
+ when "x"
241
+ # \xXX is byte with hex value XX
242
+ code = @input.read 2
243
+ @column = @column + 2
244
+ consume
245
+ @text << code
246
+ result = [code].pack('H2')
247
+ when "\n"
248
+ # \<newline> is line continuation character
249
+ consume
250
+ # Skip indentation of next line
251
+ while lookahead =~ /\s/
252
+ consume
253
+ end
254
+ result = ''
255
+ else
256
+ # Default to just passing on next character
257
+ result = lookahead
177
258
  consume
178
259
  end
179
- result = ''
180
- else
181
- # Default to just passing on next character
182
- result = lookahead
183
- consume
260
+ result
184
261
  end
185
- result
186
262
  end
187
263
 
188
264
  # Parses a number.
189
265
  # This method should be called while the lookahead is the first
190
266
  # character of the number.
191
267
  def parse_number
192
- text = lookahead
193
- consume
194
- while lookahead =~ /\d/
195
- text << lookahead
268
+ wrap_exceptions do
269
+ text = lookahead
196
270
  consume
271
+ while lookahead =~ /\d/
272
+ text << lookahead
273
+ consume
274
+ end
275
+ text.to_i
197
276
  end
198
- text.to_i
199
277
  end
200
278
 
201
279
  # Parses a string.
202
280
  # This method should be called while the lookahead is the opening
203
281
  # double quote.
204
282
  def parse_string
205
- consume
206
- result = ''
207
- while true
208
- case lookahead
209
- when "\""
210
- consume
211
- break
212
- when "\\"
213
- result << parse_escape
214
- else
215
- result << lookahead
216
- consume
283
+ wrap_exceptions do
284
+ consume
285
+ result = ''
286
+ while true
287
+ case lookahead
288
+ when "\""
289
+ consume
290
+ break
291
+ when "\\"
292
+ result << parse_escape
293
+ else
294
+ result << lookahead
295
+ consume
296
+ end
217
297
  end
298
+ result
218
299
  end
219
- result
220
300
  end
221
301
 
222
302
  # Parses a symbol.
223
303
  # This method should be called while the lookahead is the first
224
304
  # character of the symbol name.
225
305
  def parse_symbol
226
- name = ''
227
- while true
228
- case lookahead
229
- when "\\"
230
- name << parse_escape
231
- when /\w|-/
232
- name << lookahead
233
- consume
234
- when ':'
235
- # Colon parsed as last character of the symbol name
236
- name << lookahead
237
- consume
238
- break
239
- else
240
- break
306
+ wrap_exceptions do
307
+ name = ''
308
+ while true
309
+ case lookahead
310
+ when "\\"
311
+ name << parse_escape
312
+ when /\w|-/
313
+ name << lookahead
314
+ consume
315
+ when ':'
316
+ # Colon parsed as last character of the symbol name
317
+ name << lookahead
318
+ consume
319
+ break
320
+ else
321
+ break
322
+ end
241
323
  end
324
+ name.to_sym
242
325
  end
243
- name.to_sym
244
326
  end
245
327
 
246
328
  #
@@ -252,13 +334,11 @@ module Voodoo
252
334
  # The character is appended to @text.
253
335
  def consume
254
336
  old = @lookahead
255
- if old == 10
337
+ @lookahead = nil
338
+ if old == "\n"
256
339
  @line = @line.succ
257
340
  @column = 0
258
341
  end
259
- @lookahead = @input.getc
260
- @lookahead = :eof if @lookahead == nil
261
- @column = @column.succ unless @lookahead == :eof
262
342
  @text << old
263
343
  old
264
344
  end
@@ -274,18 +354,18 @@ module Voodoo
274
354
  end
275
355
 
276
356
  # Returns the current lookahead character,
277
- # or +nil+ when the end of the input has been reached.
357
+ # or +:eof+ when the end of the input has been reached.
278
358
  def lookahead
279
359
  if @lookahead == nil
280
360
  @lookahead = @input.getc
281
- @column = @column.succ
282
- end
283
- case @lookahead
284
- when :eof
285
- :eof
286
- else
287
- @lookahead.chr
361
+ if @lookahead == nil
362
+ @lookahead = :eof
363
+ else
364
+ @lookahead = @lookahead.chr
365
+ @column = @column.succ
366
+ end
288
367
  end
368
+ @lookahead
289
369
  end
290
370
 
291
371
  # Parses a conditional statement
@@ -316,7 +396,7 @@ module Voodoo
316
396
  error.set_backtrace caller
317
397
 
318
398
  # If we are not at a new line, skip until the next line
319
- while @column != 1 && lookahead != :eof
399
+ while @column > 1 && lookahead != :eof
320
400
  consume
321
401
  end
322
402
 
@@ -348,7 +428,6 @@ module Voodoo
348
428
  when "#"
349
429
  # Skip comment
350
430
  while lookahead != :eof && lookahead != "\n"
351
- word << lookahead
352
431
  consume
353
432
  end
354
433
  else
@@ -364,6 +443,7 @@ module Voodoo
364
443
  # Add word to statement
365
444
  words << word
366
445
  end
446
+ words
367
447
  end
368
448
 
369
449
  # We have a line of input. Conditionals and function declarations
@@ -505,7 +585,9 @@ module Voodoo
505
585
  @start_line = @line
506
586
  @start_column = @column
507
587
  @text = ''
508
- yield
588
+ wrap_exceptions do
589
+ yield
590
+ end
509
591
  ensure
510
592
  # Restore old values
511
593
  @start_line = old_line
@@ -514,5 +596,20 @@ module Voodoo
514
596
  end
515
597
  end
516
598
 
599
+ # Ensures that any exceptions that escape from block are instances of
600
+ # Parser::Error.
601
+ def wrap_exceptions &block
602
+ begin
603
+ yield
604
+ rescue Parser::Error
605
+ # Already an instance of Parser::Error; pass it through.
606
+ raise
607
+ rescue => e
608
+ # Some other error; wrap in ParserInternalError.
609
+ raise ParserInternalError.new(e, @input_name, @line,
610
+ @column, @text)
611
+ end
612
+ end
613
+
517
614
  end
518
615
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voodoo
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 0
10
- version: 1.0.0
9
+ - 1
10
+ version: 1.0.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Robbert Haarman
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-01-14 00:00:00 -08:00
18
+ date: 2012-01-29 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21