Voodoo::Validator

Functionality for validating Voodoo code.

See validate_top_level, validate_statement, and validate_expression.

Constants

BINOPS

Expressions that take two parameters.

EXPRS

Symbols that may occur as the first word of an expression.

NTH

Maps indices 0, 1, 2 to English words.

STATEMENTS

Symbols that are a valid start of a statement.

TOP_LEVELS

Symbols that are valid at top-level.

UNOPS

Expressions that take a single parameter.

VARARG_EXPRS

Expressions that take zero or more parameters.

Public Instance Methods

assert_at_least_n_params(expr, n) click to toggle source

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
assert_n_params(expr, n) click to toggle source

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
assert_params_are_values(expr, ns = nil) click to toggle source

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
at_expr?(x) click to toggle source
# 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
int?(x) click to toggle source
# File voodoo/validator.rb, line 423
def int? x
  x.kind_of?(::Integer) || substitution?(x)
end
int_or_symbol?(x) click to toggle source
# File voodoo/validator.rb, line 427
def int_or_symbol? x
  x.kind_of?(::Symbol) || int?(x)
end
int_or_symbol_or_at?(x) click to toggle source
# File voodoo/validator.rb, line 431
def int_or_symbol_or_at? x
  int_or_symbol?(x) || at_expr?(x)
end
substitution?(x) click to toggle source
# File voodoo/validator.rb, line 435
def substitution? x
  x.respond_to?(:length) && x.length == 2 && x[0] == :'%' &&
    symbol?(x[1])
end
symbol?(x) click to toggle source
# File voodoo/validator.rb, line 440
def symbol? x
  x.kind_of? ::Symbol
end
symbol_or_at?(x) click to toggle source
# File voodoo/validator.rb, line 444
def symbol_or_at? x
  symbol?(x) || at_expr?(x)
end
validate_expression(code) click to toggle source

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
validate_statement(code) click to toggle source

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
validate_top_level(code) click to toggle source

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

[Validate]

Generated with the Darkfish Rdoc Generator 2.