Changed parser to return symbols as symbols and numbers as numbers
[voodoo-lang.git] / lib / ruby / voodoo / parser.rb
blob7cd25f59208ab8340c68bd9d472d34f200f8a67f
1 module Voodoo
2   # Voodoo parser
3   class Parser
4     # Creates a parser using the specified object as input.
5     # The input object must support a method +getc+, which must
6     # return the next character of the input, or +nil+ to indicate
7     # the end of the input has been reached.
8     def initialize input
9       @input = input
10       @line = 1
11       @char = 0
12       @lookahead = nil
13     end
15     # Consumes the current lookahead character
16     def consume
17       old = @lookahead
18       if old == "\n"
19         @line = @line.succ
20         @char = 0
21       end
22       @lookahead = @input.getc
23       @char = @char.succ unless @lookahead == nil
24       old
25     end
27     # Returns the current lookahead character,
28     # or +nil+ when the end of the input has been reached.
29     def lookahead
30       if @lookahead == nil
31         @lookahead = @input.getc
32         @char = @char.succ
33       end
34       @lookahead == nil ? nil : @lookahead.chr
35     end
37     # Parses a line of input.
38     # Returns an array containing the parts of the input line.
39     # Eeach element of the array is a Symbol, a String, or an
40     # Integer.
41     def parse_line
42       words = []
43       word = ""
45       while true
46         case lookahead
47         when nil
48           # End of input
49           break
50         when "\n"
51           # Newline; we're done!
52           consume
53           break
54         when "#"
55           # Skip comment
56           while lookahead != nil && lookahead != "\n"
57             word << lookahead
58             consume
59           end
60         when /\d|-/
61           # Digit; parse number
62           words << parse_number
63         when /\w|\\/
64           # Letter, underscore, or backslash; parse symbol
65           # Note: \w matches digites, too, so keep this case after \d
66           words << parse_symbol
67         when "\""
68           # Double quote; parse string
69           words << parse_string
70         when /\s/
71           # Skip whitespace
72           consume
73         else
74           raise "Invalid character (#{@lookahead}) at #{@line}:#{@char}"
75         end
76       end
77       (!words.empty? || @lookahead) ? words : nil
78     end
80     # Parses an escape sequence.
81     # This method should be called while the lookahead is the escape
82     # character (backslash). It decodes the escape sequence and returns
83     # the result as a string.
84     def parse_escape
85       result = nil
86       consume
87       case lookahead
88       when nil
89         raise "Unexpected end of input in escape sequence"
90       when "\\", "\"", " "
91         result = lookahead
92         consume
93       when "n"
94         # \n is newline
95         consume
96         result = "\n"
97       when "r"
98         # \r is carriage return
99         consume
100         result = "\r"
101       when "x"
102         # \xXX is byte with hex value XX
103         code = @input.read 2
104         @char = @char + 2
105         consume
106         result = [code].pack('H2')
107       when "\n"
108         # \<newline> is line continuation character
109         consume
110         # Skip indentation of next line
111         while lookahead =~ /\s/
112           consume
113         end
114         result = ''
115       else
116         # Default to just passing on next character
117         result = lookahead
118         consume
119       end
120       result
121     end
123     # Parses a number.
124     # This method should be called while the lookahead is the first
125     # character of the number.
126     def parse_number
127       text = lookahead
128       consume
129       while lookahead =~ /\d/
130         text << lookahead
131         consume
132       end
133       text.to_i
134     end
136     # Parses a string.
137     # This method should be called while the lookahead is the opening
138     # double quote.
139     def parse_string
140       consume
141       result = ''
142       while true
143         case lookahead
144         when "\""
145           consume
146           break
147         when "\\"
148           result << parse_escape
149         else
150           result << lookahead
151           consume
152         end
153       end
154       result
155     end
157     # Parses a symbol.
158     # This method should be called while the lookahead is the first
159     # character of the symbol name.
160     def parse_symbol
161       name = ''
162       while true
163         case lookahead
164         when "\\"
165           name << parse_escape
166         when /\w|-/
167           name << lookahead
168           consume
169         when ':'
170           # Colon parsed as last character of the symbol name
171           name << lookahead
172           consume
173           break
174         else
175           break
176         end
177       end
178       name.to_sym
179     end
180   end