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

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.



27
28
29
30
31
32
33
34
# File 'lib/voodoo/parser.rb', line 27

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 a body for a function or a conditional



161
162
163
164
165
166
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
# File 'lib/voodoo/parser.rb', line 161

def parse_body kind
  wrap_exceptions do
    body = []
    errors = []
    case kind
    when :function
      kind_text = 'function definition'
    else
      kind_text = kind.to_s
    end
    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
            if statement[0] == :function
              parse_error "Function definitions are only allowed at top-level"
            end
            begin
              Validator.validate_statement statement
              body << statement
            rescue Validator::ValidationError => e
              parse_error e.message
            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.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/voodoo/parser.rb', line 222

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.



267
268
269
270
271
272
273
274
275
276
277
# File 'lib/voodoo/parser.rb', line 267

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.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/voodoo/parser.rb', line 282

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.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/voodoo/parser.rb', line 305

def parse_symbol
  wrap_exceptions do
    name = ''
    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]]


148
149
150
151
152
153
154
155
156
157
158
# File 'lib/voodoo/parser.rb', line 148

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