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 #, if it is defined.
You can also specify a default method to call for any Sexp types without a process_
Here is a simple example:
class MyProcessor < SexpProcessor
def initialize
super
self.strict = false
end
def process_lit(exp)
val = exp.shift
return val
end
end
An array that specifies node types that are unsupported by this processor. SexpProcessor will raise UnsupportedNodeError if you try to process one of those node types.
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
# File lib/sexp_processor.rb, line 102
102: def initialize
103: @default_method = nil
104: @warn_on_default = true
105: @auto_shift_type = false
106: @strict = false
107: @unsupported = [:alloca, :cfunc, :cref, :ifunc, :last, :memo,
108: :newline, :opt_n, :method]
109: @unsupported_checked = false
110: @debug = {}
111: @expected = Sexp
112: @require_empty = true
113: @exceptions = {}
114:
115: # we do this on an instance basis so we can subclass it for
116: # different processors.
117: @processors = {}
118: @rewriters = {}
119: @context = []
120:
121: public_methods.each do |name|
122: case name
123: when /^process_(.*)/ then
124: @processors[$1.intern] = name.intern
125: when /^rewrite_(.*)/ then
126: @rewriters[$1.intern] = name.intern
127: end
128: end
129: end
# File lib/sexp_processor.rb, line 131
131: def assert_empty(meth, exp, exp_orig)
132: unless exp.empty? then
133: msg = "exp not empty after #{self.class}.#{meth} on #{exp.inspect}"
134: msg += " from #{exp_orig.inspect}" if $DEBUG
135: raise NotEmptyError, msg
136: end
137: end
Raises unless the Sexp type for list matches typ
# File lib/sexp_processor.rb, line 257
257: def assert_type(list, typ)
258: raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
259: not Array === list or list.first != typ
260: end
Registers an error handler for node
# File lib/sexp_processor.rb, line 278
278: def on_error_in(node_type, &block)
279: @exceptions[node_type] = block
280: end
Default Sexp processor. Invokes process_
# File lib/sexp_processor.rb, line 163
163: def process(exp)
164: return nil if exp.nil?
165: exp = self.rewrite(exp) if self.context.empty?
166:
167: unless @unsupported_checked then
168: m = public_methods.grep(/^process_/) { |o| o.to_s.sub(/^process_/, '').intern }
169: supported = m - (m - @unsupported)
170:
171: raise UnsupportedNodeError, "#{supported.inspect} shouldn't be in @unsupported" unless supported.empty?
172:
173: @unsupported_checked = true
174: end
175:
176: result = self.expected.new
177:
178: type = exp.first
179: raise "type should be a Symbol, not: #{exp.first.inspect}" unless
180: Symbol === type
181:
182: self.context.unshift type
183:
184: if @debug.has_key? type then
185: str = exp.inspect
186: puts "// DEBUG: #{str}" if str =~ @debug[type]
187: end
188:
189: exp_orig = nil
190: exp_orig = exp.deep_clone if $DEBUG or
191: @debug.has_key? type or @exceptions.has_key?(type)
192:
193: raise UnsupportedNodeError, "'#{type}' is not a supported node type" if
194: @unsupported.include? type
195:
196: if @debug.has_key? type then
197: str = exp.inspect
198: puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
199: end
200:
201: # now do a pass with the real processor (or generic)
202: meth = @processors[type] || @default_method
203: if meth then
204:
205: if @warn_on_default and meth == @default_method then
206: warn "WARNING: Using default method #{meth} for #{type}"
207: end
208:
209: exp.shift if @auto_shift_type and meth != @default_method
210:
211: result = error_handler(type, exp_orig) do
212: self.send(meth, exp)
213: end
214:
215: raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
216:
217: self.assert_empty(meth, exp, exp_orig) if @require_empty
218: else
219: unless @strict then
220: until exp.empty? do
221: sub_exp = exp.shift
222: sub_result = nil
223: if Array === sub_exp then
224: sub_result = error_handler(type, exp_orig) do
225: process(sub_exp)
226: end
227: raise "Result is a bad type" unless Array === sub_exp
228: raise "Result does not have a type in front: #{sub_exp.inspect}" unless Symbol === sub_exp.first unless sub_exp.empty?
229: else
230: sub_result = sub_exp
231: end
232: result << sub_result
233: end
234:
235: # NOTE: this is costly, but we are in the generic processor
236: # so we shouldn't hit it too much with RubyToC stuff at least.
237: #if Sexp === exp and not exp.sexp_type.nil? then
238: begin
239: result.sexp_type = exp.sexp_type
240: rescue Exception
241: # nothing to do, on purpose
242: end
243: else
244: msg = "Bug! Unknown node-type #{type.inspect} to #{self.class}"
245: msg += " in #{exp_orig.inspect} from #{caller.inspect}" if $DEBUG
246: raise UnknownNodeError, msg
247: end
248: end
249:
250: self.context.shift
251: result
252: end
A fairly generic processor for a dummy node. Dummy nodes are used when your processor is doing a complicated rewrite that replaces the current sexp with multiple sexps.
Bogus Example:
def process_something(exp)
return s(:dummy, process(exp), s(:extra, 42))
end
# File lib/sexp_processor.rb, line 293
293: def process_dummy(exp)
294: result = @expected.new(:dummy) rescue @expected.new
295:
296: until exp.empty? do
297: result << self.process(exp.shift)
298: end
299:
300: result
301: end
# File lib/sexp_processor.rb, line 139
139: def rewrite(exp)
140: type = exp.first
141:
142: self.context.unshift type
143:
144: exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
145:
146: self.context.shift
147:
148: begin
149: meth = @rewriters[type]
150: exp = self.send(meth, exp) if meth
151: break unless Sexp === exp
152: old_type, type = type, exp.first
153: end until old_type == type
154:
155: exp
156: 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
# File lib/sexp_processor.rb, line 322
322: def scope &block
323: env.scope(&block)
324: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.