2 # Functionality for validating Voodoo code.
5 # Expressions that take two arguments
6 BINOPS = [:add, :and, :asr, :bsr, :div, :mod, :mul, :or,
7 :rol, :ror, :shl, :shr, :sub, :xor]
8 # Expressions that take zero or more parameters
9 VARARG_EXPRS = [:call, :'tail-call']
10 # Symbols that may occur as the first word of an expression
11 EXPRS = BINOPS + VARARG_EXPRS + [:not]
13 # Symbols that are a valid start of a statement
14 STATEMENTS = [:block, :call,
15 :'get-byte', :'get-word', :goto,
16 :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne,
17 :label, :let, :return, :set,
18 :'set-byte', :'set-word', :'tail-call']
20 # Symbols that are valid at top-level
21 TOP_LEVELS = [:align, :byte, :export, :function, :import,
22 :section, :string, :word] + STATEMENTS
26 # Validates an expression.
27 # Returns true if the expression is valid.
28 # Raises ValidationError if the expression is not valid.
29 def validate_expression code
30 if int_or_symbol_or_at? code
32 elsif code.respond_to? :[]
33 if BINOPS.member? code[0]
34 # binop must have 2 parameters, both of them atomic values
36 if int_or_symbol_or_at? code[1]
37 if int_or_symbol_or_at? code[2]
40 raise ValidationError.new("#{code[2].inspect} should be" +
41 "a Symbol or an Integer", code)
44 raise ValidationError.new("#{code[1].inspect} should be" +
45 "a Symbol or an Integer", code)
48 raise ValidationError.new("#{code[0]} should get exactly 2" +
52 # code[0] is not a binop
54 when :call, :'tail-call'
55 # call must have at least 1 parameter
56 # and all parameters must be atomic values
58 raise ValidationError.new("#{code[0]} requires at least" +
59 " one parameter", code)
61 code[1..-1].each do |x|
62 unless int_or_symbol_or_at? x
63 raise ValidationError.new("All parameters to #{code[0]}" +
64 " should be atomic values" +
65 " (symbols or integers)",
72 when :'get-byte', :'get-word'
73 # Must have exactly 2 parameters.
74 # First parameter must be a symbol.
75 # Second parameter must be an atomic value.
77 raise ValidationError.new("#{code[0]} should have exactly" +
78 " two parameters", code)
79 elsif !code[1].kind_of?(::Symbol)
80 raise ValidationError.new("First parameter to #{code[0]} must" +
82 elsif !int_or_symbol_or_at?(code[2])
83 raise ValidationError.new("Second parameter to #{code[0]} must" +
84 " be a symbol or an integer", code)
90 # must have a single, atomic parameter
92 raise ValidationError.new("not takes exactly one parameter",
94 elsif int_or_symbol_or_at? code[1]
97 raise ValidationError.new("Parameter to not must be a" +
98 " symbol or integer", code)
102 raise ValidationError.new("#{code[0].inspect}" +
103 " cannot start an expression",
108 # code is not an atomic value and does not respond to :[]
109 raise ValidationError.new("#{code.inspect} is not a valid expression",
114 # Validates a statement.
115 # Returns true if the statement is valid.
116 # Raises ValidationError if the statement is not valid.
117 def validate_statement code
121 code[1..-1].each {|stmt| validate_statement stmt}
124 when :call, :'tail-call'
125 validate_expression code
128 # must have exactly 1 parameter, which must be atomic
130 raise ValidationError.new("goto should have exactly" +
131 " one parameter", code)
132 elsif int_or_symbol_or_at? code[1]
135 raise ValidationError.new("parameter to goto must be a" +
136 " label or an integer", code)
139 when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne
140 # Must have 2 or 3 parameters.
141 # First parameter must be an array (or similar) containing
142 # two elements, both atomic.
143 # Second parameter must consist of one or more statements.
144 # Third parameter, if present, must consist of zero or more
146 # let is not allowed as a statement in either parameter, though
147 # it can be embedded in a block in either.
148 if code.length < 3 || code.length > 4
149 raise ValidationError.new("#{code[0]} takes 2 or 3 parameters",
151 elsif code[1].length != 2
152 raise ValidationError.new("#{code[0]} requires two values to" +
153 " compare in its first parameter",
155 elsif !code[1].all? {|x| int_or_symbol_or_at? x}
156 raise ValidationError.new("Values to compare must be atomic" +
157 " (symbols or integers)", code)
159 code[2].each do |stmt|
160 validate_statement stmt
162 raise ValidationError.new("let is not allowed inside " +
168 code[3].each do |stmt|
169 validate_statement stmt
171 raise ValidationError.new("let is not allowed inside " +
181 # must have 1 parameter which must be a symbol
182 if code.length != 2 || !code[1].kind_of?(::Symbol)
183 raise ValidationError.new("label requires a single symbol" +
184 "as its parameter", code)
190 # must have at least 2 parameters
192 raise ValidationError.new("#{code[0]} requires a symbol" +
193 " and an expression", code)
194 elsif code[1].kind_of? ::Symbol
195 # After the symbol, there must be an expression
198 validate_expression expr[0]
200 validate_expression expr
203 raise ValidationError.new("First parameter to #{code[0]} must be" +
208 # Must either have no parameters, or a single expression as
214 validate_expression code[1]
216 validate_expression code[1..-1]
219 when :'set-byte', :'set-word'
220 # Must have exactly 3 parameters.
221 # First parameter must be a symbol.
222 # Second and third parameters must be atomic values.
224 raise ValidationError.new("#{code[0]} should have exactly" +
225 " three parameters", code)
226 elsif !code[1].kind_of?(::Symbol)
227 raise ValidationError.new("First parameter to #{code[0]} must" +
228 " be a symbol", code)
229 elsif !int_or_symbol_or_at?(code[2])
230 raise ValidationError.new("Second parameter to #{code[0]} must" +
231 " be a symbol or an integer", code)
232 elsif !int_or_symbol_or_at?(code[3])
233 raise ValidationError.new("Third parameter to #{code[0]} must" +
234 " be a symbol or an integer", code)
240 raise ValidationError.new("Not a valid statement: #{code.inspect}",
244 rescue ValidationError
248 rescue Exception => e
249 if code.respond_to? :[]
250 # Pass on the execption
253 raise ValidationError.new("#{code.inspect} does not respond to" +
260 # Validates a top-level directive.
261 # Returns true if the directive is valid.
262 # Raises ValidationError if the directive is not valid.
263 def validate_toplevel code
267 # Must either have no parameter or a single integer parameter
268 if code.length == 1 || (code.length == 2 &&
269 code[1].kind_of?(::Integer))
272 raise ValidationError.new("align requires either a single" +
273 " integer parameter, or no parameters",
278 # Must have a single integer or symbol parameter
279 if code.length != 2 || !int_or_symbol_or_at?(code[1])
280 raise ValidationError.new("#{code[0]} requires a single" +
281 " parameter that is either an " +
282 " integer or a symbol", code)
287 when :export, :import
288 # Must have at least 1 parameter, and all parameters must
291 raise ValidationError.new("#{code[0]} requires at least " +
292 " one parameter", code)
293 elsif code[1..-1].all? {|x| x.kind_of? ::Symbol}
296 raise ValidationError.new("All parameters to #{code[0]}" +
297 " should be symbols", code)
302 # Check that formal parameters have been specified
304 raise ValidationError.new("Formal parameters should be" +
305 " specified for function",
309 # Check that all formal parameters are symbols
310 code[1].each do |formal|
311 unless formal.kind_of? ::Symbol
312 raise ValidationError.new("Formal parameter #{formal.inspect}" +
313 " should be a symbol",
319 code[2..-1].each {|stmt| validate_statement stmt}
323 # Check that we have a string or a symbol
326 raise ValidationError.new("Section name should be specified",
329 unless code[1].kind_of?(::String) || code[1].kind_of?(::Symbol)
330 raise ValidationError.new("Section name should be a String" +
336 raise ValidationError.new("section directive should have only" +
337 " a single parameter",
342 # Must have a single string parameter
343 if code.length != 2 || !code[1].kind_of?(::String)
344 raise ValidationError.new("string requires a single string" +
345 " as a parameter", code)
351 if STATEMENTS.member? code[0]
352 validate_statement code
354 raise ValidationError.new("Directive #{code[0].inspect}" +
355 " not valid at top-level;" +
356 " valid directives are: " +
357 TOP_LEVELS.join(', '),
362 rescue ValidationError
366 rescue Exception => e
367 if code.respond_to? :[]
368 # Pass on the exception
371 raise ValidationError.new("#{code.inspect} does not respond to" +
376 # If we got here, all is well
380 # Base class for errors signalled by the Validator.
381 class ValidationError < StandardError
382 def initialize message, code = nil
390 x.kind_of?(::Symbol) || x.kind_of?(::Integer)
393 def int_or_symbol_or_at? x
395 (x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
396 int_or_symbol?(x[1]))