Parent

Namespace

SexpProcessor

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_ method or use the default processor provided to skip over them.

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

Constants

VERSION

Attributes

auto_shift_type[RW]

Automatically shifts off the Sexp type before handing the Sexp to process_

context[R]

Return a stack of contexts. Most recent node is first.

debug[RW]

A Hash of Sexp types and Regexp.

Print a debug message if the Sexp type matches the Hash key and the Sexp’s # output matches the Regexp.

default_method[RW]

A default method to call if a process_ method is not found for the Sexp type.

expected[RW]

Expected result class

require_empty[RW]

Raise an exception if the Sexp is not empty after processing

strict[RW]

Raise an exception if no process_ method is found for a Sexp.

unsupported[RW]

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.

warn_on_default[RW]

Emit a warning when the method in # is called.

env[R]

A scoped environment to make you happy.

Public Class Methods

new() click to toggle source

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.to_sym] = name.to_sym
125:       when /^rewrite_(.*)/ then
126:         @rewriters[$1.to_sym]  = name.to_sym
127:       end
128:     end
129:   end

Public Instance Methods

assert_empty(meth, exp, exp_orig) click to toggle source
     # 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
assert_type(list, typ) click to toggle source

Raises unless the Sexp type for list matches typ

     # File lib/sexp_processor.rb, line 269
269:   def assert_type(list, typ)
270:     raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
271:       not Array === list or list.first != typ
272:   end
in_context(type) click to toggle source
     # File lib/sexp_processor.rb, line 338
338:   def in_context type
339:     self.context.unshift type
340: 
341:     yield
342: 
343:     self.context.shift
344:   end
on_error_in(node_type, &block) click to toggle source

Registers an error handler for node

     # File lib/sexp_processor.rb, line 290
290:   def on_error_in(node_type, &block)
291:     @exceptions[node_type] = block
292:   end
process(exp) click to toggle source

Default Sexp processor. Invokes process_ methods matching the Sexp type given. Performs additional checks as specified by the initializer.

     # File lib/sexp_processor.rb, line 172
172:   def process(exp)
173:     return nil if exp.nil?
174:     if self.context.empty? then
175:       p :rewriting unless debug.empty?
176:       exp = self.rewrite(exp)
177:       p :done_rewriting unless debug.empty?
178:     end
179: 
180:     unless @unsupported_checked then
181:       m = public_methods.grep(/^process_/) { |o| o.to_s.sub(/^process_/, '').to_sym }
182:       supported = m - (m - @unsupported)
183: 
184:       raise UnsupportedNodeError, "#{supported.inspect} shouldn't be in @unsupported" unless supported.empty?
185: 
186:       @unsupported_checked = true
187:     end
188: 
189:     result = self.expected.new
190: 
191:     type = exp.first
192:     raise "type should be a Symbol, not: #{exp.first.inspect}" unless
193:       Symbol === type
194: 
195:     in_context type do
196:       if @debug.has_key? type then
197:         str = exp.inspect
198:         puts "// DEBUG:(original ): #{str}" if str =~ @debug[type]
199:       end
200: 
201:       exp_orig = nil
202:       exp_orig = exp.deep_clone if $DEBUG or
203:         @debug.has_key? type or @exceptions.has_key?(type)
204: 
205:       raise UnsupportedNodeError, "'#{type}' is not a supported node type" if
206:         @unsupported.include? type
207: 
208:       # now do a pass with the real processor (or generic)
209:       meth = @processors[type] || @default_method
210:       if meth then
211: 
212:         if @warn_on_default and meth == @default_method then
213:           warn "WARNING: Using default method #{meth} for #{type}"
214:         end
215: 
216:         exp.shift if @auto_shift_type and meth != @default_method
217: 
218:         result = error_handler(type, exp_orig) do
219:           self.send(meth, exp)
220:         end
221: 
222:         if @debug.has_key? type then
223:           str = exp.inspect
224:           puts "// DEBUG (processed): #{str}" if str =~ @debug[type]
225:         end
226: 
227:         raise SexpTypeError, "Result must be a #{@expected}, was #{result.class}:#{result.inspect}" unless @expected === result
228: 
229:         self.assert_empty(meth, exp, exp_orig) if @require_empty
230:       else
231:         unless @strict then
232:           until exp.empty? do
233:             sub_exp = exp.shift
234:             sub_result = nil
235:             if Array === sub_exp then
236:               sub_result = error_handler(type, exp_orig) do
237:                 process(sub_exp)
238:               end
239:               raise "Result is a bad type" unless Array === sub_exp
240:               raise "Result does not have a type in front: #{sub_exp.inspect}" unless Symbol === sub_exp.first unless sub_exp.empty?
241:             else
242:               sub_result = sub_exp
243:             end
244:             result << sub_result
245:           end
246: 
247:           # NOTE: this is costly, but we are in the generic processor
248:           # so we shouldn't hit it too much with RubyToC stuff at least.
249:           #if Sexp === exp and not exp.sexp_type.nil? then
250:           begin
251:             result.sexp_type = exp.sexp_type
252:           rescue Exception
253:             # nothing to do, on purpose
254:           end
255:         else
256:           msg = "Bug! Unknown node-type #{type.inspect} to #{self.class}"
257:           msg += " in #{exp_orig.inspect} from #{caller.inspect}" if $DEBUG
258:           raise UnknownNodeError, msg
259:         end
260:       end
261:     end
262: 
263:     result
264:   end
process_dummy(exp) click to toggle source

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 305
305:   def process_dummy(exp)
306:     result = @expected.new(:dummy) rescue @expected.new
307: 
308:     until exp.empty? do
309:       result << self.process(exp.shift)
310:     end
311: 
312:     result
313:   end
rewrite(exp) click to toggle source
     # File lib/sexp_processor.rb, line 139
139:   def rewrite(exp)
140:     type = exp.first
141: 
142:     if @debug.has_key? type then
143:       str = exp.inspect
144:       puts "// DEBUG (original ): #{str}" if str =~ @debug[type]
145:     end
146: 
147:     in_context type do
148:       exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
149:     end
150: 
151:     begin
152:       meth = @rewriters[type]
153:       exp  = self.send(meth, exp) if meth
154:       break unless Sexp === exp
155: 
156:       if @debug.has_key? type then
157:         str = exp.inspect
158:         puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
159:       end
160: 
161:       old_type, type = type, exp.first
162:     end until old_type == type
163: 
164:     exp
165:   end
scope(&block) click to toggle source

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 334
334:   def scope &block
335:     env.scope(&block)
336:   end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.