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 +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
|