Skip to content

Commit

Permalink
Rbx proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
Josep M. Bach committed May 4, 2011
1 parent c2f8a7f commit 17f89d2
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 9 deletions.
2 changes: 1 addition & 1 deletion bin/brainfuck
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
require 'brainfuck'
Brainfuck.run File.read(ARGV.first)
Brainfuck::Main.new.main ARGV.first
5 changes: 5 additions & 0 deletions lib/brainfuck.rb
Expand Up @@ -6,6 +6,10 @@

require 'brainfuck/parser'
require 'brainfuck/interpreter'
require 'brainfuck/stages'
require 'brainfuck/compiler'
require 'brainfuck/main'
require 'brainfuck/code_loader'

module Brainfuck
class << self
Expand All @@ -15,6 +19,7 @@ def run code
code = Parser.clean code
parsed = Parser.new.parse code
ast = Interpreter.new.apply parsed
Compiler.compile_ast ast, 'file', Compiler::Print.new(true, true, true)
ast.each(&:eval)
Interpreter.stack.to_a
end
Expand Down
40 changes: 40 additions & 0 deletions lib/brainfuck/ast.rb
Expand Up @@ -2,22 +2,45 @@ module Brainfuck
module AST
class FwdNode < Struct.new(:stack)
def eval; stack.fwd ; end
def bytecode(g)
g.push_literal 1
g.push 1
g.meta_send_op_plus(0)
puts g.state.scope.methods.sort
g.set_literal g.state.scope.new_literal(:pointer).reference.slot
g.pop
end
end
class BwdNode < Struct.new(:stack)
def eval; stack.bwd ; end
def bytecode(g)
g.push 1
end
end

class IncNode < Struct.new(:stack)
def eval; stack.inc ; end
def bytecode(g)
g.push 1
end
end
class DecNode < Struct.new(:stack)
def eval; stack.dec ; end
def bytecode(g)
g.push 1
end
end
class PutsNode < Struct.new(:stack)
def eval; stack.puts ; end
def bytecode(g)
g.push 1
end
end
class GetsNode < Struct.new(:stack)
def eval; stack.gets ; end
def bytecode(g)
g.push 1
end
end
class IterationNode < Struct.new(:stack, :exp)
def eval
Expand All @@ -28,5 +51,22 @@ def eval
end
end
end
class Script < Struct.new(:exp)

def bytecode(g)
g.push 0
g.make_array 1
g.set_literal g.state.scope.new_literal(:heap).reference.slot
g.pop

g.push 0
g.set_literal g.state.scope.new_literal(:pointer).reference.slot
g.pop

exp.each do |e|
e.bytecode(g)
end
end
end
end
end
34 changes: 34 additions & 0 deletions lib/brainfuck/code_loader.rb
@@ -0,0 +1,34 @@
module Brainfuck
class CodeLoader

def self.execute_code(code, binding, from_module, print = Compiler::Print.new)
cm = Compiler.compile_for_eval(code, binding.variables,
"(eval)", 1, print)
cm.scope = binding.static_scope.dup
cm.name = :__eval__

script = Rubinius::CompiledMethod::Script.new(cm, "(eval)", true)
script.eval_binding = binding
script.eval_source = code

cm.scope.script = script

be = Rubinius::BlockEnvironment.new
be.under_context(binding.variables, cm)
be.from_eval!
be.call
end

# Takes a .py file name, compiles it if needed and executes it.
# Sets the module name to be __main__, so this should be called
# only on the main program. For loading other python modules from
# it use the load_module method.
def self.execute_file(name, compile_to = nil, print = Compiler::Print.new)
cm = Compiler.compile_if_needed(name, compile_to, print)
ss = ::Rubinius::StaticScope.new Object
code = Object.new
::Rubinius.attach_method(:__run__, cm, ss, code)
code.__run__
end
end
end
82 changes: 82 additions & 0 deletions lib/brainfuck/compiler.rb
@@ -0,0 +1,82 @@
module Brainfuck
class Compiler < Rubinius::Compiler

def self.compiled_filename(filename)
if filename =~ /.bf$/
filename + "c"
else
filename + ".compiled.bfc"
end
end

def self.always_recompile=(flag)
@always_recompile = flag
end

def self.compile_if_needed(file, output = nil, print = Print.new)
puts file.inspect
compiled = output || compiled_filename(file)
needed = @always_recompile || !File.exists?(compiled) ||
File.stat(compiled).mtime < File.stat(file).mtime
if needed
compile_file(file, compiled, print)
else
Rubinius::CodeLoader.new(compiled).load_compiled_file(compiled, 0)
end
end


def self.compile_file(file, output = nil, print = Print.new)
compiler = new :brainfuck_file, :compiled_file
parser = compiler.parser

parser.input file

compiler.generator.root = Rubinius::AST::Script
compiler.writer.name = output || compiled_filename(file)

parser.print = print
compiler.packager.print.bytecode = true if print.asm?

