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:
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:
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