Class: SexpProcessor

Inherits:
Object show all
Defined in:
lib/sexp_processor.rb

Overview

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.

You can also specify a default method to call for any Sexp types without a process_<type> 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

Direct Known Subclasses

CompositeSexpProcessor, Unifier

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSexpProcessor

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



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/sexp_processor.rb', line 135

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

Instance Attribute Details

#auto_shift_typeObject

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



81
82
83
# File 'lib/sexp_processor.rb', line 81

def auto_shift_type
  @auto_shift_type
end

#contextObject (readonly)

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



86
87
88
# File 'lib/sexp_processor.rb', line 86

def context
  @context
end

#debugObject

A Hash of Sexp types and Regexp.

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



94
95
96
# File 'lib/sexp_processor.rb', line 94

def debug
  @debug
end

#default_methodObject

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



100
101
102
# File 'lib/sexp_processor.rb', line 100

def default_method
  @default_method
end

#expectedObject

Expected result class



105
106
107
# File 'lib/sexp_processor.rb', line 105

def expected
  @expected
end

#require_emptyObject

Raise an exception if the Sexp is not empty after processing



110
111
112
# File 'lib/sexp_processor.rb', line 110

def require_empty
  @require_empty
end

#strictObject

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



115
116
117
# File 'lib/sexp_processor.rb', line 115

def strict
  @strict
end

#unsupportedObject

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.



122
123
124
# File 'lib/sexp_processor.rb', line 122

def unsupported
  @unsupported
end

#warn_on_defaultObject

Emit a warning when the method in #default_method is called.



127
128
129
# File 'lib/sexp_processor.rb', line 127

def warn_on_default
  @warn_on_default
end

Instance Method Details

#assert_empty(meth, exp, exp_orig) ⇒ Object



163
164
165
166
167
168
169
# File 'lib/sexp_processor.rb', line 163

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

#assert_type(list, typ) ⇒ Object

Raises unless the Sexp type for list matches typ

Raises:



291
292
293
294
# File 'lib/sexp_processor.rb', line 291

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

#generateObject

:nodoc:

Raises:

  • (NotImplementedError)


284
285
286
# File 'lib/sexp_processor.rb', line 284

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

#on_error_in(node_type, &block) ⇒ Object

Registers an error handler for node



313
314
315
# File 'lib/sexp_processor.rb', line 313

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

#process(exp) ⇒ Object

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



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/sexp_processor.rb', line 193

def process(exp)
  return nil if exp.nil?

  unless @unsupported_checked then
    m = public_methods.grep(/^process_/) { |o| o.to_s.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

#process_dummy(exp) ⇒ Object

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


328
329
330
331
332
333
334
# File 'lib/sexp_processor.rb', line 328

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

#rewrite(exp) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/sexp_processor.rb', line 171

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