Permalink
Browse files

Rbx proof of concept

  • Loading branch information...
1 parent c2f8a7f commit 17f89d29b99b03f62dcca567569d14e81a52dedd @txus committed May 4, 2011
Showing with 421 additions and 9 deletions.
  1. +1 −1 bin/brainfuck
  2. +5 −0 lib/brainfuck.rb
  3. +40 −0 lib/brainfuck/ast.rb
  4. +34 −0 lib/brainfuck/code_loader.rb
  5. +82 −0 lib/brainfuck/compiler.rb
  6. +8 −8 lib/brainfuck/interpreter.rb
  7. +111 −0 lib/brainfuck/main.rb
  8. +139 −0 lib/brainfuck/stages.rb
  9. +1 −0 test.bf
View
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
require 'brainfuck'
-Brainfuck.run File.read(ARGV.first)
+Brainfuck::Main.new.main ARGV.first
View
@@ -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
@@ -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
View
@@ -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
@@ -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
@@ -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
View
@@ -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
@@ -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
View
@@ -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
Oops, something went wrong.

0 comments on commit 17f89d2

Please sign in to comment.