begin
compiler.run

rescue Exception => e
compiler_error "Error trying to compile brainfuck: #{file}", e
end
end

def self.compile_for_eval(code, variable_scope, file = "(eval)", line = 0, print = Print.new)
compiler = new :brainfuck_code, :compiled_method
parser = compiler.parser

parser.input code, file, line
compiler.generator.root = Rubinius::AST::EvalExpression
compiler.generator.variable_scope = variable_scope

parser.print = print
compiler.packager.print.bytecode = true if print.asm?

begin
compiler.run
rescue Exception => e
compiler_error "Error trying to compile brainfuck: #{file}", e
end
end

class Print < Struct.new(:sexp, :ast, :asm)
def sexp?
@sexp
end

def ast?
@ast
end

def asm?
@asm
end
end

end
end
16 changes: 8 additions & 8 deletions lib/brainfuck/interpreter.rb
Expand Up @@ -4,17 +4,17 @@ def self.stack
@@stack ||= Stack.new
end

rule(:fwd => simple(:fwd)) { AST::FwdNode.new(Interpreter.stack) }
rule(:bwd => simple(:bwd)) { AST::BwdNode.new(Interpreter.stack) }
rule(:fwd => simple(:fwd)) { AST::FwdNode.new }
rule(:bwd => simple(:bwd)) { AST::BwdNode.new }

rule(:inc => simple(:inc)) { AST::IncNode.new(Interpreter.stack) }
rule(:dec => simple(:dec)) { AST::DecNode.new(Interpreter.stack) }
rule(:inc => simple(:inc)) { AST::IncNode.new }
rule(:dec => simple(:dec)) { AST::DecNode.new }

rule(:puts => simple(:puts)) { AST::PutsNode.new(Interpreter.stack) }
rule(:gets => simple(:gets)) { AST::GetsNode.new(Interpreter.stack) }
rule(:puts => simple(:puts)) { AST::PutsNode.new }
rule(:gets => simple(:gets)) { AST::GetsNode.new }

rule(:iteration => subtree(:iteration)) { AST::IterationNode.new(Interpreter.stack, iteration) }
rule(:iteration => subtree(:iteration)) { AST::IterationNode.new(iteration) }

rule(:exp => subtree(:exp)) { exp }
rule(:exp => subtree(:exp)) { AST::Script.new(exp) }
end
end
111 changes: 111 additions & 0 deletions lib/brainfuck/main.rb
@@ -0,0 +1,111 @@
require 'brainfuck/version'

module Brainfuck

# Command line interface to Brainfuck.
#
# It should support the same command line options than the `brainfuck'
# program. And additional options specific to Brainfuck and Rubinius.
#
# But currently we only take a brainfuck source name to compile and
# run.
#
class Main

def initialize
@print = Compiler::Print.new
@compile_only = false
@evals = []
@rest = []
end

def main(argv=ARGV)
options(argv)
# return repl if @rest.empty? && @evals.empty? && !@compile_only
evals unless @evals.empty?
script unless @rest.empty?
compile if @compile_only
end

# Batch compile all brainfuck files given as arguments.
def compile
@rest.each do |py|
begin
Compiler.compile_file py, nil, @print
rescue Compiler::Error => e
e.show
end
end
end

# Evaluate code given on command line
def evals
bnd = Object.new
def bnd.get; binding; end
bnd = bnd.get
mod = nil
@evals.each do |code|
CodeLoader.execute_code code, bnd, mod, @print
end
end

# Run the given script if any
def script
CodeLoader.execute_file @rest.first, nil, @print
end

# # Run the Brainfuck REPL unless we were given an script
# def repl
# require 'brainfuck/repl'
# ReadEvalPrintLoop.new(@print).main
# end

# Parse command line options
def options(argv)
options = Rubinius::Options.new "Usage: brainfuck [options] [program]", 20
options.doc "Brainfuck is a Brainfuck implementation for the Rubinius VM."
options.doc "It is inteded to expose the same command line options as"
options.doc "the `brainfuck` program and some Rubinius specific options."
options.doc ""
options.doc "OPTIONS:"

options.on "-", "Read and evalute code from STDIN" do
@evals << STDIN.read
end

options.on "--print-ast", "Print the Brainfuck AST" do
@print.ast = true
end

options.on "--print-asm", "Print the Rubinius ASM" do
@print.asm = true
end

options.on "--print-sexp", "Print the Brainfuck Sexp" do
@print.sexp = true
end

options.on "--print-all", "Print Sexp, AST and Rubinius ASM" do
@print.ast = @print.asm = @print.sexp = true
end

options.on "-C", "--compile", "Just batch compile dont execute." do
@compile_only = true
end

options.on "-e", "CODE", "Execute CODE" do |e|
@evals << e
end

options.on "-h", "--help", "Display this help" do
puts options
exit 0
end

options.doc ""

@rest = options.parse(argv)
end

end
end

0 comments on commit 17f89d2

Please sign in to comment.