vendored parslet, deemed stable enough and better without dependency
This commit is contained in:
236
lib/parslet/transform.rb
Normal file
236
lib/parslet/transform.rb
Normal file
@@ -0,0 +1,236 @@
|
||||
|
||||
require 'parslet/pattern'
|
||||
|
||||
# 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.
|
||||
#
|
||||
class Parslet::Transform
|
||||
# FIXME: Maybe only part of it? Or maybe only include into constructor
|
||||
# context?
|
||||
include Parslet
|
||||
|
||||
class << self
|
||||
# FIXME: Only do this for subclasses?
|
||||
include Parslet
|
||||
|
||||
# Define a rule for the transform subclass.
|
||||
#
|
||||
def rule(expression, &block)
|
||||
@__transform_rules ||= []
|
||||
@__transform_rules << [Parslet::Pattern.new(expression), block]
|
||||
end
|
||||
|
||||
# Allows accessing the class' rules
|
||||
#
|
||||
def rules
|
||||
@__transform_rules || []
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(&block)
|
||||
@rules = []
|
||||
|
||||
if block
|
||||
instance_eval(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# 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*
|
||||
#
|
||||
def rule(expression, &block)
|
||||
@rules << [
|
||||
Parslet::Pattern.new(expression),
|
||||
block
|
||||
]
|
||||
end
|
||||
|
||||
# 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.
|
||||
#
|
||||
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
|
||||
|
||||
# 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] }
|
||||
#
|
||||
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
|
||||
|
||||
# Allow easy access to all rules, the ones defined in the instance and the
|
||||
# ones predefined in a subclass definition.
|
||||
#
|
||||
def rules
|
||||
self.class.rules + @rules
|
||||
end
|
||||
|
||||
# @api private
|
||||
#
|
||||
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
|
||||
|
||||
# @api private
|
||||
#
|
||||
def recurse_hash(hsh, ctx)
|
||||
hsh.inject({}) do |new_hsh, (k,v)|
|
||||
new_hsh[k] = apply(v, ctx)
|
||||
new_hsh
|
||||
end
|
||||
end
|
||||
# @api private
|
||||
#
|
||||
def recurse_array(ary, ctx)
|
||||
ary.map { |elt| apply(elt, ctx) }
|
||||
end
|
||||
end
|
||||
|
||||
require 'parslet/context'
|
Reference in New Issue
Block a user