Functionality for validating Voodoo code.
See validate_top_level, validate_statement, and validate_expression.
Expressions that take two parameters.
Symbols that may occur as the first word of an expression.
Maps indices 0, 1, 2 to English words.
Symbols that are a valid start of a statement.
Symbols that are valid at top-level.
Expressions that take a single parameter.
Expressions that take zero or more parameters.
Tests that an expression has at least n parameters. Raises a ValidationError if this is not the case.
# File voodoo/validator.rb, line 367 def assert_at_least_n_params expr, n if expr.length <= n if n == 1 raise ValidationError.new "#{expr[0]} should have at least one parameter" else raise ValidationError.new "#{expr[0]} should have at least #{n} parameters" end end true end
Tests that an expression has exactly n parameters. Raises a ValidationError if this is not the case.
# File voodoo/validator.rb, line 382 def assert_n_params expr, n if expr.length != n + 1 if n == 1 raise ValidationError.new "#{expr[0]} should have exactly one parameter" else raise ValidationError.new "#{expr[0]} should have exactly #{n} parameters" end end true end
Tests that parameters to an expression are values (integers, symbols, or at-expressions), and raises ValidationError if this is not the case. If ns is nil (default) all parameters should me values. Alternatively, ns may be a range or array containing the indices of the parameters that should be values.
# File voodoo/validator.rb, line 404 def assert_params_are_values expr, ns = nil if ns == nil ns = (1...expr.length) end ns.each do |i| unless int_or_symbol_or_at?(expr[i]) raise ValidationError.new "#{NTH[i - 1]} parameter to #{expr[0]}" + " should be a value (symbol, integer, or at-expression)" end end true end
# File voodoo/validator.rb, line 418 def at_expr? x x.respond_to?(:length) && x.length == 2 && x[0] == :'@' && int_or_symbol?(x[1]) end
# File voodoo/validator.rb, line 423 def int? x x.kind_of?(::Integer) || substitution?(x) end
# File voodoo/validator.rb, line 427 def int_or_symbol? x x.kind_of?(::Symbol) || int?(x) end
# File voodoo/validator.rb, line 431 def int_or_symbol_or_at? x int_or_symbol?(x) || at_expr?(x) end
# File voodoo/validator.rb, line 435 def substitution? x x.respond_to?(:length) && x.length == 2 && x[0] == :'%' && symbol?(x[1]) end
# File voodoo/validator.rb, line 440 def symbol? x x.kind_of? ::Symbol end
# File voodoo/validator.rb, line 444 def symbol_or_at? x symbol?(x) || at_expr?(x) end
Validates an expression. Returns true if the expression is valid. Raises ValidationError if the expression is not valid.
# File voodoo/validator.rb, line 36 def validate_expression code if int_or_symbol_or_at? code true elsif code.respond_to? :[] op = code[0] if BINOPS.member? op # binop should have 2 parameters, both of them atomic values assert_n_params code, 2 assert_params_are_values code elsif UNOPS.member? op # should have a single, atomic parameter assert_n_params code, 1 assert_params_are_values code else # op is not a unary or binary operator case op when :call, :'tail-call' # call should have at least 1 parameter # and all parameters should be atomic values assert_at_least_n_params code, 1 assert_params_are_values code when :'get-byte', :'get-word' # Should have exactly 2 parameters, both of which should be values. assert_n_params code, 2 assert_params_are_values code else raise ValidationError.new("#{code[0].inspect}" + " cannot start an expression", code) end end else # code is not an atomic value and does not respond to :[] raise ValidationError.new("#{code.inspect} is not a valid expression", code) end end
Validates a statement. Returns true if the statement is valid. Raises ValidationError if the statement is not valid.
# File voodoo/validator.rb, line 77 def validate_statement code begin case code[0] when :block code[1..-1].each {|stmt| validate_statement stmt} true when :byte, :word # Should have a single integer or symbol parameter if code.length != 2 || !int_or_symbol?(code[1]) raise ValidationError.new("#{code[0]} requires a single" + " parameter that is either an " + " integer or a symbol", code) else true end when :call, :'tail-call' validate_expression code when :goto # should have exactly 1 parameter, which should be atomic assert_n_params code, 1 assert_params_are_values code when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne # Should have 2 or 3 parameters. # First parameter should be an array (or similar) containing # two elements, both atomic. # Second parameter should consist of one or more statements. # Third parameter, if present, should consist of zero or more # statements. # let is not allowed as a statement in either parameter, though # it can be embedded in a block in either. if code.length < 3 || code.length > 4 raise ValidationError.new("#{code[0]} takes 2 or 3 parameters", code) elsif code[1].length != 2 raise ValidationError.new("#{code[0]} requires two values to" + " compare in its first parameter", code) elsif !code[1].all? {|x| int_or_symbol_or_at? x} raise ValidationError.new("Values to compare should be values" + " (symbols, integers, or at-exrpssions)", code) else code[2].each do |stmt| validate_statement stmt if stmt[0] == :let raise ValidationError.new("let is not allowed inside " + code[0].to_s, code) end end if code.length > 3 code[3].each do |stmt| validate_statement stmt if stmt[0] == :let raise ValidationError.new("let is not allowed inside " + code[0].to_s, code) end end end true end when :label # should have 1 parameter which should be a symbol if code.length != 2 || !code[1].kind_of?(::Symbol) raise ValidationError.new("label requires a single symbol" + "as its parameter", code) else true end when :let # should have at least 2 parameters if code.length < 3 raise ValidationError.new("#{code[0]} requires a symbol" + " and an expression", code) elsif symbol? code[1] # After the symbol, there should be an expression expr = code[2..-1] if expr.length == 1 validate_expression expr[0] else validate_expression expr end else raise ValidationError.new("First parameter to #{code[0]} should be" + " a symbol", code) end when :set # should have at least 2 parameters if code.length < 3 raise ValidationError.new( "#{code[0]} requires a symbol or at-expression" + " followed by an expression", code) elsif symbol_or_at? code[1] # After the symbol/at-expr, there should be an expression expr = code[2..-1] if expr.length == 1 validate_expression expr[0] else validate_expression expr end else raise ValidationError.new("First parameter to #{code[0]} should be" + " a symbol or an at-expression", code) end when :return # Should either have no parameters, or a single expression as # a parameter. case code.length when 1 true when 2 validate_expression code[1] else validate_expression code[1..-1] end when :'set-byte', :'set-word' # Should have exactly 3 parameters, all of which should be # atomic values. assert_n_params code, 3 assert_params_are_values code when :'restore-frame', :'save-frame' # Should have exactly 1 parameter. assert_n_params code, 1 assert_params_are_values code when :'restore-locals', :'save-frame-and-locals', :'save-locals' # Should have 1 or more parameters. assert_at_least_n_params code, 1 assert_params_are_values code when :string # Should have a single string parameter if code.length != 2 || !code[1].kind_of?(::String) raise ValidationError.new("string requires a single string" + " as a parameter", code) else true end else if TOP_LEVELS.member?(code[0]) && !STATEMENTS.member?(code[0]) raise ValidationError.new("#{code[0]} is only valid at top-level", code) else raise ValidationError.new("Not a valid statement: #{code.inspect}", code) end end rescue ValidationError # Pass it on raise rescue Exception => e if code.respond_to? :[] # Pass on the exception raise else raise ValidationError.new("#{code.inspect} does not respond to" + ":[]", code) end end end
Validates a top-level incantation. Returns true if the incantation is valid. Raises ValidationError if the incantation is not valid.
# File voodoo/validator.rb, line 256 def validate_top_level code begin case code[0] when :align # Should either have no parameter or a single integer parameter if code.length == 1 || (code.length == 2 && code[1].kind_of?(::Integer)) true else raise ValidationError.new("align requires either a single" + " integer parameter, or no parameters", code) end when :export, :import # Should have at least 1 parameter, and all parameters should # be symbols. if code.length < 2 raise ValidationError.new("#{code[0]} requires at least " + " one parameter", code) elsif code[1..-1].all? {|x| x.kind_of? ::Symbol} true else raise ValidationError.new("All parameters to #{code[0]}" + " should be symbols", code) end when :function # Check that formal parameters have been specified if code.length < 2 raise ValidationError.new("Formal parameters should be" + " specified for function", code) end # Check that all formal parameters are symbols code[1].each do |formal| unless formal.kind_of? ::Symbol raise ValidationError.new("Formal parameter #{formal.inspect}" + " should be a symbol", formal) end end # Verify body. code[2..-1].each { |stmt| validate_statement stmt } when :group # Verify body. code[1..-1].each { |stmt| validate_top_level stmt } when :section # Check that we have a string or a symbol case code.length when 1 raise ValidationError.new("Section name should be specified", code) when 2 unless code[1].kind_of?(::String) || code[1].kind_of?(::Symbol) raise ValidationError.new("Section name should be a string" + " or a symbol", code) end else raise ValidationError.new("section incantation should have only" + " a single parameter", code); end else if STATEMENTS.member? code[0] validate_statement code else raise ValidationError.new("Incantation #{code[0]}" + " not valid at top-level", code) end end rescue ValidationError # Pass it on raise rescue Exception => e if code.respond_to? :[] # Pass on the exception raise else raise ValidationError.new("#{code.inspect} does not respond to" + ":[]", code) end end # If we got here, all is well true end
Generated with the Darkfish Rdoc Generator 2.