brainfucktt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Ryan Scott Lewis <ryan@rynet.us>.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,97 @@
1
+ # Brainfucktt
2
+
3
+ A [Brainfuck][brainfuck] interpreter built in [Ruby][ruby] using [Treetop][treetop].
4
+
5
+ Brainfuck is an eight-instruction turing-clomplete programming language created in 1993
6
+ by Urban Müller, based on the more formal programming language [P′′][p''] created by Corrado
7
+ Böhm in 1964.
8
+
9
+ It is designed to challenge and amuse programmers, and is not made to be suitable for
10
+ practical use.
11
+
12
+ ## Install
13
+
14
+ ### Bundler: `gem 'brainfucktt'`
15
+
16
+ ### RubyGems: `gem install brainfucktt`
17
+
18
+ ## Brainfuck Instructions
19
+
20
+ `>` Increment the data pointer (to point to the next cell to the right).
21
+
22
+ `<` Decrement the data pointer (to point to the next cell to the left).
23
+
24
+ `+` Increment (increase by one) the byte at the data pointer.
25
+
26
+ `-` Decrement (decrease by one) the byte at the data pointer.
27
+
28
+ `.` Output the byte at the data pointer as an ASCII encoded character.
29
+
30
+ `,` Accept one byte of input, storing its value in the byte at the data pointer.
31
+
32
+ `[` If the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching `]` command.
33
+
34
+ `]` If the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching `[` command.
35
+
36
+ ### Comments
37
+
38
+ Any character besides one of the 8 instructions above is not parsed and will be regarded as a comment.
39
+
40
+ ## Usage
41
+
42
+ ### Running
43
+
44
+ ```ruby
45
+ require 'brainfucktt'
46
+
47
+ # "Hello World!" written in Brainfuck
48
+ code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
49
+
50
+ Brainfucktt.run(code)
51
+ ```
52
+
53
+ ### Parsing
54
+
55
+ ```ruby
56
+ require 'brainfucktt'
57
+
58
+ # "Hello World!" written in Brainfuck
59
+ code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
60
+ parser = Brainfucktt.parse(code)
61
+
62
+ # Print out the AST of the code
63
+ p parser.tree
64
+
65
+ # Run the code within Ruby
66
+ parser.run
67
+ ```
68
+
69
+ ### StringIO
70
+
71
+ Sometimes you do now want to use STDIN or STDOUT for the I/O of the Brainfuck program.
72
+
73
+ To do that, you must use the stdlib `stringio`:
74
+
75
+ ```ruby
76
+ require 'brainfucktt'
77
+ require 'stringio'
78
+
79
+ # "Hello World!" written in Brainfuck
80
+ code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
81
+
82
+ output = StringIO.new
83
+ Brainfucktt.run(code, output: output)
84
+
85
+ p output.string # => "Hello World!\n"
86
+ ```
87
+
88
+ ## Copyright
89
+
90
+ Copyright © 2012 Ryan Scott Lewis <ryan@rynet.us>.
91
+
92
+ The MIT License (MIT) - See LICENSE for further details.
93
+
94
+ [brainfuck]: http://www.muppetlabs.com/~breadbox/bf/
95
+ [ruby]: http://ruby-lang.org
96
+ [treetop]: http://treetop.rubyforge.org
97
+ [p'']: http://en.wikipedia.org/wiki/P%E2%80%B2%E2%80%B2
@@ -0,0 +1,62 @@
1
+ require 'pathname'
2
+
3
+ def require_task(path)
4
+ begin
5
+ require path
6
+
7
+ yield
8
+ rescue LoadError
9
+ puts '', "Could not load '#{path}'.", 'Try to `rake gem:spec` and `bundle install` and try again.', ''
10
+ end
11
+ end
12
+
13
+ spec = Gem::Specification.new do |s|
14
+
15
+ # Variables
16
+ s.name = 'brainfucktt'
17
+ s.author = 'Ryan Scott Lewis'
18
+ s.email = 'ryan@rynet.us'
19
+ s.summary = 'A Brainfuck interpreter built in Ruby using Treetop.'
20
+
21
+ # Dependencies
22
+ s.add_dependency 'treetop', '~> 1.4'
23
+ s.add_dependency 'polyglot', '~> 0.3'
24
+ s.add_dependency 'version', '~> 1.0'
25
+ s.add_development_dependency 'at', '~> 0.1'
26
+ s.add_development_dependency 'rake', '~> 10.0'
27
+ s.add_development_dependency 'guard-rspec', '~> 2.1'
28
+ s.add_development_dependency 'guard-yard', '~> 2.0'
29
+ s.add_development_dependency 'rb-fsevent', '~> 0.9'
30
+ s.add_development_dependency 'fuubar', '~> 1.1'
31
+ s.add_development_dependency 'redcarpet', '~> 2.2.2'
32
+ s.add_development_dependency 'github-markup', '~> 0.7'
33
+
34
+ # Pragmatically set variables
35
+ s.homepage = "http://github.com/RyanScottLewis/#{s.name}"
36
+ s.version = Pathname.glob('VERSION*').first.read
37
+ s.description = Pathname.glob('README*').first.read
38
+ s.require_paths = ['lib']
39
+ s.files = `git ls-files`.lines.to_a.collect { |s| s.strip }
40
+ s.executables = `git ls-files -- bin/*`.lines.to_a.collect { |s| File.basename(s.strip) }
41
+
42
+ end
43
+
44
+ desc 'Generate the gemspec defined in this Rakefile'
45
+ task :gemspec do
46
+ Pathname.new("#{spec.name}.gemspec").open('w') { |f| f.write(spec.to_ruby) }
47
+ end
48
+
49
+ require_task 'rake/version_task' do
50
+ Rake::VersionTask.new do |t|
51
+ t.with_git_tag = true
52
+ t.with_gemspec = spec
53
+ end
54
+ end
55
+
56
+ require 'rubygems/package_task'
57
+ Gem::PackageTask.new(spec) do |t|
58
+ t.need_zip = false
59
+ t.need_tar = false
60
+ end
61
+
62
+ task :default => :gemspec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,59 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "brainfucktt"
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ryan Scott Lewis"]
9
+ s.date = "2012-12-06"
10
+ s.description = "# Brainfucktt\n\nA [Brainfuck][brainfuck] interpreter built in [Ruby][ruby] using [Treetop][treetop].\n\nBrainfuck is an eight-instruction turing-clomplete programming language created in 1993\nby Urban M\u{fc}ller, based on the more formal programming language [P\u{2032}\u{2032}][p''] created by Corrado\nB\u{f6}hm in 1964.\n\nIt is designed to challenge and amuse programmers, and is not made to be suitable for \npractical use.\n\n## Install\n\n### Bundler: `gem 'brainfucktt'`\n\n### RubyGems: `gem install brainfucktt`\n\n## Brainfuck Instructions\n\n`>` Increment the data pointer (to point to the next cell to the right).\n\n`<` Decrement the data pointer (to point to the next cell to the left).\n\n`+` Increment (increase by one) the byte at the data pointer.\n\n`-` Decrement (decrease by one) the byte at the data pointer.\n\n`.` Output the byte at the data pointer as an ASCII encoded character.\n\n`,` Accept one byte of input, storing its value in the byte at the data pointer.\n\n`[` If the byte at the data pointer is zero, then instead of moving the instruction pointer forward to the next command, jump it forward to the command after the matching `]` command.\n\n`]` If the byte at the data pointer is nonzero, then instead of moving the instruction pointer forward to the next command, jump it back to the command after the matching `[` command.\n\n### Comments\n\nAny character besides one of the 8 instructions above is not parsed and will be regarded as a comment.\n\n## Usage\n\n### Running\n\n```ruby\nrequire 'brainfucktt'\n\n# \"Hello World!\" written in Brainfuck\ncode = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\n\nBrainfucktt.run(code)\n```\n\n### Parsing\n\n```ruby\nrequire 'brainfucktt'\n\n# \"Hello World!\" written in Brainfuck\ncode = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\nparser = Brainfucktt.parse(code)\n\n# Print out the AST of the code\np parser.tree\n\n# Run the code within Ruby\nparser.run\n```\n\n### StringIO\n\nSometimes you do now want to use STDIN or STDOUT for the I/O of the Brainfuck program.\n\nTo do that, you must use the stdlib `stringio`:\n\n```ruby\nrequire 'brainfucktt'\nrequire 'stringio'\n\n# \"Hello World!\" written in Brainfuck\ncode = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\n\noutput = StringIO.new\nBrainfucktt.run(code, output: output)\n\np output.string # => \"Hello World!\\n\"\n```\n\n## Copyright\n\nCopyright \u{a9} 2012 Ryan Scott Lewis <ryan@rynet.us>.\n\nThe MIT License (MIT) - See LICENSE for further details.\n\n[brainfuck]: http://www.muppetlabs.com/~breadbox/bf/\n[ruby]: http://ruby-lang.org\n[treetop]: http://treetop.rubyforge.org\n[p'']: http://en.wikipedia.org/wiki/P%E2%80%B2%E2%80%B2"
11
+ s.email = "ryan@rynet.us"
12
+ s.files = ["Gemfile", "LICENSE", "README.md", "Rakefile", "VERSION", "brainfucktt.gemspec", "examples/hello_world.rb", "examples/hello_world_with_comments.rb", "examples/stringio.rb", "lib/brainfucktt.rb", "lib/brainfucktt/byte.rb", "lib/brainfucktt/conversion_helpers.rb", "lib/brainfucktt/data.rb", "lib/brainfucktt/errors.rb", "lib/brainfucktt/language.rb", "lib/brainfucktt/language/decrement_byte.rb", "lib/brainfucktt/language/decrement_pointer.rb", "lib/brainfucktt/language/increment_byte.rb", "lib/brainfucktt/language/increment_pointer.rb", "lib/brainfucktt/language/input_byte.rb", "lib/brainfucktt/language/loop.rb", "lib/brainfucktt/language/output_byte.rb", "lib/brainfucktt/language/tree.rb", "lib/brainfucktt/language_parser.treetop", "lib/brainfucktt/node.rb", "lib/brainfucktt/parser.rb"]
13
+ s.homepage = "http://github.com/RyanScottLewis/brainfucktt"
14
+ s.require_paths = ["lib"]
15
+ s.rubygems_version = "1.8.24"
16
+ s.summary = "A Brainfuck interpreter built in Ruby using Treetop."
17
+
18
+ if s.respond_to? :specification_version then
19
+ s.specification_version = 3
20
+
21
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
22
+ s.add_runtime_dependency(%q<treetop>, ["~> 1.4"])
23
+ s.add_runtime_dependency(%q<polyglot>, ["~> 0.3"])
24
+ s.add_runtime_dependency(%q<version>, ["~> 1.0"])
25
+ s.add_development_dependency(%q<at>, ["~> 0.1"])
26
+ s.add_development_dependency(%q<rake>, ["~> 10.0"])
27
+ s.add_development_dependency(%q<guard-rspec>, ["~> 2.1"])
28
+ s.add_development_dependency(%q<guard-yard>, ["~> 2.0"])
29
+ s.add_development_dependency(%q<rb-fsevent>, ["~> 0.9"])
30
+ s.add_development_dependency(%q<fuubar>, ["~> 1.1"])
31
+ s.add_development_dependency(%q<redcarpet>, ["~> 2.2.2"])
32
+ s.add_development_dependency(%q<github-markup>, ["~> 0.7"])
33
+ else
34
+ s.add_dependency(%q<treetop>, ["~> 1.4"])
35
+ s.add_dependency(%q<polyglot>, ["~> 0.3"])
36
+ s.add_dependency(%q<version>, ["~> 1.0"])
37
+ s.add_dependency(%q<at>, ["~> 0.1"])
38
+ s.add_dependency(%q<rake>, ["~> 10.0"])
39
+ s.add_dependency(%q<guard-rspec>, ["~> 2.1"])
40
+ s.add_dependency(%q<guard-yard>, ["~> 2.0"])
41
+ s.add_dependency(%q<rb-fsevent>, ["~> 0.9"])
42
+ s.add_dependency(%q<fuubar>, ["~> 1.1"])
43
+ s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
44
+ s.add_dependency(%q<github-markup>, ["~> 0.7"])
45
+ end
46
+ else
47
+ s.add_dependency(%q<treetop>, ["~> 1.4"])
48
+ s.add_dependency(%q<polyglot>, ["~> 0.3"])
49
+ s.add_dependency(%q<version>, ["~> 1.0"])
50
+ s.add_dependency(%q<at>, ["~> 0.1"])
51
+ s.add_dependency(%q<rake>, ["~> 10.0"])
52
+ s.add_dependency(%q<guard-rspec>, ["~> 2.1"])
53
+ s.add_dependency(%q<guard-yard>, ["~> 2.0"])
54
+ s.add_dependency(%q<rb-fsevent>, ["~> 0.9"])
55
+ s.add_dependency(%q<fuubar>, ["~> 1.1"])
56
+ s.add_dependency(%q<redcarpet>, ["~> 2.2.2"])
57
+ s.add_dependency(%q<github-markup>, ["~> 0.7"])
58
+ end
59
+ end
@@ -0,0 +1,6 @@
1
+ require_relative '../lib/brainfucktt'
2
+
3
+ # "Hello World!" written in Brainfuck
4
+ code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
5
+
6
+ Brainfucktt.run(code)
@@ -0,0 +1,28 @@
1
+ require_relative '../lib/brainfucktt'
2
+
3
+ code = DATA.read
4
+
5
+ Brainfucktt.run(code)
6
+
7
+ __END__
8
+ +++++ +++++ initialize counter (cell #0) to 10
9
+ [ use loop to set the next four cells to 70/100/30/10
10
+ > +++++ ++ add 7 to cell #1
11
+ > +++++ +++++ add 10 to cell #2
12
+ > +++ add 3 to cell #3
13
+ > + add 1 to cell #4
14
+ <<<< - decrement counter (cell #0)
15
+ ] end loop
16
+ > ++ . print 'H'
17
+ > + . print 'e'
18
+ +++++ ++ . print 'l'
19
+ . print 'l'
20
+ +++ . print 'o'
21
+ > ++ . print ' '
22
+ << +++++ +++++ +++++ . print 'W'
23
+ > . print 'o'
24
+ +++ . print 'r'
25
+ ----- - . print 'l'
26
+ ----- --- . print 'd'
27
+ > + . print '!'
28
+ > . print '\n'
@@ -0,0 +1,11 @@
1
+ require_relative '../lib/brainfucktt'
2
+
3
+ require 'stringio'
4
+
5
+ # "Hello World!" written in Brainfuck
6
+ code = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
7
+
8
+ output = StringIO.new
9
+ Brainfucktt.run(code, output: output)
10
+
11
+ p output.string # => "Hello World!\n"
@@ -0,0 +1,17 @@
1
+ require 'pathname'
2
+ require 'forwardable'
3
+
4
+ __LIB__ ||= Pathname.new(__FILE__).join('..').expand_path
5
+ $:.unshift(__LIB__.to_s) unless $:.include?(__LIB__)
6
+
7
+ require 'brainfucktt/parser'
8
+
9
+ # A Brainfuck interpreter built in Ruby using Treetop.
10
+ module Brainfucktt
11
+ class << self
12
+ extend Forwardable
13
+
14
+ delegate [:parse, :run] => Parser
15
+
16
+ end
17
+ end
@@ -0,0 +1,104 @@
1
+ require 'brainfucktt/conversion_helpers'
2
+
3
+ module Brainfucktt
4
+
5
+ # A Byte.
6
+ class Byte
7
+ include ConversionHelpers
8
+ include Comparable
9
+
10
+ # Set the value of the Byte instance at the given offset.
11
+ #
12
+ # @param [Byte, Integer, #to_i] value
13
+ # @raise [Brainfucktt::InvalidByteError] When the given value cannot be converted into an Integer or is not between 0 and 255.
14
+ def initialize(value=0)
15
+ @value = convert_to_integer(value)
16
+ end
17
+
18
+ # Add to this Byte instance's value.
19
+ #
20
+ # @param [Byte, Integer, #to_i] value
21
+ # @raise [Brainfucktt::InvalidByteError] When the given value cannot be converted into an Integer or is not between 0 and 255.
22
+ # @return [Integer] The new value.
23
+ def +(value)
24
+ @value += convert_to_integer(value)
25
+ end
26
+
27
+ # Subtract from this Byte instance's value.
28
+ #
29
+ # @param [Byte, Integer, #to_i] value
30
+ # @raise [Brainfucktt::InvalidByteError] When the given value cannot be converted into an Integer or is not between 0 and 255.
31
+ # @return [Integer] The new value.
32
+ def -(value)
33
+ @value -= convert_to_integer(value)
34
+ end
35
+
36
+ # Compare with another Byte, Integer, String, etc..
37
+ #
38
+ # @return [nil, Integer]
39
+ def <=>(other)
40
+ @value <=> convert_to_integer(other)
41
+ end
42
+
43
+ # Check to see if the given object instance is the same instance as self.
44
+ #
45
+ # @return [true, false]
46
+ def ==(other)
47
+ self == other
48
+ end
49
+
50
+ # Check to see if the given object instance as an Integer is the same as the value of
51
+ # this Byte instance.
52
+ #
53
+ # @return [true, false]
54
+ def eql?(other)
55
+ @value == convert_to_integer(other)
56
+ end
57
+ alias_method :equal?, :eql?
58
+
59
+ # Check this Byte instance is empty.
60
+ #
61
+ # @return [true, false]
62
+ def empty?
63
+ @value == 0
64
+ end
65
+
66
+ # Return the Byte as an Integer
67
+ #
68
+ # @return [Integer]
69
+ def to_i
70
+ @value
71
+ end
72
+
73
+ # Return the Byte as a binary string.
74
+ #
75
+ # @return [String]
76
+ def to_binary
77
+ @value.to_s(2)
78
+ end
79
+
80
+ # Return the Byte as an ASCII character.
81
+ #
82
+ # @return [String]
83
+ def to_ascii
84
+ @value.chr
85
+ end
86
+ alias_method :to_s, :to_ascii
87
+
88
+ # Return the Byte as a hexadecimal string.
89
+ #
90
+ # @return [String]
91
+ def to_hex
92
+ @value.to_s(16)
93
+ end
94
+
95
+ # Return the Byte as a binary string.
96
+ #
97
+ # @return [String]
98
+ def inspect
99
+ "#<#{self.class}:#{object_id.to_s(16)} integer=#{to_i.inspect} hex=#{to_hex.inspect} ascii=#{to_ascii.inspect} binary=#{to_binary.inspect} >"
100
+ end
101
+
102
+ end
103
+
104
+ end
@@ -0,0 +1,37 @@
1
+ require 'brainfucktt/errors'
2
+
3
+ module Brainfucktt
4
+
5
+ # Helpers for Classes which have methods that accepts a value that need
6
+ # to be converted into a specific type.
7
+ module ConversionHelpers
8
+
9
+ protected
10
+
11
+ def convert_to_integer(value)
12
+ raise InvalidByteError unless value.is_a?(Integer) || value.respond_to?(:to_i)
13
+ value = value.to_i unless value.is_a?(Integer)
14
+
15
+ value
16
+ end
17
+
18
+ def convert_to_byte(value)
19
+ raise InvalidByteError unless value.is_a?(Byte) || value.is_a?(Integer) || value.respond_to?(:to_i)
20
+ value = value.to_i unless value.is_a?(Byte) || value.is_a?(Integer)
21
+ value = Byte.new(value) unless value.is_a?(Byte)
22
+
23
+ # TODO: Complain about 0 to 255 compliance
24
+
25
+ value
26
+ end
27
+
28
+ def convert_to_options(value)
29
+ raise InvalidOptionsError unless value.is_a?(Hash) || value.respond_to?(:to_hash) || value.respond_to?(:to_h)
30
+ value = value.to_hash rescue value.to_h unless value.is_a?(Hash)
31
+
32
+ value
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,56 @@
1
+ require 'brainfucktt/conversion_helpers'
2
+ require 'brainfucktt/byte'
3
+
4
+ module Brainfucktt
5
+
6
+ # An Array of Byte instances.
7
+ class Data
8
+ include ConversionHelpers
9
+
10
+ attr_reader :bytes
11
+
12
+ def initialize
13
+ @bytes = []
14
+ end
15
+
16
+ # Retrieve the Byte instance at the given offset.
17
+ #
18
+ # @param [Integer, #to_i] offset
19
+ # @raise [Brainfucktt::InvalidOffsetError] When the given offset cannot be converted into an Integer.
20
+ # @return [Byte] The Byte instance at the given offset.
21
+ def [](offset)
22
+ offset = convert_to_integer(offset)
23
+ self[offset] = 0 if @bytes[offset].nil?
24
+
25
+ @bytes[offset]
26
+ end
27
+
28
+ # Set the value of the Byte instance at the given offset.
29
+ #
30
+ # @param [Integer, #to_i] offset
31
+ # @param [Byte, Integer, #to_i] value
32
+ # @raise [Brainfucktt::InvalidByteError] When the given value cannot be converted into an Integer or is not between 0 and 255.
33
+ # @raise [Brainfucktt::InvalidOffsetError] When the given offset cannot be converted into an Integer.
34
+ # @return [Byte] The Byte instance at the given offset.
35
+ def []=(offset, value)
36
+ expand_to(offset)
37
+
38
+ @bytes[offset] = convert_to_byte(value)
39
+ end
40
+
41
+ # Fill from the end of the Data collection to the given offset with empty
42
+ # Byte instances, if Data collection is less than the given offset.
43
+ #
44
+ # @param [Integer, #to_i] offset
45
+ # @raise [Brainfucktt::InvalidOffsetError] When the given offset cannot be converted into an Integer.
46
+ # @return [Byte] The Byte instance at the given offset.
47
+ def expand_to(offset)
48
+ offset = convert_to_integer(offset)
49
+ (@bytes.length..offset).each { @bytes << Byte.new } if @bytes.at(offset).nil?
50
+
51
+ @bytes[offset]
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,62 @@
1
+ require 'brainfucktt/language_parser'
2
+
3
+ module Brainfucktt
4
+
5
+ # Base class for all errors within this library.
6
+ class Error < StandardError
7
+
8
+ end
9
+
10
+ # Raised when the value set within a Data instance is greater than a byte or less than zero.
11
+ class InvalidByteError < Error
12
+
13
+ # @return [String] The error message.
14
+ def to_s
15
+ 'The value of a Byte must be an Integer or respond to to_i and be between 0 and 255'
16
+ end
17
+
18
+ end
19
+
20
+ # Raised when the offset given is notDatanteger.
21
+ class InvalidOffsetError < Error
22
+
23
+ # @return [String] The error message.
24
+ def to_s
25
+ 'The offset of a Byte must be an Integer or respond to to_i'
26
+ end
27
+
28
+ end
29
+
30
+ # Raised when the offset given is notDatash.
31
+ class InvalidOptionsError < Error
32
+
33
+ # @return [String] The error message.
34
+ def to_s
35
+ 'The options must be a Hash or respond to to_hash or to_h'
36
+ end
37
+
38
+ end
39
+
40
+ # Raised when the code being parsed has a syntax error.
41
+ class ParserError < Error
42
+ extend Forwardable
43
+
44
+ def_delegator :@language_parser, :failure_reason, :reason
45
+ def_delegator :@language_parser, :failure_line, :line
46
+ def_delegator :@language_parser, :failure_column, :column
47
+
48
+ # @param [Brainfucktt::LanguageParser] language_parser
49
+ def initialize(language_parser)
50
+ raise TypeError unless language_parser.instance_of?(Brainfucktt::LanguageParser)
51
+
52
+ @language_parser = language_parser
53
+ end
54
+
55
+ # @return [String]
56
+ def to_s
57
+ "Error at column #{column}, line #{line} - '#{reason}'"
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,14 @@
1
+ require 'pathname'
2
+
3
+ Dir[ Pathname.new(__FILE__).join('..', 'language').expand_path.join('**', '*.rb') ].each do |path|
4
+ require path
5
+ end
6
+
7
+ module Brainfucktt
8
+
9
+ # The container for syntax nodes.
10
+ module Language
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # -
7
+ class DecrementByte < Node
8
+
9
+ # Decrement the byte at the pointer.
10
+ def run(parser)
11
+ parser.byte -= 1
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # <
7
+ class DecrementPointer < Node
8
+
9
+ # Decrement the pointer.
10
+ def run(parser)
11
+ parser.pointer -= 1
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # +
7
+ class IncrementByte < Node
8
+
9
+ # Increment the byte at the pointer.
10
+ def run(parser)
11
+ parser.byte += 1
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # >
7
+ class IncrementPointer < Node
8
+
9
+ # Increment the pointer.
10
+ def run(parser)
11
+ parser.pointer += 1
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # ,
7
+ class InputByte < Node
8
+
9
+ # Input an ASCII character and store it in the byte at the pointer.
10
+ def run(parser)
11
+ parser.byte = get_character(parser.stdin)
12
+ end
13
+
14
+ protected
15
+
16
+ def get_character(io)
17
+ begin
18
+ system("stty raw -echo")
19
+ str = io.getc
20
+ ensure
21
+ system("stty -raw echo")
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # [ ]
7
+ class Loop < Node
8
+
9
+ # Run the loop
10
+ def run(parser)
11
+ elements.first.run(parser) until parser.byte.empty?
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # .
7
+ class OutputByte < Node
8
+
9
+ # Output the byte at the pointer as an ASCII character.
10
+ def run(parser)
11
+ parser.output.print parser.byte.to_s
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'brainfucktt/node'
2
+
3
+ module Brainfucktt
4
+ module Language
5
+
6
+ # The syntax tree of the source code or loop.
7
+ class Tree < Node
8
+ attr_accessor :data
9
+
10
+ # Run the code.
11
+ def run(parser)
12
+ elements.each { |element| element.run(parser) }
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module Brainfucktt
2
+ grammar Language
3
+
4
+ rule tree
5
+ (loop / increment_pointer / decrement_pointer / increment_byte / decrement_byte / input_byte / output_byte / ignored_character)* <Tree>
6
+ end
7
+
8
+ rule ignored_character
9
+ !(begin_loop / end_loop / increment_pointer / decrement_pointer / increment_byte / decrement_byte / input_byte / output_byte) .
10
+ end
11
+
12
+ rule loop
13
+ begin_loop tree end_loop <Loop>
14
+ end
15
+
16
+ rule begin_loop
17
+ '['
18
+ end
19
+
20
+ rule end_loop
21
+ ']'
22
+ end
23
+
24
+ rule increment_pointer
25
+ '>' <IncrementPointer>
26
+ end
27
+
28
+ rule decrement_pointer
29
+ '<' <DecrementPointer>
30
+ end
31
+
32
+ rule increment_byte
33
+ '+' <IncrementByte>
34
+ end
35
+
36
+ rule decrement_byte
37
+ '-' <DecrementByte>
38
+ end
39
+
40
+ rule input_byte
41
+ ',' <InputByte>
42
+ end
43
+
44
+ rule output_byte
45
+ '.' <OutputByte>
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require 'treetop/runtime/syntax_node'
2
+
3
+ module Brainfucktt
4
+
5
+ # The base class for Brainfucktt syntax nodes in the AST
6
+ class Node < Treetop::Runtime::SyntaxNode
7
+
8
+ alias_method :elements_with_treetop, :elements
9
+
10
+ # The children of this Node instance.
11
+ #
12
+ # @return [<Brainfucktt::Node>]
13
+ def elements
14
+ elements_with_treetop.find_all { |node| node.is_a?(Brainfucktt::Node) } rescue []
15
+ end
16
+
17
+ # Return the text value of this Node instance.
18
+ #
19
+ # @return [String]
20
+ def to_s
21
+ text_value
22
+ end
23
+
24
+ # Print out the AST of this instance node and it's children with indentation.
25
+ #
26
+ # @return [String]
27
+ def inspect(indent="")
28
+ result = ""
29
+ result << indent
30
+ result << self.class.to_s.sub(/.*:/,'')
31
+ result << " #{to_s} " unless self.is_a?(Language::Tree) || self.is_a?(Language::Loop)
32
+
33
+ unless elements.empty?
34
+ result << ":"
35
+ elements.each do |e|
36
+ result << "\n#{e.inspect(indent+" ")}" rescue "\n#{indent} #{e.inspect}"
37
+ end
38
+ end
39
+
40
+ result
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,78 @@
1
+ require 'polyglot'
2
+ require 'treetop'
3
+ require 'brainfucktt/errors'
4
+ require 'brainfucktt/language'
5
+ require 'brainfucktt/language_parser'
6
+ require 'brainfucktt/data'
7
+ require 'brainfucktt/conversion_helpers'
8
+
9
+ module Brainfucktt
10
+
11
+ # The Brainfuck parser.
12
+ class Parser
13
+ include ConversionHelpers
14
+
15
+ class << self
16
+
17
+ # Get a new or the cached instance of this class.
18
+ #
19
+ # @return [Brainfucktt::LanguageParser]
20
+ def instance
21
+ @instance ||= LanguageParser.new
22
+ end
23
+
24
+ # Parse the given Brainfuck code.
25
+ #
26
+ # @param [String, #to_s] code
27
+ # @raise [Brainfucktt::ParserError]
28
+ # @return [Brainfucktt::Parser]
29
+ def parse(code)
30
+ tree = instance.parse(code)
31
+ raise ParserError, instance unless tree
32
+
33
+ new(tree)
34
+ end
35
+
36
+ # Parse and run the given Brainfuck code.
37
+ #
38
+ # @param [String, #to_s] code
39
+ def run(code, options={})
40
+ parse(code).run(options)
41
+ end
42
+ end
43
+
44
+ attr_reader :data, :tree, :input, :output
45
+ attr_accessor :pointer
46
+
47
+ def initialize(tree)
48
+ @data, @tree, @pointer = Data.new, tree, 0
49
+ end
50
+
51
+ # Run the parsed Brainfuck code.
52
+ #
53
+ # @raise [Brainfucktt::InvalidOptionsError] When the given offset cannot be converted into an Integer.
54
+ # @param [Hash, #to_hash, #to_h] options
55
+ def run(options={})
56
+ options = { input: STDIN, output: STDOUT }.merge( convert_to_options(options) )
57
+
58
+ @input, @output = options.values_at(:input, :output)
59
+ @tree.run(self)
60
+ end
61
+
62
+ # Returns the Byte instance within the @data collection at pointer.
63
+ #
64
+ # @return [Brainfucktt::Byte]
65
+ def byte
66
+ @data[@pointer]
67
+ end
68
+
69
+ # Set the value of the Byte instance within the @data collection at pointer.
70
+ #
71
+ # @return [Brainfucktt::Byte]
72
+ def byte=(value)
73
+ @data[@pointer] = value
74
+ end
75
+
76
+ end
77
+
78
+ end
metadata ADDED
@@ -0,0 +1,279 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainfucktt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Scott Lewis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-06 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: treetop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ - !ruby/object:Gem::Dependency
31
+ name: polyglot
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.3'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: version
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: at
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.1'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.1'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: '10.0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: '10.0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: guard-rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '2.1'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '2.1'
110
+ - !ruby/object:Gem::Dependency
111
+ name: guard-yard
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: '2.0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rb-fsevent
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: '0.9'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.9'
142
+ - !ruby/object:Gem::Dependency
143
+ name: fuubar
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: '1.1'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: '1.1'
158
+ - !ruby/object:Gem::Dependency
159
+ name: redcarpet
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ version: 2.2.2
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: 2.2.2
174
+ - !ruby/object:Gem::Dependency
175
+ name: github-markup
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ~>
180
+ - !ruby/object:Gem::Version
181
+ version: '0.7'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ~>
188
+ - !ruby/object:Gem::Version
189
+ version: '0.7'
190
+ description: ! "# Brainfucktt\n\nA [Brainfuck][brainfuck] interpreter built in [Ruby][ruby]
191
+ using [Treetop][treetop].\n\nBrainfuck is an eight-instruction turing-clomplete
192
+ programming language created in 1993\nby Urban Müller, based on the more formal
193
+ programming language [P′′][p''] created by Corrado\nBöhm in 1964.\n\nIt is designed
194
+ to challenge and amuse programmers, and is not made to be suitable for \npractical
195
+ use.\n\n## Install\n\n### Bundler: `gem 'brainfucktt'`\n\n### RubyGems: `gem install
196
+ brainfucktt`\n\n## Brainfuck Instructions\n\n`>` Increment the data pointer (to
197
+ point to the next cell to the right).\n\n`<` Decrement the data pointer (to point
198
+ to the next cell to the left).\n\n`+` Increment (increase by one) the byte at the
199
+ data pointer.\n\n`-` Decrement (decrease by one) the byte at the data pointer.\n\n`.`
200
+ Output the byte at the data pointer as an ASCII encoded character.\n\n`,` Accept
201
+ one byte of input, storing its value in the byte at the data pointer.\n\n`[` If
202
+ the byte at the data pointer is zero, then instead of moving the instruction pointer
203
+ forward to the next command, jump it forward to the command after the matching `]`
204
+ command.\n\n`]` If the byte at the data pointer is nonzero, then instead of moving
205
+ the instruction pointer forward to the next command, jump it back to the command
206
+ after the matching `[` command.\n\n### Comments\n\nAny character besides one of
207
+ the 8 instructions above is not parsed and will be regarded as a comment.\n\n##
208
+ Usage\n\n### Running\n\n```ruby\nrequire 'brainfucktt'\n\n# \"Hello World!\" written
209
+ in Brainfuck\ncode = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\n\nBrainfucktt.run(code)\n```\n\n###
210
+ Parsing\n\n```ruby\nrequire 'brainfucktt'\n\n# \"Hello World!\" written in Brainfuck\ncode
211
+ = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\nparser
212
+ = Brainfucktt.parse(code)\n\n# Print out the AST of the code\np parser.tree\n\n#
213
+ Run the code within Ruby\nparser.run\n```\n\n### StringIO\n\nSometimes you do now
214
+ want to use STDIN or STDOUT for the I/O of the Brainfuck program.\n\nTo do that,
215
+ you must use the stdlib `stringio`:\n\n```ruby\nrequire 'brainfucktt'\nrequire 'stringio'\n\n#
216
+ \"Hello World!\" written in Brainfuck\ncode = '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'\n\noutput
217
+ = StringIO.new\nBrainfucktt.run(code, output: output)\n\np output.string # => \"Hello
218
+ World!\\n\"\n```\n\n## Copyright\n\nCopyright © 2012 Ryan Scott Lewis <ryan@rynet.us>.\n\nThe
219
+ MIT License (MIT) - See LICENSE for further details.\n\n[brainfuck]: http://www.muppetlabs.com/~breadbox/bf/\n[ruby]:
220
+ http://ruby-lang.org\n[treetop]: http://treetop.rubyforge.org\n[p'']: http://en.wikipedia.org/wiki/P%E2%80%B2%E2%80%B2"
221
+ email: ryan@rynet.us
222
+ executables: []
223
+ extensions: []
224
+ extra_rdoc_files: []
225
+ files:
226
+ - Gemfile
227
+ - LICENSE
228
+ - README.md
229
+ - Rakefile
230
+ - VERSION
231
+ - brainfucktt.gemspec
232
+ - examples/hello_world.rb
233
+ - examples/hello_world_with_comments.rb
234
+ - examples/stringio.rb
235
+ - lib/brainfucktt.rb
236
+ - lib/brainfucktt/byte.rb
237
+ - lib/brainfucktt/conversion_helpers.rb
238
+ - lib/brainfucktt/data.rb
239
+ - lib/brainfucktt/errors.rb
240
+ - lib/brainfucktt/language.rb
241
+ - lib/brainfucktt/language/decrement_byte.rb
242
+ - lib/brainfucktt/language/decrement_pointer.rb
243
+ - lib/brainfucktt/language/increment_byte.rb
244
+ - lib/brainfucktt/language/increment_pointer.rb
245
+ - lib/brainfucktt/language/input_byte.rb
246
+ - lib/brainfucktt/language/loop.rb
247
+ - lib/brainfucktt/language/output_byte.rb
248
+ - lib/brainfucktt/language/tree.rb
249
+ - lib/brainfucktt/language_parser.treetop
250
+ - lib/brainfucktt/node.rb
251
+ - lib/brainfucktt/parser.rb
252
+ homepage: http://github.com/RyanScottLewis/brainfucktt
253
+ licenses: []
254
+ post_install_message:
255
+ rdoc_options: []
256
+ require_paths:
257
+ - lib
258
+ required_ruby_version: !ruby/object:Gem::Requirement
259
+ none: false
260
+ requirements:
261
+ - - ! '>='
262
+ - !ruby/object:Gem::Version
263
+ version: '0'
264
+ segments:
265
+ - 0
266
+ hash: -1616283130574261905
267
+ required_rubygems_version: !ruby/object:Gem::Requirement
268
+ none: false
269
+ requirements:
270
+ - - ! '>='
271
+ - !ruby/object:Gem::Version
272
+ version: '0'
273
+ requirements: []
274
+ rubyforge_project:
275
+ rubygems_version: 1.8.24
276
+ signing_key:
277
+ specification_version: 3
278
+ summary: A Brainfuck interpreter built in Ruby using Treetop.
279
+ test_files: []