brainfucktt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []