##
# SexpProcessor provides a uniform interface to process Sexps.
#
# In order to create your own SexpProcessor subclass you'll need
# to call super in the initialize method, then set any of the
# Sexp flags you want to be different from the defaults.
#
# SexpProcessor uses a Sexp's type to determine which process method
# to call in the subclass. For Sexp s(:lit, 1)
# SexpProcessor will call #process_lit, if it is defined.
#
class Brakeman::SexpProcessor
VERSION = 'CUSTOM'
##
# Return a stack of contexts. Most recent node is first.
attr_reader :context
##
# Expected result class
attr_accessor :expected
##
# A scoped environment to make you happy.
attr_reader :env
# Cache process methods per class
def self.processors
@processors ||= {}
end
##
# Creates a new SexpProcessor. Use super to invoke this
# initializer from SexpProcessor subclasses, then use the
# attributes above to customize the functionality of the
# SexpProcessor
def initialize
@expected = Sexp
@processors = self.class.processors
@context = []
@current_class = @current_module = @current_method = @visibility = nil
if @processors.empty?
public_methods.each do |name|
if name.to_s.start_with? "process_" then
@processors[name[8..-1].to_sym] = name.to_sym
end
end
end
end
##
# Default Sexp processor. Invokes process_ methods matching
# the Sexp type given. Performs additional checks as specified by
# the initializer.
def process(exp)
return nil if exp.nil?
result = nil
type = exp.first
raise "Type should be a Symbol, not: #{exp.first.inspect} in #{exp.inspect}" unless Symbol === type
in_context type do
# now do a pass with the real processor (or generic)
meth = @processors[type]
if meth then
result = self.send(meth, exp)
else
result = self.process_default(exp)
end
end
raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
result
end
##
# Add a scope level to the current env. Eg:
#
# def process_defn exp
# name = exp.shift
# args = process(exp.shift)
# scope do
# body = process(exp.shift)
# # ...
# end
# end
#
# env[:x] = 42
# scope do
# env[:x] # => 42
# env[:y] = 24
# end
# env[:y] # => nil
def scope &block
env.scope(&block)
end
def in_context type
self.context.unshift type
yield
self.context.shift
end
end