Skip to content
Create a gist now

Instantly share code, notes, and snippets.

###
# 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
mixonic commented Aug 30, 2012

Sick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.