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 # Consumes the current lookahead character
39 @lookahead = @input.getc
40 @char = @char.succ unless @lookahead == nil
44 # Returns the current lookahead character,
45 # or +nil+ when the end of the input has been reached.
48 @lookahead = @input.getc
51 @lookahead == nil ? nil : @lookahead.chr
54 # Parses a top-level element.
55 # Returns an array containing the parts of the element.
56 # Eeach element of the array is a Symbol, a String, or an
59 # For a label, returns:
60 # [:label, label_name]
62 # For a function definition, returns:
63 # [:function, [formala, formalb, ...], [statementa, statementb, ...]]
65 # For a conditional, returns:
66 # [condition, expression, [truea, trueb, ...], [falsea, falseb, ...]]
80 # Exit the loop, but only if the line wasn't empty
81 break unless words.empty?
84 while lookahead != nil && lookahead != "\n"
92 # Letter, underscore, or backslash; parse symbol
93 # Note: \w matches digites, too, so keep this case after \d
95 if words.length == 1 && is_label?(words[-1])
96 # We have a label; return it
97 return [:label, words[-1].to_s[0..-2].to_sym]
100 # Double quote; parse string
101 words << parse_string
106 raise "Invalid character (#{@lookahead}) at #{@line}:#{@char}"
110 # We have a line of input. Conditionals and function declarations
111 # must be handled specially, because they consist of more than one
114 # Nothing to parse; return nil
116 elsif words[0] == :function
117 # Function declaration. Parse function body
118 body = parse_body :function
119 [:function, words[1..-1], body]
120 elsif is_conditional?(words[0])
121 # Conditional. Parse true part
122 true_body = parse_body :conditional
123 # Parse false part, if present
124 if true_body[-1] == :else
125 true_body = true_body[0..-2]
126 false_body = parse_body :conditional
130 [words[0], words[1..-1], true_body, false_body]
132 # Statement or data declaration; simply return it
137 # Parses a body for a function or a conditional
142 kind_text = 'function definition'
144 kind_text = kind.to_s
147 statement = parse_top_level
149 raise "End of input while inside #{kind_text}"
150 elsif statement[0] == :end
153 elsif kind == :conditional && statement[0] == :else
154 # Done parsing body, but there is another one coming up
158 # Parsed a statement. Add it to body.
165 # Parses an escape sequence.
166 # This method should be called while the lookahead is the escape
167 # character (backslash). It decodes the escape sequence and returns
168 # the result as a string.
174 raise "Unexpected end of input in escape sequence"
183 # \r is carriage return
187 # \xXX is byte with hex value XX
191 result = [code].pack('H2')
193 # \<newline> is line continuation character
195 # Skip indentation of next line
196 while lookahead =~ /\s/
201 # Default to just passing on next character
209 # This method should be called while the lookahead is the first
210 # character of the number.
214 while lookahead =~ /\d/
222 # This method should be called while the lookahead is the opening
233 result << parse_escape
243 # This method should be called while the lookahead is the first
244 # character of the symbol name.
255 # Colon parsed as last character of the symbol name
266 # Tests if a symbol is a label
268 symbol.to_s[-1] == ?:
271 # Tests if a symbol is a conditional starter
272 def is_conditional? symbol
273 [:ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne].member? symbol