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 CHANGED
@@ -2,3 +2,5 @@ pkg/*
2
2
  *.gem
3
3
  .bundle
4
4
  coverage
5
+ *.rbc
6
+ *.bfc
@@ -1,29 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brainfuck (0.1.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.3)
12
- diff-lcs (1.1.2)
13
- highline (1.6.1)
14
- parslet (1.0.0)
15
- blankslate (~> 2.1.2.3)
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
- bundler
35
- highline
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 'simplecov'
5
- SimpleCov.start do
6
- add_group "Lib", 'lib'
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 'rspec/core'
10
- require 'rspec/core/rake_task'
11
- RSpec::Core::RakeTask.new(:spec) do |spec|
12
- spec.pattern = FileList['spec/**/*_spec.rb']
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 => :spec
17
- task :test => [:spec]
19
+ task :default => [:test, :acceptance]
data/Readme.md CHANGED
@@ -1,31 +1,37 @@
1
1
  # brainfuck
2
2
 
3
- Just another Brainfuck interpreter in Ruby!
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
- This interpreter works with MRI 1.8.7, 1.9.2 and JRuby 1.5.5.
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: `brainfuck my_file.bf`
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.run "+++>+++<---"
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.
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'brainfuck'
3
- Brainfuck.run File.read(ARGV.first)
3
+ Brainfuck::Main.new.main ARGV.first
@@ -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{Another Brainfuck interpreter in Ruby}
13
- s.description = %q{Another Brainfuck interpreter in Ruby}
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
- s.add_development_dependency "rspec"
20
- s.add_development_dependency "bundler"
21
- s.add_development_dependency "simplecov"
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")
@@ -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/interpreter'
9
-
10
- module Brainfuck
11
- class << self
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'
@@ -1,30 +1,138 @@
1
1
  module Brainfuck
2
2
  module AST
3
- class FwdNode < Struct.new(:stack)
4
- def eval; stack.fwd ; end
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 < Struct.new(:stack)
7
- def eval; stack.bwd ; end
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 < Struct.new(:stack)
11
- def eval; stack.inc ; end
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 < Struct.new(:stack)
14
- def eval; stack.dec ; end
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 < Struct.new(:stack)
17
- def eval; stack.puts ; end
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 GetsNode < Struct.new(:stack)
20
- def eval; stack.gets ; end
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 IterationNode < Struct.new(:stack, :exp)
23
- def eval
24
- until stack.current == 0
25
- exp.each do |node|
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