| ### | |
| # 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 |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Show comment
Hide comment
mixonic
commented
Aug 30, 2012
|
Sick. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sick.