brain_love 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +50 -0
- data/Rakefile +8 -0
- data/bin/brain_love +33 -0
- data/brain_love.gemspec +20 -0
- data/lib/brain_love/ast.rb +31 -0
- data/lib/brain_love/compiler.rb +89 -0
- data/lib/brain_love/parser.rb +28 -0
- data/lib/brain_love/transformer.rb +26 -0
- data/lib/brain_love/version.rb +3 -0
- data/lib/brain_love/vm.rb +90 -0
- data/lib/brain_love.rb +24 -0
- data/spec/bin/brain_love_spec.rb +21 -0
- data/spec/brain_love/compiler_spec.rb +49 -0
- data/spec/brain_love/parser_spec.rb +62 -0
- data/spec/brain_love/transformer_spec.rb +40 -0
- data/spec/brain_love/vm_spec.rb +96 -0
- data/spec/brain_love_spec.rb +28 -0
- data/spec/fixtures/hello_world.bf +1 -0
- data/spec/spec_helper.rb +50 -0
- metadata +108 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
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
|
data/brain_love.gemspec
ADDED
@@ -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,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
|
+
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.>>>++++++++[<++++>-]<.>>>++++++++++[<+++++++++>-]<---.<<<<.+++.------.--------.>>+.
|
data/spec/spec_helper.rb
ADDED
@@ -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
|