Class: Parslet::Transform

### Home Classes Methods

### In Files

- lib/parslet/transform.rb

### Parent

Object

### Namespace

- CLASS Parslet::Transform::Context

### Methods

- ::new
- ::rule
- ::rules
- #apply
- #call_on_match
- #recurse_array
- #recurse_hash
- #rule
- #rules
- #transform_elt

### Included Modules

- Parslet

### Files

- README

### Class/Module Index

Quicksearch

- Parslet
- Parslet::Atoms
- Parslet::Atoms::Alternative
- Parslet::Atoms::Base
- Parslet::Atoms::CanFlatten
- Parslet::Atoms::Context
- Parslet::Atoms::DSL
- Parslet::Atoms::Entity
- Parslet::Atoms::Lookahead
- Parslet::Atoms::Named
- Parslet::Atoms::Precedence
- Parslet::Atoms::Re
- Parslet::Atoms::Repetition
- Parslet::Atoms::Sequence
- Parslet::Atoms::Str
- Parslet::Cause
- Parslet::ClassMethods
- Parslet::DelayedMatchConstructor
- Parslet::ErrorReporter
- Parslet::ErrorReporter::Deepest
- Parslet::ErrorReporter::Tree
- Parslet::Expression
- Parslet::Expression::Treetop
- Parslet::Expression::Treetop::Parser
- Parslet::Expression::Treetop::Transform
- Parslet::ParseFailed
- Parslet::Parser
- Parslet::Parser::PrettyPrinter
- Parslet::Parser::Visitors
- Parslet::Parser::Visitors::Citrus
- Parslet::Parser::Visitors::Treetop
- Parslet::Pattern
- Parslet::Pattern::SequenceBind
- Parslet::Pattern::SimpleBind
- Parslet::Pattern::SubtreeBind
- Parslet::Slice
- Parslet::Source
- Parslet::Source::LineCache
- Parslet::Source::RangeSearch
- Parslet::Transform
- Parslet::Transform::Context
- Parslet::UnconsumedInput

No matching classes.

# Parslet::Transform

Transforms an expression tree into something else. The transformation performs a depth-first, post-order traversal of the expression tree. During that traversal, each time a rule matches a node, the node is replaced by the result of the block associated to the rule. Otherwise the node is accepted as is into the result tree.

This is almost what you would generally do with a tree visitor, except that you can match several levels of the tree at once.

As a consequence of this, the resulting tree will contain pieces of the original tree and new pieces. Most likely, you will want to transform the original tree wholly, so this isn't a problem.

You will not be able to create a loop, given that each node will be replaced only once and then left alone. This means that the results of a replacement will not be acted upon.

Example:

    class Example < Parslet::Transform
      rule(:string => simple(:x)) {  # (1)
        StringLiteral.new(x)
      }
    end

A tree transform (Parslet::Transform) is defined by a set of rules. Each rule can be defined by calling rule with the pattern as argument. The block given will be called every time the rule matches somewhere in the tree given to apply. It is passed a Hash containing all the variable bindings of this pattern match.

In the above example, (1) illustrates a simple matching rule.

Let's say you want to parse matching parentheses and distill a maximum nest depth. You would probably write a parser like the one in example/parens.rb; here's the relevant part:

    rule(:balanced) {
      str('(').as(:l) >> balanced.maybe.as(:m) >> str(')').as(:r)
    }

If you now apply this to a string like '(())', you get a intermediate parse tree that looks like this:

    {
      l: '(',
      m: {
        l: '(',
        m: nil,
        r: ')'
      },
      r: ')'
    }

This parse tree is good for debugging, but what we would really like to have is just the nesting depth. This transformation rule will produce that:

    rule(:l => '(', :m => simple(:x), :r => ')') {
      # innermost :m will contain nil
      x.nil? ? 1 : x+1
    }

# Usage patterns

There are four ways of using this class. The first one is very much recommended, followed by the second one for generality. The other ones are omitted here.

Recommended usage is as follows:

    class MyTransformator < Parslet::Transform
      rule(...) { ... }
      rule(...) { ... }
      # ...
    end
    MyTransformator.new.apply(tree)

Alternatively, you can use the Transform class as follows:

    transform = Parslet::Transform.new do
      rule(...) { ... }
    end
    transform.apply(tree)

