Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Created August 30, 2012 18:10
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save tenderlove/3536045 to your computer and use it in GitHub Desktop.
Save tenderlove/3536045 to your computer and use it in GitHub Desktop.
###
# Scheme code is translated to YARV byte code, then evaluated in the
# Ruby Virtual Machine
require 'rbconfig'
require 'dl'
require 'fiddle'
require 'strscan'
class RubyVM
class InstructionSequence
handle = DL::Handle::DEFAULT
address = handle['rb_iseq_load']
func = Fiddle::Function.new(address, [DL::TYPE_VOIDP] * 3,
DL::TYPE_VOIDP)
# This monkey patch allows us to load arbitrary byte code with
# Ruby's VM
define_singleton_method(:load) do |data, parent = nil, opt = nil|
func.call(DL.dlwrap(data), parent, opt).to_value
end
end
end
module Chicken
class Compiler
# Compile scheme source to Ruby byte code and load it as an ISeq object
def compile source
tok = Tokenizer.new source
code = []
iseq = [
"YARVInstructionSequence/SimpleDataFormat", 2, 0, 1, {},
"<compiled>", "<compiled>", nil, 1, :top, [], 0, [],
code
]
_compile tok, code
code << [:leave]
RubyVM::InstructionSequence.load iseq
end
# Evaluate some scheme code by translating it to Ruby bytecode and
# evaluating the bytecode:
#
# cc = Chicken::Compiler.new
# cc.eval '(+ 20 (- 5 2))'
def eval scheme
compile(scheme).eval
end
private
# Translate one ( ... ) to ruby byte code. Recurses on inner parens.
def _compile tok, stack
tok.next_token # LPAREN
type, func = tok.next_token
loop do
case tok.peek.first
when :LPAREN then _compile tok, stack
when :RPAREN then break
else
stack << opcode(*tok.next_token)
end
end
stack << opcode(type, func)
end
# Determine the opcode for the token we parsed.
def opcode tok, value
case tok
when :NUM then [:putobject, value]
when :PLUS then [:opt_plus, 0]
when :MINUS then [:opt_minus, 0]
else
raise "unknown token: #{tok}"
end
end
end
class Tokenizer
def initialize source
@s = StringScanner.new source
@peek = nil
end
def peek
@peek ||= next_token
end
def next_token
return nil if @s.eos?
if @peek
x = @peek
@peek = nil
return x
end
case
when t = @s.scan(/\(/) then [:LPAREN, t]
when t = @s.scan(/\)/) then [:RPAREN, t]
when t = @s.scan(/\s+/) then next_token
when t = @s.scan(/\d+/) then [:NUM, t.to_i]
when t = @s.scan(/\+/) then [:PLUS, t]
when t = @s.scan(/\-/) then [:MINUS, t]
when t = @s.scan(/[^\s\(\)]+/) then [:IDENT, t]
end
end
end
end
if $0 == __FILE__
compiler = Chicken::Compiler.new
puts compiler.eval('(+ 10 (- 5 2))')
end
@mixonic
Copy link

mixonic commented Aug 30, 2012

Sick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment