Class: Parslet::Transform
- Inherits:
-
Object
- Object
- Parslet::Transform
- Extended by:
- Parslet
- Includes:
- Parslet
- Defined in:
- lib/parslet/transform.rb
Overview
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.
Direct Known Subclasses
Defined Under Namespace
Classes: Context
Class Method Summary collapse
-
.rule(expression, &block) ⇒ Object
Define a rule for the transform subclass.
-
.rules ⇒ Object
Allows accessing the class’ rules.
Instance Method Summary collapse
-
#apply(obj, context = nil) ⇒ Object
Applies the transformation to a tree that is generated by Parslet::Parser or a simple parslet.
-
#call_on_match(bindings, block) ⇒ Object
Executes the block on the bindings obtained by Pattern#match, if such a match can be made.
-
#initialize(&block) ⇒ Transform
constructor
A new instance of Transform.
- #recurse_array(ary, ctx) ⇒ Object private
- #recurse_hash(hsh, ctx) ⇒ Object private
-
#rule(expression, &block) ⇒ Object
Defines a rule to be applied whenever apply is called on a tree.
-
#rules ⇒ Object
Allow easy access to all rules, the ones defined in the instance and the ones predefined in a subclass definition.
- #transform_elt(elt, context) ⇒ Object private
Methods included from Parslet
any, exp, included, match, sequence, simple, str, subtree
Constructor Details
#initialize(&block) ⇒ Transform
Returns a new instance of Transform.
139 140 141 142 143 144 145 |
# File 'lib/parslet/transform.rb', line 139 def initialize(&block) @rules = [] if block instance_eval(&block) end end |
Class Method Details
.rule(expression, &block) ⇒ Object
Define a rule for the transform subclass.
127 128 129 130 |
# File 'lib/parslet/transform.rb', line 127 def rule(expression, &block) @__transform_rules ||= [] @__transform_rules << [Parslet::Pattern.new(expression), block] end |
.rules ⇒ Object
Allows accessing the class’ rules
134 135 136 |
# File 'lib/parslet/transform.rb', line 134 def rules @__transform_rules || [] end |
Instance Method Details
#apply(obj, context = nil) ⇒ Object
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.
164 165 166 167 168 169 170 171 172 173 174 175 176 |
# 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) ⇒ Object
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] }
191 192 193 194 195 196 197 198 199 200 |
# 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) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
233 234 235 |
# File 'lib/parslet/transform.rb', line 233 def recurse_array(ary, ctx) ary.map { |elt| apply(elt, ctx) } end |
#recurse_hash(hsh, ctx) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
225 226 227 228 229 230 |
# 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) ⇒ Object
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*
153 154 155 156 157 158 |
# File 'lib/parslet/transform.rb', line 153 def rule(expression, &block) @rules << [ Parslet::Pattern.new(expression), block ] end |
#rules ⇒ Object
Allow easy access to all rules, the ones defined in the instance and the ones predefined in a subclass definition.
205 206 207 |
# File 'lib/parslet/transform.rb', line 205 def rules self.class.rules + @rules end |
#transform_elt(elt, context) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
211 212 213 214 215 216 217 218 219 220 221 |
# 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 |