brainfuck 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile.lock +8 -23
- data/Rakefile +12 -10
- data/Readme.md +17 -11
- data/bin/brainfuck +1 -1
- data/brainfuck.gemspec +5 -6
- data/lib/brainfuck.rb +5 -18
- data/lib/brainfuck/ast.rb +126 -18
- data/lib/brainfuck/code_loader.rb +31 -0
- data/lib/brainfuck/compiler.rb +81 -0
- data/lib/brainfuck/lexer.rb +32 -0
- data/lib/brainfuck/main.rb +99 -0
- data/lib/brainfuck/parser.rb +9 -24
- data/lib/brainfuck/stages.rb +170 -0
- data/lib/brainfuck/version.rb +1 -1
- data/test/acceptance/acceptance_test.rb +75 -0
- data/test/brainfuck/ast_test.rb +7 -0
- data/test/brainfuck/lexer_test.rb +21 -0
- data/test/brainfuck/parser_test.rb +16 -0
- data/test/test_helper.rb +10 -0
- metadata +32 -53
- data/lib/brainfuck/interpreter.rb +0 -20
- data/lib/brainfuck/stack.rb +0 -51
- data/spec/acceptance/acceptance_spec.rb +0 -105
- data/spec/brainfuck/ast_spec.rb +0 -36
- data/spec/brainfuck/interpreter_spec.rb +0 -25
- data/spec/brainfuck/parser_spec.rb +0 -27
- data/spec/brainfuck/stack_spec.rb +0 -81
- data/spec/brainfuck_spec.rb +0 -23
- data/spec/spec_helper.rb +0 -18
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,29 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
brainfuck (0.
|
5
|
-
highline
|
4
|
+
brainfuck (0.2.0)
|
6
5
|
parslet
|
7
6
|
|
8
7
|
GEM
|
9
8
|
remote: http://rubygems.org/
|
10
9
|
specs:
|
11
|
-
blankslate (2.1.2.
|
12
|
-
|
13
|
-
|
14
|
-
parslet (1.
|
15
|
-
blankslate (~> 2.
|
16
|
-
rspec (2.2.0)
|
17
|
-
rspec-core (~> 2.2)
|
18
|
-
rspec-expectations (~> 2.2)
|
19
|
-
rspec-mocks (~> 2.2)
|
20
|
-
rspec-core (2.2.1)
|
21
|
-
rspec-expectations (2.2.0)
|
22
|
-
diff-lcs (~> 1.1.2)
|
23
|
-
rspec-mocks (2.2.0)
|
24
|
-
simplecov (0.3.7)
|
25
|
-
simplecov-html (>= 0.3.7)
|
26
|
-
simplecov-html (0.3.9)
|
10
|
+
blankslate (2.1.2.4)
|
11
|
+
minitest (2.0.2)
|
12
|
+
mocha (0.9.12)
|
13
|
+
parslet (1.2.0)
|
14
|
+
blankslate (~> 2.0)
|
27
15
|
|
28
16
|
PLATFORMS
|
29
17
|
java
|
@@ -31,8 +19,5 @@ PLATFORMS
|
|
31
19
|
|
32
20
|
DEPENDENCIES
|
33
21
|
brainfuck!
|
34
|
-
|
35
|
-
|
36
|
-
parslet
|
37
|
-
rspec
|
38
|
-
simplecov
|
22
|
+
minitest
|
23
|
+
mocha
|
data/Rakefile
CHANGED
@@ -1,17 +1,19 @@
|
|
1
1
|
require 'bundler'
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
3
|
|
4
|
-
require '
|
5
|
-
|
6
|
-
|
4
|
+
require 'rake/testtask'
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << "test"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb'] - FileList['test/acceptance/**/*_test.rb']
|
8
|
+
t.verbose = true
|
7
9
|
end
|
8
10
|
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
require 'rake/testtask'
|
12
|
+
desc 'Run acceptance tests'
|
13
|
+
Rake::TestTask.new :acceptance do |t|
|
14
|
+
t.libs << "test"
|
15
|
+
t.test_files = FileList['test/acceptance/**/*_test.rb']
|
16
|
+
t.verbose = true
|
13
17
|
end
|
14
|
-
task
|
15
18
|
|
16
|
-
task :default => :
|
17
|
-
task :test => [:spec]
|
19
|
+
task :default => [:test, :acceptance]
|
data/Readme.md
CHANGED
@@ -1,31 +1,37 @@
|
|
1
1
|
# brainfuck
|
2
2
|
|
3
|
-
|
3
|
+
An implementation of Brainfuck on the [Rubinius](http://rubini.us) VM.
|
4
|
+
|
4
5
|
(If you don't know what Brainfuck is, you definitely
|
5
6
|
[should](http://en.wikipedia.org/wiki/Brainfuck)).
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
## UPDATE: Known caveats solved since 0.1.0!
|
10
|
-
|
11
|
-
Thanks to a complete rewrite using Kaspar Schiess' `parslet` (which you should
|
12
|
-
definitely [check it out](http://github.com/kschiess/parslet)) nested loops
|
13
|
-
work flawlessly. So yes, you can now run that high-security online payment
|
14
|
-
system you wrote in Brainfuck :)
|
8
|
+
Obviously... needs Rubinius!
|
15
9
|
|
16
10
|
## Installation and usage
|
17
11
|
|
18
12
|
You just `gem install brainfuck` (or `gem 'brainfuck'` in your Gemfile)!
|
19
13
|
|
20
|
-
And then:
|
14
|
+
And then:
|
15
|
+
|
16
|
+
$ brainfuck my_file.bf
|
17
|
+
|
18
|
+
Or if you just want to generate the compiled bytecode in `my_file.bfc`:
|
19
|
+
|
20
|
+
$ brainfuck -C my_file.bf
|
21
21
|
|
22
22
|
You can also require the gem and use inline brainfuck in your ruby scripts like this:
|
23
23
|
|
24
24
|
require 'brainfuck'
|
25
25
|
|
26
|
-
Brainfuck.
|
26
|
+
# Brainfuck needs an object binding to do its stuff.
|
27
|
+
bnd = Object.new
|
28
|
+
def bnd.get; binding; end
|
29
|
+
bnd = bnd.get
|
30
|
+
|
31
|
+
Brainfuck::CodeLoader.execute_code "+++>+++<---", bnd, nil
|
27
32
|
# => [0, 3]
|
28
33
|
|
34
|
+
|
29
35
|
## Note on Patches/Pull Requests
|
30
36
|
|
31
37
|
* Fork the project.
|
data/bin/brainfuck
CHANGED
data/brainfuck.gemspec
CHANGED
@@ -9,16 +9,15 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Josep M. Bach"]
|
10
10
|
s.email = ["josep.m.bach@gmail.com"]
|
11
11
|
s.homepage = "http://github.com/txus/brainfuck"
|
12
|
-
s.summary = %q{
|
13
|
-
s.description
|
12
|
+
s.summary = %q{An implementation of Brainfuck on the Rubinius VM.}
|
13
|
+
s.description = %q{An implementation of Brainfuck on the Rubinius VM.}
|
14
14
|
|
15
15
|
s.rubyforge_project = "brainfuck"
|
16
16
|
|
17
|
-
s.add_runtime_dependency "highline"
|
18
17
|
s.add_runtime_dependency "parslet"
|
19
|
-
|
20
|
-
s.add_development_dependency "
|
21
|
-
s.add_development_dependency "
|
18
|
+
|
19
|
+
s.add_development_dependency "minitest"
|
20
|
+
s.add_development_dependency "mocha"
|
22
21
|
|
23
22
|
s.files = `git ls-files`.split("\n")
|
24
23
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/brainfuck.rb
CHANGED
@@ -1,22 +1,9 @@
|
|
1
1
|
require 'parslet'
|
2
|
-
require 'highline/system_extensions'
|
3
2
|
|
4
|
-
require 'brainfuck/stack'
|
5
3
|
require 'brainfuck/ast'
|
6
|
-
|
4
|
+
require 'brainfuck/lexer'
|
7
5
|
require 'brainfuck/parser'
|
8
|
-
require 'brainfuck/
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def run code
|
13
|
-
Interpreter.stack.clear
|
14
|
-
|
15
|
-
code = Parser.clean code
|
16
|
-
parsed = Parser.new.parse code
|
17
|
-
ast = Interpreter.new.apply parsed
|
18
|
-
ast.each(&:eval)
|
19
|
-
Interpreter.stack.to_a
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
6
|
+
require 'brainfuck/stages'
|
7
|
+
require 'brainfuck/compiler'
|
8
|
+
require 'brainfuck/main'
|
9
|
+
require 'brainfuck/code_loader'
|
data/lib/brainfuck/ast.rb
CHANGED
@@ -1,30 +1,138 @@
|
|
1
1
|
module Brainfuck
|
2
2
|
module AST
|
3
|
-
class FwdNode
|
4
|
-
def
|
3
|
+
class FwdNode
|
4
|
+
def bytecode(g)
|
5
|
+
g.push_local 1
|
6
|
+
g.meta_push_1
|
7
|
+
g.meta_send_op_plus 0
|
8
|
+
g.set_local 1
|
9
|
+
|
10
|
+
# Check the contents of the new cell
|
11
|
+
g.push_local 0
|
12
|
+
g.swap_stack
|
13
|
+
g.send :[], 1, false
|
14
|
+
|
15
|
+
fin = g.new_label
|
16
|
+
g.git fin
|
17
|
+
g.pop
|
18
|
+
|
19
|
+
# If the cell is nil set it to 0
|
20
|
+
g.push_local 0
|
21
|
+
g.push_local 1
|
22
|
+
g.meta_push_0
|
23
|
+
g.send :[]=, 2, false
|
24
|
+
|
25
|
+
# Otherwise, do nothing
|
26
|
+
fin.set!
|
27
|
+
end
|
5
28
|
end
|
6
|
-
class BwdNode
|
7
|
-
def
|
29
|
+
class BwdNode
|
30
|
+
def bytecode(g)
|
31
|
+
g.push_local 1
|
32
|
+
g.meta_push_1
|
33
|
+
g.meta_send_op_minus 0
|
34
|
+
g.set_local 1
|
35
|
+
|
36
|
+
# Check the contents of the new cell
|
37
|
+
g.push_local 0
|
38
|
+
g.swap_stack
|
39
|
+
g.send :[], 1, false
|
40
|
+
|
41
|
+
fin = g.new_label
|
42
|
+
g.git fin
|
43
|
+
g.pop
|
44
|
+
|
45
|
+
# If the cell is nil set it to 0
|
46
|
+
g.push_local 0
|
47
|
+
g.push_local 1
|
48
|
+
g.meta_push_0
|
49
|
+
g.send :[]=, 2, false
|
50
|
+
|
51
|
+
# Otherwise, do nothing
|
52
|
+
fin.set!
|
53
|
+
end
|
8
54
|
end
|
9
55
|
|
10
|
-
class IncNode
|
11
|
-
def
|
56
|
+
class IncNode
|
57
|
+
def bytecode(g)
|
58
|
+
g.push_local 0
|
59
|
+
g.push_local 1
|
60
|
+
|
61
|
+
g.dup_many(2)
|
62
|
+
g.send :[], 1, false
|
63
|
+
g.meta_push_1
|
64
|
+
g.meta_send_op_plus 0
|
65
|
+
|
66
|
+
g.send :[]=, 2, false
|
67
|
+
g.pop
|
68
|
+
end
|
12
69
|
end
|
13
|
-
class DecNode
|
14
|
-
def
|
70
|
+
class DecNode
|
71
|
+
def bytecode(g)
|
72
|
+
g.push_local 0
|
73
|
+
g.push_local 1
|
74
|
+
|
75
|
+
g.dup_many(2)
|
76
|
+
g.send :[], 1, false
|
77
|
+
g.meta_push_1
|
78
|
+
g.meta_send_op_minus 0
|
79
|
+
|
80
|
+
g.send :[]=, 2, false
|
81
|
+
g.pop
|
82
|
+
end
|
15
83
|
end
|
16
|
-
class PutsNode
|
17
|
-
def
|
84
|
+
class PutsNode
|
85
|
+
def bytecode(g)
|
86
|
+
g.push_const :STDOUT
|
87
|
+
|
88
|
+
g.push_local 0
|
89
|
+
g.push_local 1
|
90
|
+
g.send :[], 1, false
|
91
|
+
|
92
|
+
g.send :putc, 1, true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
class GetsNode
|
96
|
+
def bytecode(g)
|
97
|
+
g.push :self
|
98
|
+
g.push_literal "stty raw -echo"
|
99
|
+
g.send :system, 1, true
|
100
|
+
g.pop
|
101
|
+
|
102
|
+
g.push_local 0
|
103
|
+
g.push_local 1
|
104
|
+
|
105
|
+
g.push_const :STDIN
|
106
|
+
g.send :getc, 0, false
|
107
|
+
|
108
|
+
g.send :[]=, 2, false
|
109
|
+
|
110
|
+
g.push :self
|
111
|
+
g.push_literal "stty -raw echo"
|
112
|
+
g.send :system, 1, true
|
113
|
+
g.pop
|
114
|
+
end
|
18
115
|
end
|
19
|
-
class
|
20
|
-
def
|
116
|
+
class IterationNode < Struct.new(:exp)
|
117
|
+
def bytecode(g)
|
118
|
+
repeat = g.new_label
|
119
|
+
repeat.set!
|
120
|
+
|
121
|
+
exp.bytecode(g)
|
122
|
+
|
123
|
+
g.push_local 0
|
124
|
+
g.push_local 1
|
125
|
+
g.send :[], 1, true
|
126
|
+
g.meta_push_0
|
127
|
+
g.meta_send_op_equal 0
|
128
|
+
|
129
|
+
g.gif repeat
|
130
|
+
end
|
21
131
|
end
|
22
|
-
class
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
node.eval
|
27
|
-
end
|
132
|
+
class Script < Struct.new(:exp)
|
133
|
+
def bytecode(g)
|
134
|
+
exp.each do |e|
|
135
|
+
e.bytecode(g)
|
28
136
|
end
|
29
137
|
end
|
30
138
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Brainfuck
|
2
|
+
class CodeLoader < Rubinius::CodeLoader
|
3
|
+
|
4
|
+
def self.execute_code(code, binding, from_module, print = Compiler::Print.new)
|
5
|
+
cm = Compiler.compile_for_eval(code, binding.variables,
|
6
|
+
"(eval)", 1, print)
|
7
|
+
cm.scope = binding.static_scope.dup
|
8
|
+
cm.name = :__eval__
|
9
|
+
|
10
|
+
script = Rubinius::CompiledMethod::Script.new(cm, "(eval)", true)
|
11
|
+
script.eval_binding = binding
|
12
|
+
script.eval_source = code
|
13
|
+
|
14
|
+
cm.scope.script = script
|
15
|
+
|
16
|
+
be = Rubinius::BlockEnvironment.new
|
17
|
+
be.under_context(binding.variables, cm)
|
18
|
+
be.from_eval!
|
19
|
+
be.call
|
20
|
+
end
|
21
|
+
|
22
|
+
# Takes a .bf file name, compiles it if needed and executes it.
|
23
|
+
def self.execute_file(name, compile_to = nil, print = Compiler::Print.new)
|
24
|
+
cm = Compiler.compile_if_needed(name, compile_to, print)
|
25
|
+
ss = ::Rubinius::StaticScope.new Object
|
26
|
+
code = Object.new
|
27
|
+
::Rubinius.attach_method(:__run__, cm, ss, code)
|
28
|
+
code.__run__
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Brainfuck
|
2
|
+
class Compiler < Rubinius::Compiler
|
3
|
+
|
4
|
+
def self.compiled_filename(filename)
|
5
|
+
if filename =~ /.bf$/
|
6
|
+
filename + "c"
|
7
|
+
else
|
8
|
+
filename + ".compiled.bfc"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.always_recompile=(flag)
|
13
|
+
@always_recompile = flag
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.compile_if_needed(file, output = nil, print = Print.new)
|
17
|
+
compiled = output || compiled_filename(file)
|
18
|
+
needed = @always_recompile || !File.exists?(compiled) ||
|
19
|
+
File.stat(compiled).mtime < File.stat(file).mtime
|
20
|
+
if needed
|
21
|
+
compile_file(file, compiled, print)
|
22
|
+
else
|
23
|
+
Brainfuck::CodeLoader.new(compiled).load_compiled_file(compiled, 0)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def self.compile_file(file, output = nil, print = Print.new)
|
29
|
+
compiler = new :brainfuck_file, :compiled_file
|
30
|
+
parser = compiler.parser
|
31
|
+
|
32
|
+
parser.input file
|
33
|
+
|
34
|
+
compiler.generator = Rubinius::Generator.new
|
35
|
+
compiler.writer.name = output || compiled_filename(file)
|
36
|
+
|
37
|
+
parser.print = print
|
38
|
+
compiler.packager.print.bytecode = true if print.asm?
|
39
|
+
|
40
|
+
begin
|
41
|
+
compiler.run
|
42
|
+
|
43
|
+
rescue Exception => e
|
44
|
+
compiler_error "Error trying to compile brainfuck: #{file}", e
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.compile_for_eval(code, variable_scope, file = "(eval)", line = 0, print = Print.new)
|
49
|
+
compiler = new :brainfuck_code, :compiled_method
|
50
|
+
parser = compiler.parser
|
51
|
+
|
52
|
+
parser.input code, file, line
|
53
|
+
compiler.generator.root = Rubinius::AST::EvalExpression
|
54
|
+
compiler.generator.variable_scope = variable_scope
|
55
|
+
|
56
|
+
parser.print = print
|
57
|
+
compiler.packager.print.bytecode = true if print.asm?
|
58
|
+
|
59
|
+
begin
|
60
|
+
compiler.run
|
61
|
+
rescue Exception => e
|
62
|
+
compiler_error "Error trying to compile brainfuck: #{file}", e
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Print < Struct.new(:sexp, :ast, :asm)
|
67
|
+
def sexp?
|
68
|
+
@sexp
|
69
|
+
end
|
70
|
+
|
71
|
+
def ast?
|
72
|
+
@ast
|
73
|
+
end
|
74
|
+
|
75
|
+
def asm?
|
76
|
+
@asm
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|