Permalink
Browse files

Interpreter works.

Now to wire it all up.
  • Loading branch information...
1 parent 83bb1b1 commit e737939b158af9124a1a3bac49c0792331da989b @steveklabnik committed Dec 9, 2012
Showing with 308 additions and 3 deletions.
  1. +2 −0 Rakefile
  2. +3 −0 lib/mojikun/errors.rb
  3. +73 −0 lib/mojikun/interpreter.rb
  4. +42 −0 lib/mojikun/runtime.rb
  5. +131 −0 test/interpreter_test.rb
  6. +46 −3 test/runtime_test.rb
  7. +11 −0 test/test_helper.rb
View
@@ -8,3 +8,5 @@ Rake::TestTask.new do |t|
t.ruby_opts = ['-r./test/test_helper.rb']
t.verbose = true
end
+
+task :default => :test
View
@@ -0,0 +1,3 @@
+module Mojikun
+ MismatchedBracketError = Class.new(StandardError)
+end
View
@@ -0,0 +1,73 @@
+require 'io/console'
+
+require 'mojikun/errors'
+
+require 'mojikun/point_right_node'
+require 'mojikun/point_left_node'
+require 'mojikun/thumbs_up_node'
+require 'mojikun/thumbs_down_node'
+require 'mojikun/display_node'
+require 'mojikun/save_node'
+require 'mojikun/loop_node'
+require 'mojikun/end_loop_node'
+
+
+module Mojikun
+ class Interpreter
+ attr_reader :runtime, :loop_map
+
+ def initialize(runtime)
+ @runtime = runtime
+ @loop_map = {}
+ end
+
+ def evaluate(ast)
+ # first, update the loop map so we know positions of LoopNode/EndLoopNode
+ loop_counter = 0
+ loop_stack = []
+
+ ast.each_with_index do |node, index|
+ case node
+ when LoopNode
+ loop_stack << index
+ when EndLoopNode
+ raise MismatchedBracketError if loop_stack.empty?
+
+ @loop_map[loop_stack.pop] = index
+ end
+ end
+
+ raise MismatchedBracketError unless loop_stack.empty?
+
+ # then, evaluate the nodes
+ until(runtime.instruction_pointer == ast.count)
+ case ast[runtime.instruction_pointer]
+ when PointRightNode
+ runtime.increment_data_pointer
+ when PointLeftNode
+ runtime.decrement_data_pointer
+ when ThumbsUpNode
+ runtime.increment_data
+ when ThumbsDownNode
+ runtime.decrement_data
+ when DisplayNode
+ STDOUT.print(runtime.current_data.chr)
+ when SaveNode
+ runtime.set_current_data(STDIN.getch.ord)
+ when LoopNode
+ if runtime.current_data == 0
+ runtime.set_instruction_pointer(@loop_map[runtime.instruction_pointer])
+ end
+ when EndLoopNode
+ if runtime.current_data != 0
+ runtime.set_instruction_pointer(@loop_map.key(runtime.instruction_pointer))
+ end
+ end
+
+ runtime.increment_instruction_pointer
+ end
+
+ self
+ end
+ end
+end
View
@@ -9,5 +9,47 @@ def initialize
@data_pointer = 0
@data = [0] * 30000 # original interpreter had 30,000 cells
end
+
+ def increment_data_pointer
+ @data_pointer += 1
+ self
+ end
+
+ def decrement_data_pointer
+ @data_pointer -= 1
+ self
+ end
+
+ def current_data
+ data[data_pointer]
+ end
+
+ def set_current_data(val)
+ data[data_pointer] = val
+
+ self
+ end
+
+ def increment_data
+ data[data_pointer] += 1
+ self
+ end
+
+ def decrement_data
+ data[data_pointer] -= 1
+ self
+ end
+
+ def set_instruction_pointer(num)
+ @instruction_pointer = num
+
+ self
+ end
+
+ def increment_instruction_pointer
+ @instruction_pointer += 1
+
+ self
+ end
end
end
View
@@ -0,0 +1,131 @@
+require 'mojikun/interpreter'
+require 'mojikun/runtime'
+
+require 'minitest/autorun'
+
+class InterpreterTest < Minitest::Unit::TestCase
+ def setup
+ @runtime = Mojikun::Runtime.new
+ @interpreter = Mojikun::Interpreter.new(@runtime)
+ end
+
+ def test_runtime
+ assert_equal @runtime, @interpreter.runtime
+ end
+
+ def test_eval_point_right_node
+ ast = [Mojikun::PointRightNode.new]
+ assert_equal 1, @interpreter.evaluate(ast).runtime.data_pointer
+ end
+
+ def test_eval_point_left_node
+ ast = [Mojikun::PointLeftNode.new]
+ assert_equal -1, @interpreter.evaluate(ast).runtime.data_pointer
+ end
+
+ def test_eval_thumbs_up_node
+ ast = [Mojikun::ThumbsUpNode.new]
+ assert_equal 1, @interpreter.evaluate(ast).runtime.current_data
+ end
+
+ def test_eval_thumbs_down_node
+ ast = [Mojikun::ThumbsDownNode.new]
+ assert_equal -1, @interpreter.evaluate(ast).runtime.current_data
+ end
+
+ def test_eval_display_node
+ mock = MiniTest::Mock.new.expect(:print, nil, ["H"])
+
+ redefine_constant("STDOUT", mock) do
+
+ ast = 72.times.collect { Mojikun::ThumbsUpNode.new }
+ ast << Mojikun::DisplayNode.new
+
+ @interpreter.evaluate(ast)
+ end
+
+ mock.verify
+ end
+
+ def test_eval_save_node
+ STDIN.stub :getch, "a" do
+ ast = [Mojikun::SaveNode.new]
+
+ @interpreter.evaluate(ast)
+
+ assert_equal 97, @interpreter.runtime.current_data
+ end
+ end
+
+ def test_eval_loop_node
+ ast = [Mojikun::LoopNode.new, Mojikun::ThumbsUpNode.new, Mojikun::EndLoopNode.new]
+ @interpreter.evaluate(ast)
+
+ assert_equal 0, @interpreter.runtime.current_data
+ end
+
+ def test_eval_end_loop_node
+ # so we enter the loop
+ ast = [Mojikun::ThumbsUpNode.new,
+ Mojikun::LoopNode.new,
+
+ # move right once, increment, move left
+ Mojikun::PointRightNode.new,
+ Mojikun::ThumbsUpNode.new,
+ Mojikun::PointLeftNode.new,
+
+ # decrement so we leave the loop
+ Mojikun::ThumbsDownNode.new,
+ Mojikun::EndLoopNode.new,
+
+ # move right so we can see the 1 we did in the loop
+ Mojikun::PointRightNode.new,
+ ]
+ @interpreter.evaluate(ast)
+
+ assert_equal 1, @interpreter.runtime.current_data
+ end
+
+ def test_loop_map
+ ast = [Mojikun::LoopNode.new, Mojikun::EndLoopNode.new]
+
+ @interpreter.evaluate(ast)
+
+ map = {}
+ map[0] = 1
+
+ assert_equal map, @interpreter.loop_map
+ end
+
+ def test_nested_loop_map
+ ast = [Mojikun::LoopNode.new, Mojikun::LoopNode.new, Mojikun::EndLoopNode.new, Mojikun::EndLoopNode.new]
+
+ @interpreter.evaluate(ast)
+
+ map = {}
+ map[0] = 3
+ map[1] = 2
+
+ assert_equal map, @interpreter.loop_map
+ end
+
+ def test_uneven_loop
+ ast = [Mojikun::LoopNode.new]
+ begin
+ @interpreter.evaluate(ast)
+ flunk "Did not throw MismatchedBracketError"
+ rescue Mojikun::MismatchedBracketError
+ pass
+ end
+ end
+
+ def test_uneven_end_loop
+ ast = [Mojikun::EndLoopNode.new]
+ begin
+ @interpreter.evaluate(ast)
+ flunk "Did not throw MismatchedBracketError"
+ rescue Mojikun::MismatchedBracketError
+ pass
+ end
+ end
+end
View
@@ -3,15 +3,58 @@
require 'minitest/autorun'
class RuntimeTest < MiniTest::Unit::TestCase
+ def setup
+ @runtime = Mojikun::Runtime.new
+ end
+
def test_instruction_pointer
- assert_equal 0, Mojikun::Runtime.new.instruction_pointer
+ assert_equal 0, @runtime.instruction_pointer
end
def test_data_pointer
- assert_equal 0, Mojikun::Runtime.new.data_pointer
+ assert_equal 0, @runtime.data_pointer
end
def test_data
- assert_equal [0] * 30000, Mojikun::Runtime.new.data
+ assert_equal [0] * 30000, @runtime.data
+ end
+
+ def test_increment_data_pointer
+ runtime = @runtime.increment_data_pointer
+ assert_equal 1, runtime.data_pointer
+ end
+
+ def test_decrement_data_pointer
+ runtime = @runtime.decrement_data_pointer
+ assert_equal -1, runtime.data_pointer
+ end
+
+ def test_current_data
+ assert_equal 0, @runtime.current_data
+ end
+
+ def test_increment_data
+ runtime = @runtime.increment_data
+ assert_equal 1, runtime.current_data
+ end
+
+ def test_decrement_data
+ runtime = @runtime.decrement_data
+ assert_equal -1, runtime.current_data
+ end
+
+ def test_set_current_data
+ runtime = @runtime.set_current_data(1337)
+ assert_equal 1337, runtime.current_data
+ end
+
+ def test_set_instruction_pointer
+ runtime = @runtime.set_instruction_pointer(2)
+ assert_equal 2, runtime.instruction_pointer
+ end
+
+ def test_increment_instruction_pointer
+ runtime = @runtime.increment_instruction_pointer
+ assert_equal 1, runtime.instruction_pointer
end
end
View
@@ -0,0 +1,11 @@
+def redefine_constant(const, value)
+ orig = eval(const).dup
+
+ Object.send(:remove_const, const)
+ Object.const_set(const, value)
+
+ yield
+
+ Object.send(:remove_const, const)
+ Object.const_set(const, orig)
+end

0 comments on commit e737939

Please sign in to comment.