2 # Functionality for validating Voodoo code.
5 # Expressions that take two parameter
6 BINOPS = [:add, :and, :asr, :bsr, :div, :'get-byte', :'get-word',
7 :mod, :mul, :or, :rol, :ror, :shl, :shr, :sub, :xor]
8 # Expressions that take a single parameter
9 UNOPS = [:'auto-bytes', :'auto-words', :not]
10 # Expressions that take zero or more parameters
11 VARARG_EXPRS = [:call, :'tail-call']
12 # Symbols that may occur as the first word of an expression
13 EXPRS = BINOPS + UNOPS + VARARG_EXPRS
15 # Symbols that are a valid start of a statement
16 STATEMENTS = [:block, :call, :goto,
17 :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne,
18 :label, :let, :return, :'restore-frame', :'restore-locals',
19 :'save-frame', :'save-frame-and-locals', :'save-locals',
20 :set, :'set-byte', :'set-word', :'tail-call']
22 # Symbols that are valid at top-level
23 TOP_LEVELS = [:align, :byte, :export, :function, :import,
24 :section, :string, :word] + STATEMENTS
26 NTH = ['First', 'Second', 'Third']
30 # Validates an expression.
31 # Returns true if the expression is valid.
32 # Raises ValidationError if the expression is not valid.
33 def validate_expression code
34 if int_or_symbol_or_at? code
36 elsif code.respond_to? :[]
39 # binop should have 2 parameters, both of them atomic values
40 assert_n_params code, 2
41 assert_params_are_values code
42 elsif UNOPS.member? op
43 # should have a single, atomic parameter
44 assert_n_params code, 1
45 assert_params_are_values code
47 # op is not a unary or binary operator
49 when :call, :'tail-call'
50 # call should have at least 1 parameter
51 # and all parameters should be atomic values
52 assert_at_least_n_params code, 1
53 assert_params_are_values code
54 when :'get-byte', :'get-word'
55 # Should have exactly 2 parameters, both of which should be values.
56 assert_n_params code, 2
57 assert_params_are_values code
59 raise ValidationError.new("#{code[0].inspect}" +
60 " cannot start an expression",
65 # code is not an atomic value and does not respond to :[]
66 raise ValidationError.new("#{code.inspect} is not a valid expression",
71 # Validates a statement.
72 # Returns true if the statement is valid.
73 # Raises ValidationError if the statement is not valid.
74 def validate_statement code
78 code[1..-1].each {|stmt| validate_statement stmt}
81 when :call, :'tail-call'
82 validate_expression code
85 # should have exactly 1 parameter, which should be atomic
86 assert_n_params code, 1
87 assert_params_are_values code
89 when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne
90 # Should have 2 or 3 parameters.
91 # First parameter should be an array (or similar) containing
92 # two elements, both atomic.
93 # Second parameter should consist of one or more statements.
94 # Third parameter, if present, should consist of zero or more
96 # let is not allowed as a statement in either parameter, though
97 # it can be embedded in a block in either.
98 if code.length < 3 || code.length > 4
99 raise ValidationError.new("#{code[0]} takes 2 or 3 parameters",
101 elsif code[1].length != 2
102 raise ValidationError.new("#{code[0]} requires two values to" +
103 " compare in its first parameter",
105 elsif !code[1].all? {|x| int_or_symbol_or_at? x}
106 raise ValidationError.new("Values to compare should be values" +
107 " (symbols, integers, or at-exrpssions)",
110 code[2].each do |stmt|
111 validate_statement stmt
113 raise ValidationError.new("let is not allowed inside " +
119 code[3].each do |stmt|
120 validate_statement stmt
122 raise ValidationError.new("let is not allowed inside " +
132 # should have 1 parameter which should be a symbol
133 if code.length != 2 || !code[1].kind_of?(::Symbol)
134 raise ValidationError.new("label requires a single symbol" +
135 "as its parameter", code)
141 # should have at least 2 parameters
143 raise ValidationError.new("#{code[0]} requires a symbol" +
144 " and an expression", code)
145 elsif symbol? code[1]
146 # After the symbol, there should be an expression
149 validate_expression expr[0]
151 validate_expression expr
154 raise ValidationError.new("First parameter to #{code[0]} should be" +
159 # should have at least 2 parameters
161 raise ValidationError.new(
162 "#{code[0]} requires a symbol or at-expression" +
163 " followed by an expression", code)
164 elsif symbol_or_at? code[1]
165 # After the symbol/at-expr, there should be an expression
168 validate_expression expr[0]
170 validate_expression expr
173 raise ValidationError.new("First parameter to #{code[0]} should be" +
174 " a symbol or an at-expression", code)
178 # Should either have no parameters, or a single expression as
184 validate_expression code[1]
186 validate_expression code[1..-1]
189 when :'set-byte', :'set-word'
190 # Should have exactly 3 parameters, all of which should be
192 assert_n_params code, 3
193 assert_params_are_values code
195 when :'restore-frame', :'save-frame'
196 # Should have exactly 1 parameter.
197 assert_n_params code, 1
198 assert_params_are_values code
200 when :'restore-locals', :'save-frame-and-locals', :'save-locals'
201 # Should have 1 or more parameters.
202 assert_at_least_n_params code, 1
203 assert_params_are_values code
206 raise ValidationError.new("Not a valid statement: #{code.inspect}",
210 rescue ValidationError
214 rescue Exception => e
215 if code.respond_to? :[]
216 # Pass on the exception
219 raise ValidationError.new("#{code.inspect} does not respond to" +
226 # Validates a top-level directive.
227 # Returns true if the directive is valid.
228 # Raises ValidationError if the directive is not valid.
229 def validate_top_level code
233 # Should either have no parameter or a single integer parameter
234 if code.length == 1 || (code.length == 2 &&
235 code[1].kind_of?(::Integer))
238 raise ValidationError.new("align requires either a single" +
239 " integer parameter, or no parameters",
244 # Should have a single integer or symbol parameter
245 if code.length != 2 || !int_or_symbol?(code[1])
246 raise ValidationError.new("#{code[0]} requires a single" +
247 " parameter that is either an " +
248 " integer or a symbol", code)
253 when :export, :import
254 # Should have at least 1 parameter, and all parameters should
257 raise ValidationError.new("#{code[0]} requires at least " +
258 " one parameter", code)
259 elsif code[1..-1].all? {|x| x.kind_of? ::Symbol}
262 raise ValidationError.new("All parameters to #{code[0]}" +
263 " should be symbols", code)
268 # Check that formal parameters have been specified
270 raise ValidationError.new("Formal parameters should be" +
271 " specified for function",
275 # Check that all formal parameters are symbols
276 code[1].each do |formal|
277 unless formal.kind_of? ::Symbol
278 raise ValidationError.new("Formal parameter #{formal.inspect}" +
279 " should be a symbol",
285 code[2..-1].each do |stmt|
286 if stmt.respond_to?(:[]) && stmt[0] == :function
287 raise ValidationError.new("Function definitions are only " +
288 "valid at top-level", stmt)
290 validate_statement stmt
296 # Check that we have a string or a symbol
299 raise ValidationError.new("Section name should be specified",
302 unless code[1].kind_of?(::String) || code[1].kind_of?(::Symbol)
303 raise ValidationError.new("Section name should be a string" +
309 raise ValidationError.new("section directive should have only" +
310 " a single parameter",
315 # Should have a single string parameter
316 if code.length != 2 || !code[1].kind_of?(::String)
317 raise ValidationError.new("string requires a single string" +
318 " as a parameter", code)
324 if STATEMENTS.member? code[0]
325 validate_statement code
327 raise ValidationError.new("Directive #{code[0]}" +
328 " not valid at top-level",
333 rescue ValidationError
337 rescue Exception => e
338 if code.respond_to? :[]
339 # Pass on the exception
342 raise ValidationError.new("#{code.inspect} does not respond to" +
347 # If we got here, all is well
351 # Base class for errors signaled by the Validator.
352 class ValidationError < StandardError
353 def initialize message, code = nil
360 # Tests that an expression has at least n parameters. Raises a
361 # ValidationError if this is not the case.
362 def assert_at_least_n_params expr, n
365 raise ValidationError.new \
366 "#{expr[0]} should have at least one parameter"
368 raise ValidationError.new \
369 "#{expr[0]} should have at least #{n} parameters"
375 # Tests that an expression has exactly n parameters. Raises a
376 # ValidationError if this is not the case.
377 def assert_n_params expr, n
378 if expr.length != n + 1
380 raise ValidationError.new \
381 "#{expr[0]} should have exactly one parameter"
383 raise ValidationError.new \
384 "#{expr[0]} should have exactly #{n} parameters"
390 # Tests that parameters to an expression are
391 # values (integers, symbols, or at-expressions),
392 # and raises ValidationError if this is not the
394 # If ns is nil (default) all parameters should me
396 # Alternatively, ns may be a range or array containing
397 # the indices of the parameters that should be
399 def assert_params_are_values expr, ns = nil
401 ns = (1...expr.length)
404 unless int_or_symbol_or_at?(expr[i])
405 raise ValidationError.new \
406 "#{NTH[i - 1]} parameter to #{expr[0]}" +
407 " should be a value (symbol, integer, or at-expression)"
414 x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
419 x.kind_of?(::Integer) || substitution?(x)
423 x.kind_of?(::Symbol) || int?(x)
426 def int_or_symbol_or_at? x
427 int_or_symbol?(x) || at_expr?(x)
431 x.respond_to?(:length) && x.length == 2 && x[0] == :'%' &&
440 symbol?(x) || at_expr?(x)