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, :set,
19 :'set-byte', :'set-word', :'tail-call']
21 # Symbols that are valid at top-level
22 TOP_LEVELS = [:align, :byte, :export, :function, :import,
23 :section, :string, :word] + STATEMENTS
25 NTH = ['First', 'Second', 'Third']
29 # Validates an expression.
30 # Returns true if the expression is valid.
31 # Raises ValidationError if the expression is not valid.
32 def validate_expression code
33 if int_or_symbol_or_at? code
35 elsif code.respond_to? :[]
38 # binop should have 2 parameters, both of them atomic values
39 assert_n_params code, 2
40 assert_params_are_values code
41 elsif UNOPS.member? op
42 # should have a single, atomic parameter
43 assert_n_params code, 1
44 assert_params_are_values code
46 # op is not a unary or binary operator
48 when :call, :'tail-call'
49 # call should have at least 1 parameter
50 # and all parameters should be atomic values
51 assert_at_least_n_params code, 1
52 assert_params_are_values code
53 when :'get-byte', :'get-word'
54 # Should have exactly 2 parameters, both of which should be values.
55 assert_n_params code, 2
56 assert_params_are_values code
58 raise ValidationError.new("#{code[0].inspect}" +
59 " cannot start an expression",
64 # code is not an atomic value and does not respond to :[]
65 raise ValidationError.new("#{code.inspect} is not a valid expression",
70 # Validates a statement.
71 # Returns true if the statement is valid.
72 # Raises ValidationError if the statement is not valid.
73 def validate_statement code
77 code[1..-1].each {|stmt| validate_statement stmt}
80 when :call, :'tail-call'
81 validate_expression code
84 # should have exactly 1 parameter, which should be atomic
85 assert_n_params code, 1
86 assert_params_are_values code
88 when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne
89 # Should have 2 or 3 parameters.
90 # First parameter should be an array (or similar) containing
91 # two elements, both atomic.
92 # Second parameter should consist of one or more statements.
93 # Third parameter, if present, should consist of zero or more
95 # let is not allowed as a statement in either parameter, though
96 # it can be embedded in a block in either.
97 if code.length < 3 || code.length > 4
98 raise ValidationError.new("#{code[0]} takes 2 or 3 parameters",
100 elsif code[1].length != 2
101 raise ValidationError.new("#{code[0]} requires two values to" +
102 " compare in its first parameter",
104 elsif !code[1].all? {|x| int_or_symbol_or_at? x}
105 raise ValidationError.new("Values to compare should be values" +
106 " (symbols, integers, or at-exrpssions)",
109 code[2].each do |stmt|
110 validate_statement stmt
112 raise ValidationError.new("let is not allowed inside " +
118 code[3].each do |stmt|
119 validate_statement stmt
121 raise ValidationError.new("let is not allowed inside " +
131 # should have 1 parameter which should be a symbol
132 if code.length != 2 || !code[1].kind_of?(::Symbol)
133 raise ValidationError.new("label requires a single symbol" +
134 "as its parameter", code)
140 # should have at least 2 parameters
142 raise ValidationError.new("#{code[0]} requires a symbol" +
143 " and an expression", code)
144 elsif symbol? code[1]
145 # After the symbol, there should be an expression
148 validate_expression expr[0]
150 validate_expression expr
153 raise ValidationError.new("First parameter to #{code[0]} should be" +
158 # should have at least 2 parameters
160 raise ValidationError.new(
161 "#{code[0]} requires a symbol or at-expression" +
162 " followed by an expression", code)
163 elsif symbol_or_at? code[1]
164 # After the symbol/at-expr, there should be an expression
167 validate_expression expr[0]
169 validate_expression expr
172 raise ValidationError.new("First parameter to #{code[0]} should be" +
173 " a symbol or an at-expression", code)
177 # Should either have no parameters, or a single expression as
183 validate_expression code[1]
185 validate_expression code[1..-1]
188 when :'set-byte', :'set-word'
189 # Should have exactly 3 parameters, all of which should be
191 assert_n_params code, 3
192 assert_params_are_values code
195 raise ValidationError.new("Not a valid statement: #{code.inspect}",
199 rescue ValidationError
203 rescue Exception => e
204 if code.respond_to? :[]
205 # Pass on the exception
208 raise ValidationError.new("#{code.inspect} does not respond to" +
215 # Validates a top-level directive.
216 # Returns true if the directive is valid.
217 # Raises ValidationError if the directive is not valid.
218 def validate_top_level code
222 # Should either have no parameter or a single integer parameter
223 if code.length == 1 || (code.length == 2 &&
224 code[1].kind_of?(::Integer))
227 raise ValidationError.new("align requires either a single" +
228 " integer parameter, or no parameters",
233 # Should have a single integer or symbol parameter
234 if code.length != 2 || !int_or_symbol?(code[1])
235 raise ValidationError.new("#{code[0]} requires a single" +
236 " parameter that is either an " +
237 " integer or a symbol", code)
242 when :export, :import
243 # Should have at least 1 parameter, and all parameters should
246 raise ValidationError.new("#{code[0]} requires at least " +
247 " one parameter", code)
248 elsif code[1..-1].all? {|x| x.kind_of? ::Symbol}
251 raise ValidationError.new("All parameters to #{code[0]}" +
252 " should be symbols", code)
257 # Check that formal parameters have been specified
259 raise ValidationError.new("Formal parameters should be" +
260 " specified for function",
264 # Check that all formal parameters are symbols
265 code[1].each do |formal|
266 unless formal.kind_of? ::Symbol
267 raise ValidationError.new("Formal parameter #{formal.inspect}" +
268 " should be a symbol",
274 code[2..-1].each do |stmt|
275 if stmt.respond_to?(:[]) && stmt[0] == :function
276 raise ValidationError.new("Function definitions are only " +
277 "valid at top-level", stmt)
279 validate_statement stmt
285 # Check that we have a string or a symbol
288 raise ValidationError.new("Section name should be specified",
291 unless code[1].kind_of?(::String) || code[1].kind_of?(::Symbol)
292 raise ValidationError.new("Section name should be a string" +
298 raise ValidationError.new("section directive should have only" +
299 " a single parameter",
304 # Should have a single string parameter
305 if code.length != 2 || !code[1].kind_of?(::String)
306 raise ValidationError.new("string requires a single string" +
307 " as a parameter", code)
313 if STATEMENTS.member? code[0]
314 validate_statement code
316 raise ValidationError.new("Directive #{code[0]}" +
317 " not valid at top-level",
322 rescue ValidationError
326 rescue Exception => e
327 if code.respond_to? :[]
328 # Pass on the exception
331 raise ValidationError.new("#{code.inspect} does not respond to" +
336 # If we got here, all is well
340 # Base class for errors signaled by the Validator.
341 class ValidationError < StandardError
342 def initialize message, code = nil
349 # Tests that an expression has at least n parameters. Raises a
350 # ValidationError if this is not the case.
351 def assert_at_least_n_params expr, n
354 raise ValidationError.new \
355 "#{expr[0]} should have at least one parameter"
357 raise ValidationError.new \
358 "#{expr[0]} should have at least #{n} parameters"
364 # Tests that an expression has exactly n parameters. Raises a
365 # ValidationError if this is not the case.
366 def assert_n_params expr, n
367 if expr.length != n + 1
369 raise ValidationError.new \
370 "#{expr[0]} should have exactly one parameter"
372 raise ValidationError.new \
373 "#{expr[0]} should have exactly #{n} parameters"
379 # Tests that parameters to an expression are
380 # values (integers, symbols, or at-expressions),
381 # and raises ValidationError if this is not the
383 # If ns is nil (default) all parameters should me
385 # Alternatively, ns may be a range or array containing
386 # the indices of the parameters that should be
388 def assert_params_are_values expr, ns = nil
390 ns = (1...expr.length)
393 unless int_or_symbol_or_at?(expr[i])
394 raise ValidationError.new \
395 "#{NTH[i - 1]} parameter to #{expr[0]}" +
396 " should be a value (symbol, integer, or at-expression)"
403 x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
408 x.kind_of?(::Symbol) || x.kind_of?(::Integer)
411 def int_or_symbol_or_at? x
412 int_or_symbol?(x) || at_expr?(x)
420 symbol?(x) || at_expr?(x)