public
Last active

  • Download Gist
chicken.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
###
# 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

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.