class SexpProcessor

def assert_empty(meth, exp, exp_orig)

def assert_empty(meth, exp, exp_orig)
  unless exp.empty? then
    msg = "exp not empty after #{self.class}.#{meth} on #{exp.inspect}"
    msg += " from #{exp_orig.inspect}" if $DEBUG
    raise NotEmptyError, msg
  end
end

def assert_type(list, typ)

def assert_type(list, typ)
  raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
    not Array === list or list.first != typ
end

def error_handler(type, exp=nil) # :nodoc:

:nodoc:
def error_handler(type, exp=nil) # :nodoc:
  begin
    return yield
  rescue StandardError => err
    if @exceptions.has_key? type then
      return @exceptions[type].call(self, exp, err)
    else
      $stderr.puts "#{err.class} Exception thrown while processing #{type} for sexp #{exp.inspect} #{caller.inspect}" if $DEBUG
      raise
    end
  end
end

def generate # :nodoc:

:nodoc:
def generate # :nodoc:
  raise NotImplementedError, "not implemented yet"
end

def initialize

def initialize
  @default_method = nil
  @warn_on_default = true
  @auto_shift_type = false
  @strict = false
  @unsupported = [:alloca, :cfunc, :cref, :ifunc, :last, :memo, :newline, :opt_n, :method] # internal nodes that you can't get to
  @unsupported_checked = false
  @debug = {}
  @expected = Sexp
  @require_empty = true
  @exceptions = {}
  # we do this on an instance basis so we can subclass it for
  # different processors.
  @processors = {}
  @rewriters  = {}
  @context = []
  public_methods.each do |name|
    case name
    when /^process_(.*)/ then
      @processors[$1.intern] = name.intern
    when /^rewrite_(.*)/ then
      @rewriters[$1.intern]  = name.intern
    end
  end
end

def on_error_in(node_type, &block)

def on_error_in(node_type, &block)
  @exceptions[node_type] = block
end

def process(exp)

def process(exp)
  return nil if exp.nil?
  unless @unsupported_checked then
    m = public_methods.grep(/^process_/) { |o| o.sub(/^process_/, '').intern }
    supported = m - (m - @unsupported)
    raise UnsupportedNodeError, "#{supported.inspect} shouldn't be in @unsupported" unless supported.empty?
    @unsupported_checked = true
  end
  result = self.expected.new
  type = exp.first
  raise "type should be a Symbol, not: #{exp.first.inspect}" unless
    Symbol === type
  self.context.unshift type
  if @debug.has_key? type then
    str = exp.inspect
    puts "// DEBUG: #{str}" if str =~ @debug[type]
  end
  exp_orig = nil
  exp_orig = exp.deep_clone if $DEBUG or
    @debug.has_key? type or @exceptions.has_key?(type)
  raise UnsupportedNodeError, "'#{type}' is not a supported node type" if @unsupported.include? type
  exp = self.rewrite(exp) if self.context.size == 1
  if @debug.has_key? type then
    str = exp.inspect
    puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
  end
  # now do a pass with the real processor (or generic)
  meth = @processors[type] || @default_method
  if meth then
    if @warn_on_default and meth == @default_method then
      $stderr.puts "WARNING: Using default method #{meth} for #{type}"
    end
    exp.shift if @auto_shift_type and meth != @default_method
    result = error_handler(type, exp_orig) do
      self.send(meth, exp)
    end
    raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
    self.assert_empty(meth, exp, exp_orig) if @require_empty
  else
    unless @strict then
      until exp.empty? do
        sub_exp = exp.shift
        sub_result = nil
        if Array === sub_exp then
          sub_result = error_handler(type, exp_orig) do
            process(sub_exp)
          end
          raise "Result is a bad type" unless Array === sub_exp
          raise "Result does not have a type in front: #{sub_exp.inspect}" unless Symbol === sub_exp.first unless sub_exp.empty?
        else
          sub_result = sub_exp
        end
        result << sub_result
      end
      # NOTE: this is costly, but we are in the generic processor
      # so we shouldn't hit it too much with RubyToC stuff at least.
      #if Sexp === exp and not exp.sexp_type.nil? then
      begin
        result.sexp_type = exp.sexp_type
      rescue Exception
        # nothing to do, on purpose
      end
    else
      msg = "Bug! Unknown node-type #{type.inspect} to #{self.class}"
      msg += " in #{exp_orig.inspect} from #{caller.inspect}" if $DEBUG
      raise UnknownNodeError, msg
    end
  end
  self.context.shift
  result
end

def process_dummy(exp)

def process_dummy(exp)
  result = @expected.new(:dummy) rescue @expected.new
  until exp.empty? do
    result << self.process(exp.shift)
  end
  result
end

def rewrite(exp)

def rewrite(exp)
  type = exp.first
  self.context.unshift type # FIX: first one doubles up because process already unshifted -- look at moving initial rewrite up above
  exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
  begin
    meth = @rewriters[type]
    exp  = self.send(meth, exp) if meth
    old_type, type = type, exp.first
  end until old_type == type
  self.context.shift
  exp
end