# Execution context

The execution context of action blocks differs depending on the arity of said blocks. This can be confusing. It is however somewhat intentional. You should not create fat Transform descendants containing a lot of helper methods, instead keep your AST class construction in global scope or make it available through a factory. The following piece of code illustrates usage of global scope:

    transform = Parslet::Transform.new do
      rule(...) { AstNode.new(a_variable) }
      rule(...) { Ast.node(a_variable) } # modules are nice
    end
    transform.apply(tree)

And here's how you would use a class builder (a factory):

    transform = Parslet::Transform.new do
      rule(...) { builder.add_node(a_variable) }
      rule(...) { |d| d[:builder].add_node(d[:a_variable]) }
    end
    transform.apply(tree, :builder => Builder.new)

As you can see, Transform allows you to inject local context for your rule action blocks to use.

### Public Class Methods

new(&block) click to toggle source

    # File lib/parslet/transform.rb, line 139
    def initialize(&block)
      @rules = []

      if block
        instance_eval(&block)
      end
    end

rule(expression, &block) click to toggle source

Define a rule for the transform subclass.

    # File lib/parslet/transform.rb, line 127
    def rule(expression, &block)
      @__transform_rules ||= []
      @__transform_rules << [Parslet::Pattern.new(expression), block]
    end

rules() click to toggle source

Allows accessing the class' rules

    # File lib/parslet/transform.rb, line 134
    def rules
      @__transform_rules || []
    end

### Public Instance Methods

apply(obj, context=nil) click to toggle source

Applies the transformation to a tree that is generated by Parslet::Parser or a simple parslet. Transformation will proceed down the tree, replacing parts/all of it with new objects. The resulting object will be returned.

    # File lib/parslet/transform.rb, line 164
    def apply(obj, context=nil)
      transform_elt(
        case obj
          when Hash
            recurse_hash(obj, context)
          when Array
            recurse_array(obj, context)
        else
          obj
        end,
        context
      )
    end

call_on_match(bindings, block) click to toggle source

Executes the block on the bindings obtained by Pattern#match, if such a match can be made. Depending on the arity of the given block, it is called in one of two environments: the current one or a clean toplevel environment.

If you would like the current environment preserved, please use the arity 1 variant of the block. Alternatively, you can inject a context object and call methods on it (think :ctx => self).

    # the local variable a is simulated
    t.call_on_match(:a => :b) { a }
    # no change of environment here
    t.call_on_match(:a => :b) { |d| d[:a] }

    # File lib/parslet/transform.rb, line 191
    def call_on_match(bindings, block)
      if block
        if block.arity == 1
          return block.call(bindings)
        else
          context = Context.new(bindings)
          return context.instance_eval(&block)
        end
      end
    end

recurse_array(ary, ctx) click to toggle source

@api private

    # File lib/parslet/transform.rb, line 233
    def recurse_array(ary, ctx)
      ary.map { |elt| apply(elt, ctx) }
    end

recurse_hash(hsh, ctx) click to toggle source

@api private

    # File lib/parslet/transform.rb, line 225
    def recurse_hash(hsh, ctx)
      hsh.inject({}) do |new_hsh, (k,v)|
        new_hsh[k] = apply(v, ctx)
        new_hsh
      end
    end

rule(expression, &block) click to toggle source

Defines a rule to be applied whenever apply is called on a tree. A rule is composed of two parts:

- an *expression pattern*

- a *transformation block*

    # File lib/parslet/transform.rb, line 153
    def rule(expression, &block)
      @rules << [
        Parslet::Pattern.new(expression),
        block
      ]
    end

rules() click to toggle source

Allow easy access to all rules, the ones defined in the instance and the ones predefined in a subclass definition.

    # File lib/parslet/transform.rb, line 205
    def rules
      self.class.rules + @rules
    end

transform_elt(elt, context) click to toggle source

@api private

    # File lib/parslet/transform.rb, line 211
    def transform_elt(elt, context)
      rules.each do |pattern, block|
        if bindings=pattern.match(elt, context)
          # Produces transformed value
          return call_on_match(bindings, block)
        end
      end

      # No rule matched - element is not transformed
      return elt
    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.