3 # The parser reads Voodoo[http://inglorion.net/documents/designs/voodoo/]
4 # source code and turns it into Ruby[http://www.ruby-lang.org/] objects.
6 # The public interface to Parser consists of the methods #new and
11 # require 'voodoo/parser'
13 # File.open('example.voo') do |infile|
14 # parser = Voodoo::Parser.new infile
16 # while (element = parser.parse_top_level)
17 # puts element.inspect
21 # Creates a parser using the specified object as input.
22 # The input object must support a method +getc+, which must
23 # return the next character of the input, or +nil+ to indicate
24 # the end of the input has been reached.
32 attr_reader :line, :char
34 # Consumes the current lookahead character
41 @lookahead = @input.getc
42 @lookahead = :eof if @lookahead == nil
43 @char = @char.succ unless @lookahead == :eof
47 # Returns the current lookahead character,
48 # or +nil+ when the end of the input has been reached.
51 @lookahead = @input.getc
62 # Parses a top-level element.
63 # Returns an array containing the parts of the element.
64 # Eeach element of the array is a Symbol, a String, or an
67 # For a label, returns:
68 # [:label, label_name]
70 # For a function definition, returns:
71 # [:function, [formala, formalb, ...], statementa, statementb, ...]
73 # For a conditional, returns:
74 # [condition, expression, [truea, trueb, ...], [falsea, falseb, ...]]
88 # Exit the loop, but only if the line wasn't empty
89 break unless words.empty?
92 while lookahead != :eof && lookahead != "\n"
100 # Letter, underscore, or backslash; parse symbol
101 # Note: \w matches digites, too, so keep this case after \d
102 words << parse_symbol
103 if words.length == 1 && is_label?(words[-1])
104 # We have a label; return it
105 return [:label, words[-1].to_s[0..-2].to_sym]
108 # Double quote; parse string
109 words << parse_string
114 # Parse at-expression.
115 # '@' must be followed by a number or symbol.
123 raise "Invalid character (#{@lookahead.chr.inspect})" +
124 " at #{@line}:#{@char}; expecting number or symbol"
126 words << [:'@', expr]
128 raise "Invalid character (#{@lookahead.chr.inspect})" +
129 " at #{@line}:#{@char}"
133 # We have a line of input. Conditionals and function declarations
134 # must be handled specially, because they consist of more than one
137 # Nothing to parse; return nil
139 elsif words[0] == :function
140 # Function declaration. Parse function body
141 body = parse_body :function
142 [:function, words[1..-1]] + body
143 elsif is_conditional?(words[0])
144 parse_conditional1 words[0], words[1..-1]
145 elsif words[0] == :block
146 body = parse_body :block
149 # Statement or data declaration; simply return it
154 # Parses a body for a function or a conditional
159 kind_text = 'function definition'
161 kind_text = kind.to_s
164 statement = parse_top_level
166 raise "End of input while inside #{kind_text}"
167 elsif statement[0] == :end
170 elsif kind == :conditional && statement[0] == :else
171 # Done parsing body, but there is another one coming up
175 # Parsed a statement. Add it to body.
182 # Parses an escape sequence.
183 # This method should be called while the lookahead is the escape
184 # character (backslash). It decodes the escape sequence and returns
185 # the result as a string.
191 raise "Unexpected end of input in escape sequence"
200 # \r is carriage return
204 # \xXX is byte with hex value XX
208 result = [code].pack('H2')
210 # \<newline> is line continuation character
212 # Skip indentation of next line
213 while lookahead =~ /\s/
218 # Default to just passing on next character
226 # This method should be called while the lookahead is the first
227 # character of the number.
231 while lookahead =~ /\d/
239 # This method should be called while the lookahead is the opening
250 result << parse_escape
260 # This method should be called while the lookahead is the first
261 # character of the symbol name.
272 # Colon parsed as last character of the symbol name
283 # Tests if a symbol is a label
285 symbol.to_s[-1] == ?:
288 # Tests if a symbol is a conditional starter
289 def is_conditional? symbol
290 [:ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne].member? symbol
298 # Parses a conditional statement
299 def parse_conditional1 condition, operands
300 # Parse first clause and condition for next clause
301 consequent, next_condition = split_if_clause parse_body(:conditional)
302 if next_condition == nil
305 elsif next_condition == :else
306 # Next clause is else without if
307 alternative = parse_body :conditional
309 # Next clause is else with if
310 alternative = [parse_conditional1(next_condition[0],
313 [condition, operands, consequent, alternative]
316 # Splits a parsed if-clause into two parts:
317 # 1. The list of statements making up the clause proper
318 # 2. The condition for the next clause:
319 # - If there is no next clause, nil
320 # - If the next clause is introduced by else without a condition, :else
321 # - If the next clause is introduced by else iflt x y, [:iflt [:x, :y]]
322 # - And so on for other if.. instances
323 def split_if_clause clause
325 if last.respond_to?(:[]) && last.length > 0 && last[0] == :else
326 clause = clause[0..-2]
329 [clause, [last[1], last[2..-1]]]