turing_tarpit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ ## 1.0.0
2
+
3
+ Initial Release
@@ -0,0 +1,40 @@
1
+ # Turing Tarpit, A Brainfuck Interpreter
2
+
3
+ This was built as a demo application for Practicing Ruby Issue 3.3. You can
4
+ subscribe to the journal at: http://practicingruby.com
5
+
6
+ ## Using Turing Tarpit
7
+
8
+ You need Ruby 1.9.3 to make this project work. It might be possible to get the
9
+ code running using 1.9.2 + the io-console gem, but that's completely untested
10
+ currently.
11
+
12
+ Turing Tarpit can be installed as a gem via rubygems.org, but if you're thinking
13
+ about hacking on it, the easiest way to try it out is to run the "Hello World"
14
+ program included in this repository:
15
+
16
+ $ ruby bin/turing_tarpit test/fixtures/hello.bf
17
+
18
+ There is also a Rot13 example (test/fixtures/rot13.bf), but be warned: You
19
+ need to know how to enter null characters to be able to cleanly exit it.
20
+ In OS X terminal, this can be done by pressing Ctrl+Shift+2.
21
+
22
+ Both examples are based on the code in the
23
+ [Brainfuck Wikipedia article](http://en.wikipedia.org/wiki/Brainfuck).
24
+
25
+ ## Running the tests
26
+
27
+ Before sending pull requests, please make sure all tests pass.
28
+
29
+ $ bundle
30
+ $ ruby test/suite.rb
31
+
32
+ ## License
33
+
34
+ Copyright (c) 2011 Gregory Brown
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
39
+
40
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/turing_tarpit"
4
+
5
+ interpreter = TuringTarpit::Interpreter.new(ARGF.read)
6
+
7
+ TuringTarpit::Evaluator.run(interpreter)
@@ -0,0 +1,10 @@
1
+ require "io/console"
2
+
3
+ require_relative 'turing_tarpit/version'
4
+ require_relative 'turing_tarpit/interpreter'
5
+ require_relative 'turing_tarpit/evaluator'
6
+
7
+ module TuringTarpit
8
+ PointerBoundaryError = Class.new(StandardError)
9
+ InvalidValue = Class.new(StandardError)
10
+ end
@@ -0,0 +1,70 @@
1
+ module TuringTarpit
2
+ class Evaluator
3
+ CELL_SIZE = 256
4
+
5
+ def self.run(interpreter)
6
+ evaluator = new
7
+
8
+ loop do
9
+ if operation = interpreter.next_operation(evaluator.cell_value)
10
+ evaluator.send(operation)
11
+ end
12
+ end
13
+ end
14
+
15
+ def initialize
16
+ self.pointer_position = 0
17
+ self.cells = []
18
+ end
19
+
20
+ attr_reader :pointer_position
21
+
22
+ def cell_value
23
+ cells[pointer_position] ||= 0
24
+ end
25
+
26
+ def cell_value=(value)
27
+ raise InvalidValue unless valid_cell_value?(value)
28
+
29
+ cells[pointer_position] = value
30
+ end
31
+
32
+ def increment_cell_value
33
+ self.cell_value = (cell_value + 1) % CELL_SIZE
34
+ end
35
+
36
+ def decrement_cell_value
37
+ self.cell_value = (cell_value - 1) % CELL_SIZE
38
+ end
39
+
40
+ def increment_pointer
41
+ self.pointer_position = pointer_position + 1
42
+ end
43
+
44
+ def decrement_pointer
45
+ raise PointerBoundaryError unless pointer_position > 0
46
+
47
+ self.pointer_position = pointer_position - 1
48
+ end
49
+
50
+ def output_cell_value
51
+ putc(cell_value)
52
+ end
53
+
54
+ def input_cell_value
55
+ value = $stdin.getch.ord
56
+ return if value.zero?
57
+
58
+ self.cell_value = value
59
+ end
60
+
61
+ private
62
+
63
+ attr_accessor :cells, :parser
64
+ attr_writer :pointer_position
65
+
66
+ def valid_cell_value?(value)
67
+ value.kind_of?(Integer) && value.between?(0,CELL_SIZE-1)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,79 @@
1
+ module TuringTarpit
2
+ class Interpreter
3
+ FORWARD_JUMP = "["
4
+ BACKWARD_JUMP = "]"
5
+
6
+ OPERATIONS = { "+" => :increment_cell_value,
7
+ "-" => :decrement_cell_value,
8
+ ">" => :increment_pointer,
9
+ "<" => :decrement_pointer,
10
+ "." => :output_cell_value,
11
+ "," => :input_cell_value }
12
+
13
+ def initialize(chars)
14
+ self.chars = chars
15
+ self.index = -1
16
+ end
17
+
18
+ def next_operation(cell_value)
19
+ case next_char
20
+ when FORWARD_JUMP
21
+ if cell_value.zero?
22
+ jump_forward
23
+ else
24
+ skip_while(FORWARD_JUMP)
25
+ end
26
+ when BACKWARD_JUMP
27
+ if cell_value.zero?
28
+ skip_while(BACKWARD_JUMP)
29
+ else
30
+ jump_back
31
+ end
32
+ end
33
+
34
+ OPERATIONS[current_char]
35
+ end
36
+
37
+ private
38
+
39
+ def current_char
40
+ chars[index]
41
+ end
42
+
43
+ def next_char
44
+ raise StopIteration if chars.length == index + 1
45
+
46
+ self.index = index + 1
47
+ current_char
48
+ end
49
+
50
+ def skip_while(char)
51
+ next_char until current_char != char
52
+ end
53
+
54
+ def jump_forward
55
+ jump(FORWARD_JUMP, BACKWARD_JUMP, 1)
56
+ next_char
57
+ end
58
+
59
+ def jump_back
60
+ jump(BACKWARD_JUMP, FORWARD_JUMP, -1)
61
+ next_char
62
+ end
63
+
64
+ def jump(from, to, step)
65
+ counter = 1
66
+ until counter == 0
67
+ self.index = index + step
68
+ case chars[index]
69
+ when from
70
+ counter += 1
71
+ when to
72
+ counter -= 1
73
+ end
74
+ end
75
+ end
76
+
77
+ attr_accessor :chars, :index
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module TuringTarpit
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,21 @@
1
+ +++++ +++++ initialize counter (cell #0) to 10
2
+ [ use loop to set the next four cells to 70/100/30/10
3
+ > +++++ ++ add 7 to cell #1
4
+ > +++++ +++++ add 10 to cell #2
5
+ > +++ add 3 to cell #3
6
+ > + add 1 to cell #4
7
+ <<<< - decrement counter (cell #0)
8
+ ]
9
+ > ++ . print 'H'
10
+ > + . print 'e'
11
+ +++++ ++ . print 'l'
12
+ . print 'l'
13
+ +++ . print 'o'
14
+ > ++ . print ' '
15
+ << +++++ +++++ +++++ . print 'W'
16
+ > . print 'o'
17
+ +++ . print 'r'
18
+ ----- - . print 'l'
19
+ ----- --- . print 'd'
20
+ > + . print '!'
21
+ > . print '\n'
@@ -0,0 +1,28 @@
1
+ -,+[ Read first character and start outer character reading loop
2
+ -[ Skip forward if character is 0
3
+ >>++++[>++++++++<-] Set up divisor (32) for division loop
4
+ (MEMORY LAYOUT: dividend copy remainder divisor quotient zero zero)
5
+ <+<-[ Set up dividend (x minus 1) and enter division loop
6
+ >+>+>-[>>>] Increase copy and remainder / reduce divisor / Normal case: skip forward
7
+ <[[>+<-]>>+>] Special case: move remainder back to divisor and increase quotient
8
+ <<<<<- Decrement dividend
9
+ ] End division loop
10
+ ]>>>[-]+ End skip loop; zero former divisor and reuse space for a flag
11
+ >--[-[<->+++[-]]]<[ Zero that flag unless quotient was 2 or 3; zero quotient; check flag
12
+ ++++++++++++<[ If flag then set up divisor (13) for second division loop
13
+ (MEMORY LAYOUT: zero copy dividend divisor remainder quotient zero zero)
14
+ >-[>+>>] Reduce divisor; Normal case: increase remainder
15
+ >[+[<+>-]>+>>] Special case: increase remainder / move it back to divisor / increase quotient
16
+ <<<<<- Decrease dividend
17
+ ] End division loop
18
+ >>[<+>-] Add remainder back to divisor to get a useful 13
19
+ >[ Skip forward if quotient was 0
20
+ -[ Decrement quotient and skip forward if quotient was 1
21
+ -<<[-]>> Zero quotient and divisor if quotient was 2
22
+ ]<<[<<->>-]>> Zero divisor and subtract 13 from copy if quotient was 1
23
+ ]<<[<<+>>-] Zero divisor and add 13 to copy if quotient was 0
24
+ ] End outer skip loop (jump to here if ((character minus 1)/32) was not 2 or 3)
25
+ <[-] Clear remainder from first division if second division was skipped
26
+ <.[-] Output ROT13ed character from copy and clear it
27
+ <-,+ Read next character
28
+ ] End character reading loop
@@ -0,0 +1,8 @@
1
+ require "minitest/autorun"
2
+ require_relative "../lib/turing_tarpit"
3
+
4
+ module MiniTest
5
+ class Spec
6
+ alias insist lambda
7
+ end
8
+ end
@@ -0,0 +1,36 @@
1
+ require "stringio"
2
+ require_relative "../helper"
3
+
4
+ describe "Evaluator" do
5
+ before do
6
+ $stdin = StringIO.new
7
+
8
+ def $stdin.getch
9
+ getc
10
+ end
11
+ end
12
+
13
+ it "must successfully run hello_world.bf" do
14
+ insist { bf_eval("hello.bf") }.must_output("Hello World!\n")
15
+ end
16
+
17
+ it "must successfully run rot13.bf" do
18
+ provide_input("Uryyb Jbeyq!\n\0")
19
+ insist { bf_eval("rot13.bf") }.must_output("Hello World!\n")
20
+ end
21
+
22
+ after do
23
+ $stdin = STDIN
24
+ end
25
+
26
+ def provide_input(text)
27
+ $stdin << text
28
+ $stdin.rewind
29
+ end
30
+
31
+ def bf_eval(filename)
32
+ src_file = "#{File.dirname(__FILE__)}/../fixtures/#{filename}"
33
+ interpreter = TuringTarpit::Interpreter.new(File.read(src_file))
34
+ TuringTarpit::Evaluator.run(interpreter)
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require_relative "units/interpreter_test"
5
+ require_relative "units/evaluator_test"
6
+ require_relative "integration/evaluator_test"
@@ -0,0 +1,112 @@
1
+ require_relative "../helper"
2
+
3
+ describe "Evaluator" do
4
+ it "must have an initial cell value of 0" do
5
+ evaluator = TuringTarpit::Evaluator.new
6
+ evaluator.cell_value.must_equal(0)
7
+ end
8
+
9
+ it "must be able to set the cell value at the current position" do
10
+ evaluator = TuringTarpit::Evaluator.new
11
+
12
+ evaluator.cell_value = 20
13
+
14
+ evaluator.cell_value.must_equal(20)
15
+ end
16
+
17
+ it "must restrict cell values to integers between 0 and 255" do
18
+ evaluator = TuringTarpit::Evaluator.new
19
+
20
+ insist { evaluator.cell_value = 1.5 }.
21
+ must_raise(TuringTarpit::InvalidValue)
22
+
23
+ insist { evaluator.cell_value = -1 }.
24
+ must_raise(TuringTarpit::InvalidValue)
25
+
26
+ insist { evaluator.cell_value = 256 }.must_raise(TuringTarpit::InvalidValue)
27
+ end
28
+
29
+ it "must be able to increment the cell value at the current position" do
30
+ evaluator = TuringTarpit::Evaluator.new
31
+
32
+
33
+ evaluator.increment_cell_value
34
+ evaluator.cell_value.must_equal(1)
35
+
36
+ evaluator.increment_cell_value
37
+ evaluator.cell_value.must_equal(2)
38
+ end
39
+
40
+ it "must be able to decrement the cell value at the current position" do
41
+ evaluator = TuringTarpit::Evaluator.new
42
+
43
+ 5.times { evaluator.increment_cell_value }
44
+ 2.times { evaluator.decrement_cell_value }
45
+
46
+ evaluator.cell_value.must_equal(3)
47
+
48
+ evaluator.decrement_cell_value
49
+
50
+ evaluator.cell_value.must_equal(2)
51
+ end
52
+
53
+ it "must have an initial pointer position of 0" do
54
+ evaluator = TuringTarpit::Evaluator.new
55
+ evaluator.pointer_position.must_equal(0)
56
+ end
57
+
58
+ it "must be able to increment the pointer position" do
59
+ evaluator = TuringTarpit::Evaluator.new
60
+
61
+ evaluator.increment_pointer
62
+
63
+ evaluator.pointer_position.must_equal(1)
64
+
65
+ evaluator.increment_pointer
66
+ evaluator.pointer_position.must_equal(2)
67
+ end
68
+
69
+ it "must be able to decrement the pointer position" do
70
+ evaluator = TuringTarpit::Evaluator.new
71
+ 3.times { evaluator.increment_pointer }
72
+ evaluator.decrement_pointer
73
+
74
+ evaluator.pointer_position.must_equal(2)
75
+
76
+ evaluator.decrement_pointer
77
+ evaluator.pointer_position.must_equal(1)
78
+ end
79
+
80
+ it "must raise an error if decrementing when pointer position is 0" do
81
+ evaluator = TuringTarpit::Evaluator.new
82
+
83
+ insist { evaluator.decrement_pointer }
84
+ .must_raise(TuringTarpit::PointerBoundaryError)
85
+ end
86
+
87
+ it "must treat cell values independently" do
88
+ evaluator = TuringTarpit::Evaluator.new
89
+ evaluator.increment_cell_value
90
+
91
+ evaluator.cell_value.must_equal(1)
92
+
93
+ evaluator.increment_pointer
94
+ evaluator.cell_value.must_equal(0)
95
+
96
+ 4.times { evaluator.increment_cell_value }
97
+ evaluator.cell_value.must_equal(4)
98
+
99
+ evaluator.decrement_pointer
100
+ evaluator.cell_value.must_equal(1)
101
+ end
102
+
103
+ it "must overflow the cell value gracefully" do
104
+ evaluator = TuringTarpit::Evaluator.new
105
+
106
+ evaluator.decrement_cell_value
107
+ evaluator.cell_value.must_equal(255)
108
+
109
+ evaluator.increment_cell_value
110
+ evaluator.cell_value.must_equal(0)
111
+ end
112
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "../helper"
2
+
3
+ describe "Interpreter" do
4
+ it "must be able to process a program with no jumps" do
5
+ interpreter = TuringTarpit::Interpreter.new("+-<>.,")
6
+
7
+ operations = [:increment_cell_value,
8
+ :decrement_cell_value,
9
+ :decrement_pointer,
10
+ :increment_pointer,
11
+ :output_cell_value,
12
+ :input_cell_value]
13
+
14
+ operations.each do |sym|
15
+ interpreter.next_operation(0).must_equal(sym)
16
+ end
17
+
18
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
19
+ end
20
+
21
+ it "must be able to process a program with a forward jump" do
22
+ interpreter = TuringTarpit::Interpreter.new("[++++]-")
23
+ interpreter.next_operation(0).must_equal(:decrement_cell_value)
24
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
25
+ end
26
+
27
+ it "must be able to process a program with a forward jump" do
28
+ interpreter = TuringTarpit::Interpreter.new("[++++]-")
29
+ interpreter.next_operation(0).must_equal(:decrement_cell_value)
30
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
31
+ end
32
+
33
+ it "must be able to process a program with a finite loop" do
34
+ interpreter = TuringTarpit::Interpreter.new("++[-]")
35
+ interpreter.next_operation(0).must_equal(:increment_cell_value)
36
+ interpreter.next_operation(1).must_equal(:increment_cell_value)
37
+ interpreter.next_operation(2).must_equal(:decrement_cell_value)
38
+ interpreter.next_operation(1).must_equal(:decrement_cell_value)
39
+
40
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
41
+ end
42
+
43
+ it "must be able to process a program with an infinite (non-empty) loop" do
44
+ interpreter = TuringTarpit::Interpreter.new("+[+]")
45
+
46
+ interpreter.next_operation(0).must_equal(:increment_cell_value)
47
+ (1..5).each do |i|
48
+ interpreter.next_operation(i).must_equal(:increment_cell_value)
49
+ end
50
+ end
51
+
52
+ it "must be able to process a nested jump" do
53
+ interpreter = TuringTarpit::Interpreter.new("[++++++[----]++].")
54
+
55
+ interpreter.next_operation(0).must_equal(:output_cell_value)
56
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
57
+ end
58
+
59
+ it "must be able to process a nested loop" do
60
+ interpreter = TuringTarpit::Interpreter.new("++++[-[.-],]")
61
+
62
+ 4.times { |i| interpreter.next_operation(i) }
63
+ interpreter.next_operation(4).must_equal(:decrement_cell_value)
64
+ interpreter.next_operation(3).must_equal(:output_cell_value)
65
+ interpreter.next_operation(3).must_equal(:decrement_cell_value)
66
+ interpreter.next_operation(2).must_equal(:output_cell_value)
67
+ interpreter.next_operation(2).must_equal(:decrement_cell_value)
68
+ interpreter.next_operation(1).must_equal(:output_cell_value)
69
+ interpreter.next_operation(1).must_equal(:decrement_cell_value)
70
+ interpreter.next_operation(0).must_equal(:input_cell_value)
71
+
72
+ insist { interpreter.next_operation(0) }.must_raise(StopIteration)
73
+ end
74
+
75
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turing_tarpit
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gregory Brown
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: simplecov
16
+ requirement: &2168716700 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2168716700
25
+ description: A brainfuck interpreter
26
+ email:
27
+ - gregory.t.brown@gmail.com
28
+ executables:
29
+ - turing_tarpit
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - bin/turing_tarpit
34
+ - lib/turing_tarpit/evaluator.rb
35
+ - lib/turing_tarpit/interpreter.rb
36
+ - lib/turing_tarpit/version.rb
37
+ - lib/turing_tarpit.rb
38
+ - test/fixtures/hello.bf
39
+ - test/fixtures/rot13.bf
40
+ - test/helper.rb
41
+ - test/integration/evaluator_test.rb
42
+ - test/suite.rb
43
+ - test/units/evaluator_test.rb
44
+ - test/units/interpreter_test.rb
45
+ - README.md
46
+ - CHANGELOG
47
+ homepage: http://github.com/elm-city-craftworks/turing_tarpit
48
+ licenses: []
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: 1.9.3
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.6
65
+ requirements: []
66
+ rubyforge_project: turing_tarpit
67
+ rubygems_version: 1.8.11
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: A brainfuck interpreter
71
+ test_files: []