Class: SexpProcessor

### Home Classes Methods

### In Files

- lib/sexp_processor.rb

### Parent

Object

### Namespace

- CLASS SexpProcessor::Environment

### Methods

- ::expand_dirs_to_files
- ::new
- ::processors
- ::rewriters
- #assert_empty
- #assert_type
- #in_context
- #on_error_in
- #process
- #process_dummy
- #rewrite
- #scope

### Files

- History.txt
- Manifest.txt
- README.txt

### Class/Module Index

Quicksearch

- SexpProcessor
- SexpProcessor::Environment
- CompositeSexpProcessor
- Examples
- MethodBasedSexpProcessor
- NotEmptyError
- Object
- ParseTreeTestCase
- Sexp
- SexpAny
- SexpInterpreter
- SexpMatchSpecial
- SexpMatchSpecials
- SexpProcessorError
- SexpTypeError
- Unique
- UnknownNodeError
- UnsupportedNodeError

No matching classes.

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

### Constants

VERSION

### Attributes

auto_shift_type[RW]

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

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 inspect output matches the Regexp.

default_method[RW]

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

env[R]

A scoped environment to make you happy.

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_<type> 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 default_method is called.

### Public Class Methods

expand_dirs_to_files(*dirs) click to toggle source

Expand an array of directories into a flattened array of paths, eg:

    MyProcessor.run MyProcessor.expand_dirs_to_files ARGV

    # File lib/sexp_processor.rb, line 100
    def self.expand_dirs_to_files *dirs
      extensions = ]rb rake]

      dirs.flatten.map { |p|
        if File.directory? p then
          Dir[File.join(p, '**', "*.{#{extensions.join(',')}}")]
        else
          p
        end
      }.flatten.sort
    end

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 132
    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]
      @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 = self.class.processors
      @rewriters  = self.class.rewriters
      @context    = []

      if @processors.empty?
        public_methods.each do |name|
          case name
          when /^process_(.*)/ then
            @processors[$1.to_sym] = name.to_sym
          when /^rewrite_(.*)/ then
            @rewriters[$1.to_sym]  = name.to_sym
          end
        end
      end
    end

processors() click to toggle source

Cache processor methods per class.

    # File lib/sexp_processor.rb, line 115
    def self.processors
      @processors ||= {}
    end

rewriters() click to toggle source

Cache rewiter methods per class.

    # File lib/sexp_processor.rb, line 122
    def self.rewriters
      @rewriters ||= {}
    end

### Public Instance Methods

assert_empty(meth, exp, exp_orig) click to toggle source

    # 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) click to toggle source

Raises unless the Sexp type for `list` matches `typ`

    # File lib/sexp_processor.rb, line 301
    def assert_type(list, typ)
      raise SexpTypeError, "Expected type #{typ.inspect} in #{list.inspect}" if
        not Array === list or list.first != typ
    end

in_context(type) click to toggle source

    # File lib/sexp_processor.rb, line 370
    def in_context type
      self.context.unshift type

      yield

      self.context.shift
    end

on_error_in(node_type, &block) click to toggle source

Registers an error handler for `node`

    # File lib/sexp_processor.rb, line 322
    def on_error_in(node_type, &block)
      @exceptions[node_type] = block
    end

process(exp) click to toggle source

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

    # File lib/sexp_processor.rb, line 204
    def process(exp)
      return nil if exp.nil?
      if self.context.empty? then
        p :rewriting unless debug.empty?
        exp = self.rewrite(exp)
        p :done_rewriting unless debug.empty?
      end

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

      in_context type do
        if @debug.has_key? type then
          str = exp.inspect
          puts "// DEBUG:(original ): #{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

        # 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
            warn "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

          if @debug.has_key? type then
            str = exp.inspect
            puts "// DEBUG (processed): #{str}" if str =~ @debug[type]
          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
      end

      result
    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 337
    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) click to toggle source

    # File lib/sexp_processor.rb, line 171
    def rewrite(exp)
      type = exp.first

      if @debug.has_key? type then
        str = exp.inspect
        puts "// DEBUG (original ): #{str}" if str =~ @debug[type]
      end

      in_context type do
        exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
      end

      begin
        meth = @rewriters[type]
        exp  = self.send(meth, exp) if meth
        break unless Sexp === exp

        if @debug.has_key? type then
          str = exp.inspect
          puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
        end

        old_type, type = type, exp.first
      end until old_type == type

      exp
    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 366
    def scope &block
      env.scope(&block)
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.