2 # Functionality for validating Voodoo code.
4 # See validate_top_level, validate_statement, and validate_expression.
7 # Expressions that take two parameters.
8 BINOPS = [:add, :and, :asr, :bsr, :div, :'get-byte', :'get-word',
9 :mod, :mul, :or, :rol, :ror, :shl, :shr, :sub, :xor]
10 # Expressions that take a single parameter.
11 UNOPS = [:'auto-bytes', :'auto-words', :not]
12 # Expressions that take zero or more parameters.
13 VARARG_EXPRS = [:call, :'tail-call']
14 # Symbols that may occur as the first word of an expression.
15 EXPRS = BINOPS + UNOPS + VARARG_EXPRS
17 # Symbols that are a valid start of a statement.
18 STATEMENTS = [:byte, :block, :call, :goto, :ifeq, :ifge,
19 :ifgt, :ifle, :iflt, :ifne, :label, :let, :return,
20 :'restore-frame', :'restore-locals', :'save-frame',
21 :'save-frame-and-locals', :'save-locals', :set,
22 :'set-byte', :'set-word', :string, :'tail-call', :word]
24 # Symbols that are valid at top-level.
25 TOP_LEVELS = [:align, :export, :function, :group, :import, :section] +
28 # Maps indices 0, 1, 2 to English words.
29 NTH = ['First', 'Second', 'Third']
33 # Validates an expression.
34 # Returns true if the expression is valid.
35 # Raises ValidationError if the expression is not valid.
36 def validate_expression code
37 if int_or_symbol_or_at? code
39 elsif code.respond_to? :[]
42 # binop should have 2 parameters, both of them atomic values
43 assert_n_params code, 2
44 assert_params_are_values code
45 elsif UNOPS.member? op
46 # should have a single, atomic parameter
47 assert_n_params code, 1
48 assert_params_are_values code
50 # op is not a unary or binary operator
52 when :call, :'tail-call'
53 # call should have at least 1 parameter
54 # and all parameters should be atomic values
55 assert_at_least_n_params code, 1
56 assert_params_are_values code
57 when :'get-byte', :'get-word'
58 # Should have exactly 2 parameters, both of which should be values.
59 assert_n_params code, 2
60 assert_params_are_values code
62 raise ValidationError.new("#{code[0].inspect}" +
63 " cannot start an expression",
68 # code is not an atomic value and does not respond to :[]
69 raise ValidationError.new("#{code.inspect} is not a valid expression",
74 # Validates a statement.
75 # Returns true if the statement is valid.
76 # Raises ValidationError if the statement is not valid.
77 def validate_statement code
81 code[1..-1].each {|stmt| validate_statement stmt}
85 # Should have a single integer or symbol parameter
86 if code.length != 2 || !int_or_symbol?(code[1])
87 raise ValidationError.new("#{code[0]} requires a single" +
88 " parameter that is either an " +
89 " integer or a symbol", code)
94 when :call, :'tail-call'
95 validate_expression code
98 # should have exactly 1 parameter, which should be atomic
99 assert_n_params code, 1
100 assert_params_are_values code
102 when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne
103 # Should have 2 or 3 parameters.
104 # First parameter should be an array (or similar) containing
105 # two elements, both atomic.
106 # Second parameter should consist of one or more statements.
107 # Third parameter, if present, should consist of zero or more
109 # let is not allowed as a statement in either parameter, though
110 # it can be embedded in a block in either.
111 if code.length < 3 || code.length > 4
112 raise ValidationError.new("#{code[0]} takes 2 or 3 parameters",
114 elsif code[1].length != 2
115 raise ValidationError.new("#{code[0]} requires two values to" +
116 " compare in its first parameter",
118 elsif !code[1].all? {|x| int_or_symbol_or_at? x}
119 raise ValidationError.new("Values to compare should be values" +
120 " (symbols, integers, or at-exrpssions)",
123 code[2].each do |stmt|
124 validate_statement stmt
126 raise ValidationError.new("let is not allowed inside " +
132 code[3].each do |stmt|
133 validate_statement stmt
135 raise ValidationError.new("let is not allowed inside " +
145 # should have 1 parameter which should be a symbol
146 if code.length != 2 || !code[1].kind_of?(::Symbol)
147 raise ValidationError.new("label requires a single symbol" +
148 "as its parameter", code)
154 # should have at least 2 parameters
156 raise ValidationError.new("#{code[0]} requires a symbol" +
157 " and an expression", code)
158 elsif symbol? code[1]
159 # After the symbol, there should be an expression
162 validate_expression expr[0]
164 validate_expression expr
167 raise ValidationError.new("First parameter to #{code[0]} should be" +
172 # should have at least 2 parameters
174 raise ValidationError.new(
175 "#{code[0]} requires a symbol or at-expression" +
176 " followed by an expression", code)
177 elsif symbol_or_at? code[1]
178 # After the symbol/at-expr, there should be an expression
181 validate_expression expr[0]
183 validate_expression expr
186 raise ValidationError.new("First parameter to #{code[0]} should be" +
187 " a symbol or an at-expression", code)
191 # Should either have no parameters, or a single expression as
197 validate_expression code[1]
199 validate_expression code[1..-1]
202 when :'set-byte', :'set-word'
203 # Should have exactly 3 parameters, all of which should be
205 assert_n_params code, 3
206 assert_params_are_values code
208 when :'restore-frame', :'save-frame'
209 # Should have exactly 1 parameter.
210 assert_n_params code, 1
211 assert_params_are_values code
213 when :'restore-locals', :'save-frame-and-locals', :'save-locals'
214 # Should have 1 or more parameters.
215 assert_at_least_n_params code, 1
216 assert_params_are_values code
219 # Should have a single string parameter
220 if code.length != 2 || !code[1].kind_of?(::String)
221 raise ValidationError.new("string requires a single string" +
222 " as a parameter", code)
228 if TOP_LEVELS.member?(code[0]) && !STATEMENTS.member?(code[0])
229 raise ValidationError.new("#{code[0]} is only valid at top-level", code)
232 raise ValidationError.new("Not a valid statement: #{code.inspect}",
237 rescue ValidationError
241 rescue Exception => e
242 if code.respond_to? :[]
243 # Pass on the exception
246 raise ValidationError.new("#{code.inspect} does not respond to" +
253 # Validates a top-level incantation.
254 # Returns true if the incantation is valid.
255 # Raises ValidationError if the incantation is not valid.
256 def validate_top_level code
260 # Should either have no parameter or a single integer parameter
261 if code.length == 1 || (code.length == 2 &&
262 code[1].kind_of?(::Integer))
265 raise ValidationError.new("align requires either a single" +
266 " integer parameter, or no parameters",
270 when :export, :import
271 # Should have at least 1 parameter, and all parameters should
274 raise ValidationError.new("#{code[0]} requires at least " +
275 " one parameter", code)
276 elsif code[1..-1].all? {|x| x.kind_of? ::Symbol}
279 raise ValidationError.new("All parameters to #{code[0]}" +
280 " should be symbols", code)
285 # Check that formal parameters have been specified
287 raise ValidationError.new("Formal parameters should be" +
288 " specified for function",
292 # Check that all formal parameters are symbols
293 code[1].each do |formal|
294 unless formal.kind_of? ::Symbol
295 raise ValidationError.new("Formal parameter #{formal.inspect}" +
296 " should be a symbol",
302 code[2..-1].each { |stmt| validate_statement stmt }
306 code[1..-1].each { |stmt| validate_top_level stmt }
310 # Check that we have a string or a symbol
313 raise ValidationError.new("Section name should be specified",
316 unless code[1].kind_of?(::String) || code[1].kind_of?(::Symbol)
317 raise ValidationError.new("Section name should be a string" +
323 raise ValidationError.new("section incantation should have only" +
324 " a single parameter",
329 if STATEMENTS.member? code[0]
330 validate_statement code
332 raise ValidationError.new("Incantation #{code[0]}" +
333 " not valid at top-level",
338 rescue ValidationError
342 rescue Exception => e
343 if code.respond_to? :[]
344 # Pass on the exception
347 raise ValidationError.new("#{code.inspect} does not respond to" +
352 # If we got here, all is well
356 # Base class for errors signaled by the Validator.
357 class ValidationError < StandardError
358 def initialize message, code = nil
365 # Tests that an expression has at least n parameters. Raises a
366 # ValidationError if this is not the case.
367 def assert_at_least_n_params expr, n
370 raise ValidationError.new \
371 "#{expr[0]} should have at least one parameter"
373 raise ValidationError.new \
374 "#{expr[0]} should have at least #{n} parameters"
380 # Tests that an expression has exactly n parameters. Raises a
381 # ValidationError if this is not the case.
382 def assert_n_params expr, n
383 if expr.length != n + 1
385 raise ValidationError.new \
386 "#{expr[0]} should have exactly one parameter"
388 raise ValidationError.new \
389 "#{expr[0]} should have exactly #{n} parameters"
395 # Tests that parameters to an expression are
396 # values (integers, symbols, or at-expressions),
397 # and raises ValidationError if this is not the
399 # If ns is nil (default) all parameters should me
401 # Alternatively, ns may be a range or array containing
402 # the indices of the parameters that should be
404 def assert_params_are_values expr, ns = nil
406 ns = (1...expr.length)
409 unless int_or_symbol_or_at?(expr[i])
410 raise ValidationError.new \
411 "#{NTH[i - 1]} parameter to #{expr[0]}" +
412 " should be a value (symbol, integer, or at-expression)"
419 x.respond_to?(:length) && x.length == 2 && x[0] == :'@' &&
424 x.kind_of?(::Integer) || substitution?(x)
428 x.kind_of?(::Symbol) || int?(x)
431 def int_or_symbol_or_at? x
432 int_or_symbol?(x) || at_expr?(x)
436 x.respond_to?(:length) && x.length == 2 && x[0] == :'%' &&
445 symbol?(x) || at_expr?(x)