Skip to content

Instantly share code, notes, and snippets.

@hosiawak
Created June 12, 2011 12:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hosiawak/1021515 to your computer and use it in GitHub Desktop.
Save hosiawak/1021515 to your computer and use it in GitHub Desktop.
Rubinius AST transform by sublassing Melbourne
# The goal is to not use the regular Symbol#to_proc
# (which is to_proc method in the Symbol class)
# but rather use an Abstract Syntax Tree transformation to
# transform map(&:to_s) into map {|x| x.to_s}
# This can be achieved by using the flexible compiler architecture
# in Rubinius and is presented below.
#
# First we undefine the regular Symbol#to_proc
class Symbol
def to_proc
raise "undefined"
end
end
# Make sure it doesn't work
rbx-head :006 > [1,2].map(&:to_s)
ArgumentError: Unable to convert :to_s to a Proc
from Proc.__from_block__ at kernel/common/proc.rb:12
from { } in Object#irb_binding at (irb):6
from Rubinius::BlockEnvironment#call_on_instance at kernel/common/block_environment.rb:72
# OK, now to see what we need to transform in terms of AST
# we create the AST for map(&:to_s)
rbx-head :004 > Rubinius::Melbourne.parse_string("map(&:to_s)").ascii_graph
Send
@name: map
@privately: true
@check_for_local: false
@vcall_style: false
@line: 1
@receiver: \
Self
@line: 1
@block: \
BlockPass
@line: 1
@body: \
SymbolLiteral
@value: to_s
@line: 1
# and AST for our desired form: map {|x| x.to_s}
rbx-head :005 > Rubinius::Melbourne.parse_string("map {|x| x.to_s}").ascii_graph
Send
@name: map
@privately: true
@check_for_local: false
@vcall_style: false
@line: 1
@receiver: \
Self
@line: 1
@block: \
Iter
@line: 1
@arguments: \
IterArguments
@arity: 1
@optional: 0
@required_args: 1
@splat_index: nil
@splat: nil
@prelude: single
@line: 1
@block: nil
@arguments: \
LocalVariableAssignment
@value: nil
@name: x
@variable: nil
@line: 1
@body: \
Send
@name: to_s
@privately: false
@check_for_local: false
@vcall_style: false
@block: nil
@line: 1
@receiver: \
LocalVariableAccess
@name: x
@variable: nil
@line: 1
# Then we define a custom processor for AST::BlockPass
# which will transform map(&:to_s) into map {|x| x.to_s}
# directly on the AST
class Macros < Melbourne
def process_block_pass(line, method_send, body)
if body.class == AST::SymbolLiteral
# Symbol#to_proc
name = body.value
arguments = AST::IterArguments.new(line, AST::LocalVariableAssignment.new(line, :x, nil))
receiver = AST::LocalVariableAccess.new line, :x
body = AST::Send.new line, receiver, body.value
iter = AST::Iter.new line, arguments, body
super(line, method_send, iter)
else
super
end
end
end
# We can try it out first:
rbx-head :006 > Rubinius::Macros.parse_string("map(&:to_s)").ascii_graph
Send
@name: map
@privately: true
@check_for_local: false
@vcall_style: false
@line: 1
@receiver: \
Self
@line: 1
@block: \
Iter
@line: 1
@arguments: \
IterArguments
@arity: 1
@optional: 0
@required_args: 1
@splat_index: nil
@splat: nil
@prelude: single
@line: 1
@block: nil
@arguments: \
IterArguments
@arity: 1
@optional: 0
@required_args: 1
@splat_index: nil
@splat: nil
@prelude: single
@line: 1
@block: nil
@arguments: \
LocalVariableAssignment
@value: nil
@name: x
@variable: nil
@line: 1
@body: \
Send
@name: to_s
@privately: false
@check_for_local: false
@vcall_style: false
@block: nil
@line: 1
@receiver: \
LocalVariableAccess
@name: x
@variable: nil
@line: 1
# Woohoo, we can see that our map(&:to_s) AST was transformed into the desired map {|x| x.to_s} form.
# Does it work ?, Let's see:
rbx-head :007 > [1,2].map(&:to_s)
=> ["1", "2"]
:))
If you want to learn more read about the bytecode compiler here:
http://rubini.us/doc/en/bytecode-compiler/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment