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