brain_love 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in brain_love.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Marjan Krekoten' (Мар'ян Крекотень)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # BrainLove
2
+
3
+ Ruby implementation of [brainfuck](http://esolangs.org/wiki/Brainfuck).
4
+
5
+ ## Installation
6
+
7
+ Install it as:
8
+
9
+ $ gem install brain_love
10
+
11
+ ## Usage
12
+
13
+ ### Standalone
14
+
15
+ # Hello World
16
+ brain_love -e '>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.'
17
+
18
+ Or from file:
19
+
20
+ brain_love some_file.bf
21
+
22
+ ### In your code
23
+
24
+ require 'brain_love'
25
+
26
+ # Hello World
27
+ BrainLove.run_string('>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.')
28
+
29
+ ## Portability
30
+
31
+ * Data space contains 30 000 cells
32
+ * Data cell is unsigned byte (wraps around)
33
+ * EOF sets data cell to 0
34
+ * Nested loops
35
+
36
+ ## Current limitations
37
+
38
+ * jump offsets are 1 byte
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
47
+
48
+ ## License
49
+
50
+ Copyright (c) 2012 Мар'ян Крекотень (Marjan Krekoten'). Distributed under the MIT License. See LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ desc "Default task is to run specs :) Did you expect something else?"
8
+ task :default => :spec
data/bin/brain_love ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path('../../lib', __FILE__)
3
+ require 'brain_love'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: brain_love [OPTIONS] [FILE]\nVersion: #{BrainLove::VERSION}"
10
+
11
+ opts.on("-e", "--execute CODE", "Brainfuck one liner. FILE param not accepted") do |code|
12
+ options[:execute] = code
13
+ end
14
+
15
+ opts.on('-v', '--version', 'Show version') do
16
+ options[:version] = true
17
+ end
18
+
19
+ opts.on_tail('-h', '--help', "This message") do
20
+ puts opts
21
+ exit
22
+ end
23
+ end.parse!
24
+
25
+ if options[:version]
26
+ puts "Version: #{BrainLove::VERSION}"
27
+ end
28
+
29
+ if code = options[:execute]
30
+ BrainLove.run_string(code)
31
+ elsif !ARGV.empty? && options[:execute].nil?
32
+ ARGV.each { |_| BrainLove.run_file(_) }
33
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/brain_love/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Marjan Krekoten' (Мар'ян Крекотень)"]
6
+ gem.email = ["krekoten@gmail.com"]
7
+ gem.description = %q{Brainfuck implementation for fun and love}
8
+ gem.summary = %q{Brainfuck}
9
+ gem.homepage = "http://krekoten.github.com/brain_love"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "brain_love"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = BrainLove::VERSION
17
+
18
+ gem.add_development_dependency "rspec"
19
+ gem.add_runtime_dependency "parslet"
20
+ end
@@ -0,0 +1,31 @@
1
+ module BrainLove
2
+ module AST
3
+
4
+ class Node
5
+ attr_reader :value
6
+
7
+ def initialize(value = nil)
8
+ @value = value
9
+ end
10
+
11
+ def ==(other)
12
+ self.class == other.class &&
13
+ self.value == other.value
14
+ end
15
+
16
+ def accept(visitor)
17
+ visitor.send("visit_#{self.class.to_s.split('::').last}", self)
18
+ end
19
+ end
20
+
21
+ class Statements < Node; end
22
+ class IncrementPointer < Node; end
23
+ class DecrementPointer < Node; end
24
+ class IncrementByte < Node; end
25
+ class DecrementByte < Node; end
26
+ class OutputByte < Node; end
27
+ class InputByte < Node; end
28
+ class Loop < Node; end
29
+
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ module BrainLove
2
+ class Compiler
3
+
4
+ attr_reader :bytecode, :ip
5
+
6
+ def initialize
7
+ @bytecode = ""
8
+ @ip = 0
9
+ end
10
+
11
+ def inc_dp
12
+ @bytecode << VM::INC_DP.chr
13
+ end
14
+
15
+ def dec_dp
16
+ @bytecode << VM::DEC_DP.chr
17
+ end
18
+
19
+ def inc_byte
20
+ @bytecode << VM::INC_BYTE.chr
21
+ end
22
+
23
+ def dec_byte
24
+ @bytecode << VM::DEC_BYTE.chr
25
+ end
26
+
27
+ def putc
28
+ @bytecode << VM::PUTC.chr
29
+ end
30
+
31
+ def getc
32
+ @bytecode << VM::GETC.chr
33
+ end
34
+
35
+ def jmpfz(at, offset)
36
+ @bytecode[at] = VM::JMPFZ.chr
37
+ @bytecode[at + 1] = offset.chr
38
+ end
39
+
40
+ def jmpbnz(offset)
41
+ @bytecode << VM::JMPBNZ.chr << offset.chr
42
+ end
43
+
44
+ def noop
45
+ @bytecode << VM::NOOP
46
+ end
47
+
48
+ def current_ip
49
+ @bytecode.bytesize
50
+ end
51
+
52
+ def visit_Statements(statements)
53
+ statements.value.each { |st| st.accept(self) }
54
+ end
55
+
56
+ def visit_Loop(_loop)
57
+ jmpf_ip = current_ip
58
+ noop
59
+ noop
60
+ _loop.value.accept(self)
61
+ jmpbnz(current_ip - jmpf_ip)
62
+ jmpfz(jmpf_ip, current_ip)
63
+ end
64
+
65
+ def visit_DecrementPointer(_)
66
+ dec_dp
67
+ end
68
+
69
+ def visit_IncrementPointer(_)
70
+ inc_dp
71
+ end
72
+
73
+ def visit_IncrementByte(_)
74
+ inc_byte
75
+ end
76
+
77
+ def visit_DecrementByte(_)
78
+ dec_byte
79
+ end
80
+
81
+ def visit_OutputByte(_)
82
+ putc
83
+ end
84
+
85
+ def visit_InputByte(_)
86
+ getc
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,28 @@
1
+ require 'parslet'
2
+
3
+ module BrainLove
4
+ class Parser < Parslet::Parser
5
+ rule(:increment_pointer) { str('>').as(:increment_pointer) }
6
+ rule(:decrement_pointer) { str('<').as(:decrement_pointer) }
7
+ rule(:increment_byte) { str('+').as(:increment_byte) }
8
+ rule(:decrement_byte) { str('-').as(:decrement_byte) }
9
+ rule(:output_byte) { str('.').as(:output_byte) }
10
+ rule(:input_byte) { str(',').as(:input_byte) }
11
+ rule(:jump_forward) { str('[') }
12
+ rule(:jump_backward) { str(']') }
13
+
14
+ rule :command do
15
+ increment_pointer |
16
+ decrement_pointer |
17
+ increment_byte |
18
+ decrement_byte |
19
+ output_byte |
20
+ input_byte
21
+ end
22
+
23
+ rule(:commands) { command.repeat(1) }
24
+ rule(:_loop) { (jump_forward >> (commands | _loop).as(:statements).repeat >> jump_backward).as(:loop) }
25
+
26
+ rule(:root) { (commands | _loop).as(:statements).repeat.as(:statements) }
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module BrainLove
2
+ class Transformer < Parslet::Transform
3
+ rule(:increment_pointer => simple(:_)) { AST::IncrementPointer.new }
4
+ rule(:decrement_pointer => simple(:_)) { AST::DecrementPointer.new }
5
+ rule(:increment_byte => simple(:_)) { AST::IncrementByte.new }
6
+ rule(:decrement_byte => simple(:_)) { AST::DecrementByte.new }
7
+ rule(:output_byte => simple(:_)) { AST::OutputByte.new }
8
+ rule(:input_byte => simple(:_)) { AST::InputByte.new }
9
+
10
+ rule(:loop => simple(:_)) do
11
+ AST::Loop.new
12
+ end
13
+
14
+ rule(:loop => sequence(:statements)) do
15
+ AST::Loop.new(statements.first)
16
+ end
17
+
18
+ rule :statements => simple(:statements) do
19
+ AST::Statements.new([statements])
20
+ end
21
+
22
+ rule :statements => sequence(:statements) do
23
+ AST::Statements.new(statements)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module BrainLove
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,90 @@
1
+ module BrainLove
2
+ class VM
3
+
4
+ NOOP = 0
5
+ INC_DP = 1
6
+ DEC_DP = 2
7
+ INC_BYTE = 3
8
+ DEC_BYTE = 4
9
+ PUTC = 5
10
+ GETC = 6
11
+ JMPFZ = 7
12
+ JMPBNZ = 8
13
+
14
+ attr_reader :ip, :dp, :code, :data
15
+
16
+ def initialize(code, input, output)
17
+ @code, @input, @output = code, input, output
18
+ @ip = 0
19
+ @dp = 0
20
+ @data = [0] * 30_000
21
+ end
22
+
23
+ def code_dump
24
+ index = 0
25
+ @code.each_byte do |b|
26
+ print "#{index} "
27
+ index += 1
28
+ case b
29
+ when NOOP
30
+ puts "NOOP"
31
+ when INC_DP
32
+ puts "INC_DP"
33
+ when DEC_DP
34
+ puts "DEC_DP"
35
+ when INC_BYTE
36
+ puts "INC_BYTE"
37
+ when DEC_BYTE
38
+ puts "DEC_BYTE"
39
+ when PUTC
40
+ puts "PUTC"
41
+ when GETC
42
+ puts "GETC"
43
+ when JMPFZ
44
+ puts "JMPFZ"
45
+ @jmp = true
46
+ when JMPBNZ
47
+ puts "JMPBNZ"
48
+ @jmp = true
49
+ else
50
+ if @jmp
51
+ @jmp = false
52
+ puts " offset #{b}"
53
+ else
54
+ puts "UNKNOWN BYTECODE"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def execute
61
+ while @ip < @code.bytesize do
62
+ case @code[@ip].ord
63
+ when NOOP
64
+ when INC_DP
65
+ @dp += 1
66
+ when DEC_DP
67
+ @dp -= 1
68
+ when INC_BYTE
69
+ @data[@dp] += 1
70
+ when DEC_BYTE
71
+ @data[@dp] -= 1
72
+ when PUTC
73
+ @output.putc(@data[@dp].chr)
74
+ when GETC
75
+ @data[@dp] = (@input.getc || 0).ord
76
+ when JMPFZ
77
+ @ip += @code[@ip + 1].ord - 1 if @data[@dp] == 0
78
+ when JMPBNZ
79
+ unless @data[@dp] == 0
80
+ @ip -= @code[@ip + 1].ord
81
+ else
82
+ @ip += 1
83
+ end
84
+ end
85
+
86
+ @ip += 1
87
+ end
88
+ end
89
+ end
90
+ end
data/lib/brain_love.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "brain_love/version"
2
+ require "brain_love/ast"
3
+ require "brain_love/parser"
4
+ require "brain_love/transformer"
5
+ require "brain_love/vm"
6
+ require "brain_love/compiler"
7
+
8
+ module BrainLove
9
+
10
+ def run_string(code, input = STDIN, output = STDOUT)
11
+ ast = Transformer.new.apply(Parser.new.parse(code))
12
+ compiler = Compiler.new
13
+ ast.accept(compiler)
14
+ VM.new(compiler.bytecode, input, output).execute
15
+ end
16
+ module_function :run_string
17
+
18
+ def run_file(file, input = STDIN, output = STDOUT)
19
+ code = File.open(file) { |f| f.read }
20
+ run_string(code, input, output)
21
+ end
22
+ module_function :run_file
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ def run_brain_love(cmd)
4
+ `#{File.expand_path('../../../bin/brain_love', __FILE__)} #{cmd}`
5
+ end
6
+
7
+ describe 'brain_love' do
8
+ let(:hello_world) { '>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.' }
9
+
10
+ describe 'FILE' do
11
+ it 'executes code from file' do
12
+ run_brain_love(fixture_path('hello_world.bf')).should == 'Hello World!'
13
+ end
14
+ end
15
+
16
+ describe '-e' do
17
+ it 'executes code' do
18
+ run_brain_love("-e '#{hello_world}'").should == 'Hello World!'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ def bytecode(*bytes)
4
+ bytes.map(&:chr).join
5
+ end
6
+
7
+ describe BrainLove::Compiler do
8
+ let(:compiler) { BrainLove::Compiler.new }
9
+
10
+ it "emits INC_DP" do
11
+ compiler.inc_dp
12
+ compiler.bytecode.should == bytecode(BrainLove::VM::INC_DP)
13
+ end
14
+
15
+ it "emits DEC_DP" do
16
+ compiler.dec_dp
17
+ compiler.bytecode.should == bytecode(BrainLove::VM::DEC_DP)
18
+ end
19
+
20
+ it "emits INC_BYTE" do
21
+ compiler.inc_byte
22
+ compiler.bytecode.should == bytecode(BrainLove::VM::INC_BYTE)
23
+ end
24
+
25
+ it "emits DEC_BYTE" do
26
+ compiler.dec_byte
27
+ compiler.bytecode.should == bytecode(BrainLove::VM::DEC_BYTE)
28
+ end
29
+
30
+ it "emits PUTC" do
31
+ compiler.putc
32
+ compiler.bytecode.should == bytecode(BrainLove::VM::PUTC)
33
+ end
34
+
35
+ it "emits GETC" do
36
+ compiler.getc
37
+ compiler.bytecode.should == bytecode(BrainLove::VM::GETC)
38
+ end
39
+
40
+ it "emits JMPFZ" do
41
+ compiler.jmpfz(0, 0)
42
+ compiler.bytecode.should == bytecode(BrainLove::VM::JMPFZ, 0)
43
+ end
44
+
45
+ it "emits JMPBNZ" do
46
+ compiler.jmpbnz(3)
47
+ compiler.bytecode.should == bytecode(BrainLove::VM::JMPBNZ, 3)
48
+ end
49
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe BrainLove::Parser do
4
+ describe 'increment the pointer' do
5
+ subject { BrainLove::Parser.new.increment_pointer }
6
+
7
+ it { should parse('>') }
8
+ end
9
+
10
+ describe 'decrement the pointer' do
11
+ subject { BrainLove::Parser.new.decrement_pointer }
12
+
13
+ it { should parse('<') }
14
+ end
15
+
16
+ describe 'increment the byte at the pointer' do
17
+ subject { BrainLove::Parser.new.increment_byte }
18
+
19
+ it { should parse('+') }
20
+ end
21
+
22
+ describe 'decrement the byte at the pointer' do
23
+ subject { BrainLove::Parser.new.decrement_byte }
24
+
25
+ it { should parse('-') }
26
+ end
27
+
28
+ describe 'output the byte at the pointer' do
29
+ subject { BrainLove::Parser.new.output_byte }
30
+
31
+ it { should parse('.') }
32
+ end
33
+
34
+ describe 'input a byte and sore at the pointer' do
35
+ subject { BrainLove::Parser.new.input_byte }
36
+
37
+ it { should parse(',') }
38
+ end
39
+
40
+ describe 'jump forward if the byte at the pointer is zero' do
41
+ subject { BrainLove::Parser.new.jump_forward }
42
+
43
+ it { should parse('[') }
44
+ end
45
+
46
+ describe 'jump backward if the byte at the pointer is zero' do
47
+ subject { BrainLove::Parser.new.jump_backward }
48
+
49
+ it { should parse(']') }
50
+ end
51
+
52
+ describe 'integration' do
53
+ it { should parse('[+++>++<--]') }
54
+ it { should parse('[]') }
55
+ it { should parse('++[]') }
56
+ it { should parse('[[]]') }
57
+ it { should parse('[[++]++]>.') }
58
+
59
+ it { should_not parse('[') }
60
+ it { should_not parse(']') }
61
+ end
62
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ def ast_statement(nodes)
4
+ BrainLove::AST::Statements.new(nodes)
5
+ end
6
+
7
+ describe BrainLove::Transformer do
8
+ describe 'simple commands' do
9
+ it { should transform('>').to(ast_statement([ast_statement([BrainLove::AST::IncrementPointer.new])])) }
10
+ it { should transform('<').to(ast_statement([ast_statement([BrainLove::AST::DecrementPointer.new])])) }
11
+ it { should transform('+').to(ast_statement([ast_statement([BrainLove::AST::IncrementByte.new])])) }
12
+ it { should transform('-').to(ast_statement([ast_statement([BrainLove::AST::DecrementByte.new])])) }
13
+ it { should transform('.').to(ast_statement([ast_statement([BrainLove::AST::OutputByte.new])])) }
14
+ it { should transform(',').to(ast_statement([ast_statement([BrainLove::AST::InputByte.new])])) }
15
+ end
16
+
17
+ describe 'loop' do
18
+ it { should transform('[]').to(ast_statement([ast_statement([BrainLove::AST::Loop.new()])])) }
19
+ end
20
+
21
+ describe 'commands and loop' do
22
+ it do
23
+ should transform(',[.,]').to(
24
+ ast_statement([
25
+ ast_statement([
26
+ BrainLove::AST::InputByte.new
27
+ ]),
28
+ ast_statement([
29
+ BrainLove::AST::Loop.new(
30
+ ast_statement([
31
+ BrainLove::AST::OutputByte.new,
32
+ BrainLove::AST::InputByte.new
33
+ ])
34
+ )
35
+ ])
36
+ ])
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe BrainLove::VM do
5
+ let(:input) { double('input stream') }
6
+ let(:output) { double('output stream') }
7
+ let(:vm) { BrainLove::VM.new(code, input, output) }
8
+
9
+ describe "initial state" do
10
+ let(:code) { [].join }
11
+
12
+ it "sets IP to 0" do
13
+ vm.ip.should == 0
14
+ end
15
+
16
+ it "sets DP to 0" do
17
+ vm.dp.should == 0
18
+ end
19
+ end
20
+
21
+ describe "INC_DP" do
22
+ let(:code) { [BrainLove::VM::INC_DP].map(&:chr).join }
23
+
24
+ it "increments the pointer by one" do
25
+ expect { vm.execute }.to change { vm.dp }.by(1)
26
+ end
27
+ end
28
+
29
+ describe "DEC_DP" do
30
+ let(:code) { [BrainLove::VM::INC_DP, BrainLove::VM::DEC_DP].map(&:chr).join }
31
+
32
+ it "decrements the pointer by one" do
33
+ expect { vm.execute }.to change { vm.dp }.by(0)
34
+ end
35
+ end
36
+
37
+ describe "INC_BYTE" do
38
+ let(:code) { [BrainLove::VM::INC_DP, BrainLove::VM::INC_BYTE].map(&:chr).join }
39
+
40
+ it "increments the byte at the pointer by one" do
41
+ expect { vm.execute }.to change { vm.data[1] }.by(1)
42
+ end
43
+ end
44
+
45
+ describe "DEC_BYTE" do
46
+ let(:code) { [BrainLove::VM::INC_DP, BrainLove::VM::INC_BYTE, BrainLove::VM::INC_BYTE, BrainLove::VM::DEC_BYTE].map(&:chr).join }
47
+
48
+ it "decrements the byte at the pointer by one" do
49
+ expect { vm.execute }.to change { vm.data[1] }.by(1)
50
+ end
51
+ end
52
+
53
+ describe "PUTC" do
54
+ let(:code) { ([BrainLove::VM::INC_BYTE] * 97 << BrainLove::VM::PUTC).map(&:chr).join }
55
+
56
+ it "sends the byte at the pointer as char to output stream" do
57
+ output.should_receive(:putc).with("a")
58
+ vm.execute
59
+ end
60
+ end
61
+
62
+ describe "GETC" do
63
+ let(:code) { [BrainLove::VM::INC_DP, BrainLove::VM::GETC].map(&:chr).join }
64
+
65
+ before do
66
+ input.should_receive(:getc).and_return("a")
67
+ end
68
+
69
+ it "reads char from input stream" do
70
+ vm.execute
71
+ end
72
+
73
+ it "stores char as a byte at the pointer" do
74
+ vm.execute
75
+ vm.data[1].should == 97
76
+ end
77
+ end
78
+
79
+ describe "JMPFZ" do
80
+ let(:code) { [BrainLove::VM::JMPFZ, 3, BrainLove::VM::INC_BYTE, BrainLove::VM::INC_DP, BrainLove::VM::GETC, BrainLove::VM::PUTC].map(&:chr).join }
81
+
82
+ it "jumps forward if current byte is 0" do
83
+ input.stub(:getc).and_return('t')
84
+ output.should_receive(:putc).with('t')
85
+ expect { vm.execute }.to change { vm.data[0] }.by(0)
86
+ end
87
+ end
88
+
89
+ describe "JMPBNZ" do
90
+ let(:code) { [BrainLove::VM::INC_BYTE, BrainLove::VM::DEC_BYTE, BrainLove::VM::JMPBNZ, 0, BrainLove::VM::INC_BYTE].map(&:chr).join }
91
+
92
+ it "jumps bacward unless current byte is 0" do
93
+ expect { vm.execute }.to change { vm.data[0] }.by(1)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe BrainLove do
5
+ describe '#run_string' do
6
+ it 'executes code from string' do
7
+ output = StringIO.new
8
+ BrainLove.run_string(
9
+ '>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.',
10
+ nil,
11
+ output
12
+ )
13
+ output.string.should == 'Hello World!'
14
+ end
15
+ end
16
+
17
+ describe '#run_file' do
18
+ it 'executes code from file' do
19
+ output = StringIO.new
20
+ BrainLove.run_file(
21
+ fixture_path('hello_world.bf'),
22
+ nil,
23
+ output
24
+ )
25
+ output.string.should == 'Hello World!'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1 @@
1
+ >+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.
@@ -0,0 +1,50 @@
1
+ require 'brain_love'
2
+
3
+ def fixture_path(name)
4
+ File.expand_path("../fixtures/#{name}", __FILE__)
5
+ end
6
+
7
+ RSpec::Matchers.define :parse do |string_for_parsing|
8
+ match do |parser|
9
+ begin
10
+ parser.parse(string_for_parsing)
11
+ true
12
+ rescue Parslet::ParseFailed, NotImplementedError => e
13
+ @error = parser.error_tree
14
+ false
15
+ end
16
+ end
17
+
18
+ failure_message_for_should do |parser|
19
+ "expected #{parser} to parse #{string_for_parsing}\n#{@error}"
20
+ end
21
+
22
+ failure_message_for_should_not do |parser|
23
+ "didn't expect #{parser} to parse #{string_for_parsing}"
24
+ end
25
+
26
+ description do
27
+ "parse #{string_for_parsing}"
28
+ end
29
+ end
30
+
31
+ RSpec::Matchers.define :transform do |string_for_transforming|
32
+ match do |transformer|
33
+ @intermediate_representation = transformer.apply(@raw_ir = BrainLove::Parser.new.parse(string_for_transforming))
34
+ @node == @intermediate_representation
35
+ end
36
+
37
+ chain :to do |node|
38
+ @node = node
39
+ end
40
+
41
+ failure_message_for_should do |transformer|
42
+ "expected #{string_for_transforming}\n" <<
43
+ "to be transformed to\n#{@node.inspect}\n" <<
44
+ "got\n#{@intermediate_representation.inspect}\nir:\n#{@raw_ir}"
45
+ end
46
+
47
+ description do
48
+ "transform #{string_for_transforming} to AST node"
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brain_love
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Marjan Krekoten' (Мар'ян Крекотень)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-07 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !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: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: parslet
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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'
46
+ description: Brainfuck implementation for fun and love
47
+ email:
48
+ - krekoten@gmail.com
49
+ executables:
50
+ - brain_love
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE
57
+ - README.md
58
+ - Rakefile
59
+ - bin/brain_love
60
+ - brain_love.gemspec
61
+ - lib/brain_love.rb
62
+ - lib/brain_love/ast.rb
63
+ - lib/brain_love/compiler.rb
64
+ - lib/brain_love/parser.rb
65
+ - lib/brain_love/transformer.rb
66
+ - lib/brain_love/version.rb
67
+ - lib/brain_love/vm.rb
68
+ - spec/bin/brain_love_spec.rb
69
+ - spec/brain_love/compiler_spec.rb
70
+ - spec/brain_love/parser_spec.rb
71
+ - spec/brain_love/transformer_spec.rb
72
+ - spec/brain_love/vm_spec.rb
73
+ - spec/brain_love_spec.rb
74
+ - spec/fixtures/hello_world.bf
75
+ - spec/spec_helper.rb
76
+ homepage: http://krekoten.github.com/brain_love
77
+ licenses: []
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.21
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Brainfuck
100
+ test_files:
101
+ - spec/bin/brain_love_spec.rb
102
+ - spec/brain_love/compiler_spec.rb
103
+ - spec/brain_love/parser_spec.rb
104
+ - spec/brain_love/transformer_spec.rb
105
+ - spec/brain_love/vm_spec.rb
106
+ - spec/brain_love_spec.rb
107
+ - spec/fixtures/hello_world.bf
108
+ - spec/spec_helper.rb