Class: Voodoo::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/voodoo/parser.rb

Overview

Voodoo parser. The parser reads Voodoo source code and turns it into Ruby objects.

The public interface to Parser consists of the methods #new and #parse_top_level

Example usage:

require 'voodoo/parser'

File.open('example.voo') do |infile|
  parser = Voodoo::Parser.new infile

  while (element = parser.parse_top_level)
    puts element.inspect
  end
end

Defined Under Namespace

Classes: Error, MultipleErrors, ParseError, ParserInternalError

Constant Summary collapse

NUMBER_STARTER =
/\d|-/
STRING_STARTER =
'"'
SYMBOL_STARTER =
/[[:alpha:]]|\\/

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ Parser

Creates a parser using the specified object as input. The input object must support a method getc, which must return the next character of the input, or nil to indicate the end of the input has been reached.



31
32
33
34
35
36
37
38
# File 'lib/voodoo/parser.rb', line 31

def initialize input
  @input = input
  @input_name = input.respond_to?(:path) ? input.path : nil
  @start_line = @line = 1
  @start_column = @column = 0
  @lookahead = nil
  @text = ''
end

Instance Method Details

#parse_body(kind) ⇒ Object

Parses statements up to “end X”. kind should indicate the type of body being parsed: :block, :conditional, :function, or :group. Returns an array of statements.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/voodoo/parser.rb', line 167

def parse_body kind
  wrap_exceptions do
    body = []
    errors = []
    case kind
    when :function
      kind_text = 'function definition'
    else
      kind_text = kind.to_s
    end
    # Groups are allowed to contain top-level statements.
    # All other kinds aren't.
    top_level = kind == :group
    done = false
    until done
      begin
        with_position do
          statement = parse_top_level_nonvalidating
          if statement == nil
            done = true
            parse_error "End of input while inside #{kind_text}", nil

          elsif statement[0] == :end
            # Done parsing body
            done = true
          elsif kind == :conditional && statement[0] == :else
            # Done parsing body, but there is another one coming up
            body << statement
            done = true
          else
            # Should be a normal statement. Validate it, then add it to body
            begin
              if top_level
                Validator.validate_top_level statement
              else
                Validator.validate_statement statement
              end
              body << statement
            rescue Validator::ValidationError => e
              magic_word = statement[0]
              if !top_level &&
                  Validator::TOP_LEVELS.member?(magic_word) &&
                  !Validator::STATEMENTS.member?(magic_word)
                parse_error "#{magic_word} is only allowed at top-level"
              else
                parse_error e.message
              end
            end
          end
        end
      rescue => e
        # Got some kind of error. Still try to parse the rest of the body.
        errors << e
      end
    end

    # Raise error if we had just one.
    # If we had more than one, raise a MultipleErrors instance.
    if errors.length == 1
      raise errors[0]
    elsif errors.length > 1
      raise MultipleErrors.new errors
    end

    body
  end
end

#parse_escapeObject

Parses an escape sequence. This method should be called while the lookahead is the escape character (backslash). It decodes the escape sequence and returns the result as a string.



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/voodoo/parser.rb', line 239

def parse_escape
  wrap_exceptions do
    result = nil
    consume
    case lookahead
    when :eof
      parse_error "Unexpected end of input in escape sequence", nil
    when "\\", "\"", " "
      result = lookahead
      consume
    when "n"
      # \n is newline
      consume
      result = "\n"
    when "r"
      # \r is carriage return
      consume
      result = "\r"
    when "x"
      # \xXX is byte with hex value XX
      code = @input.read 2
      @column = @column + 2
      consume
      @text << code
      result = [code].pack('H2')
    when "\n"
      # \<newline> is line continuation character
      consume
      # Skip indentation of next line
      while lookahead =~ /\s/
        consume
      end
      result = ''
    else
      # Default to just passing on next character
      result = lookahead
      consume
    end
    result
  end
end

#parse_numberObject

Parses a number. This method should be called while the lookahead is the first character of the number.



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/voodoo/parser.rb', line 284

def parse_number
  wrap_exceptions do
    text = lookahead
    consume
    while lookahead =~ /\d/
      text << lookahead
      consume
    end
    text.to_i
  end
end

#parse_stringObject

Parses a string. This method should be called while the lookahead is the opening double quote.



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/voodoo/parser.rb', line 299

def parse_string
  wrap_exceptions do
    consume
    result = ''
    while true
      case lookahead
      when "\""
        consume
        break
      when "\\"
        result << parse_escape
      else
        result << lookahead
        consume
      end
    end
    result
  end
end

#parse_symbolObject

Parses a symbol. This method should be called while the lookahead is the first character of the symbol name.



322
323
324
# File 'lib/voodoo/parser.rb', line 322

def parse_symbol
  parse_symbol1 ''
end

#parse_symbol1(name) ⇒ Object

Continues parsing a symbol. name the part of the symbol that has already been parsed.



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/voodoo/parser.rb', line 328

def parse_symbol1 name
  wrap_exceptions do
    while lookahead != :eof
      case lookahead
      when "\\"
        name << parse_escape
      when /\w|-/
        name << lookahead
        consume
      when ':'
        # Colon parsed as last character of the symbol name
        name << lookahead
        consume
        break
      else
        break
      end
    end
    name.to_sym
  end
end

#parse_top_levelObject

Parses a top-level element. Returns an array containing the parts of the element.

Some examples (Voodoo code, Ruby return values in comments):

section functions
# [:section, :functions]

call foo x 12
# [:call, :foo, :x, 12]

set x add x 42
# [:set, :x, :add, :x, 42]

set-byte @x 1 10
# [:"set-byte", [:"@", :x], 1, 10]

ifeq x y
    set z equal
else
    set z not-equal
end if
# [:ifeq, [:x, :y], [[:set, :z, :equal]], [[:set, :z, :"not-equal"]]]

foo:
# [:label, :foo]

function x y
    let z add x y
    return z
end function
# [:function, [:x, :y], [:let, :z, :add, :x, :y], [:return, :z]]


152
153
154
155
156
157
158
159
160
161
162
# File 'lib/voodoo/parser.rb', line 152

def parse_top_level
  wrap_exceptions do
    @text = ''
    # Skip whitespace, comments, and empty lines
    skip_to_next_top_level

    validate_top_level do
      parse_top_level_nonvalidating
    end
  end
end