brainfuck 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ module Brainfuck
2
+ class Lexer < Parslet::Parser
3
+ INSTRUCTIONS = %w{> < + - [ ] . ,}
4
+
5
+ alias_method :tokenize, :parse
6
+ class << self
7
+ def clean code
8
+ code.chars.select {|c| INSTRUCTIONS.include? c }.join
9
+ end
10
+ end
11
+
12
+ rule(:lparen) { str('[') >> space? }
13
+ rule(:rparen) { str(']') >> space? }
14
+
15
+ rule(:space) { match('\s').repeat(1) }
16
+ rule(:space?) { space.maybe }
17
+
18
+ rule(:fwd) { str('>') >> space? }
19
+ rule(:bwd) { str('<') >> space? }
20
+
21
+ rule(:inc) { str('+') >> space? }
22
+ rule(:dec) { str('-') >> space? }
23
+
24
+ rule(:puts) { str('.') >> space? }
25
+ rule(:gets) { str(',') >> space? }
26
+
27
+ rule(:iteration) { lparen >> expression >> rparen }
28
+
29
+ rule(:expression) { (iteration.as(:iteration) | fwd.as(:fwd) | bwd.as(:bwd) | inc.as(:inc) | dec.as(:dec) | puts.as(:puts) | gets.as(:gets)).repeat.as(:exp) }
30
+ root :expression
31
+ end
32
+ end
@@ -0,0 +1,99 @@
1
+ require 'brainfuck/version'
2
+
3
+ module Brainfuck
4
+
5
+ # Command line interface to Brainfuck.
6
+ #
7
+ # Currently we only take a brainfuck source name to compile and
8
+ # run.
9
+ #
10
+ class Main
11
+
12
+ def initialize
13
+ @print = Compiler::Print.new
14
+ @compile_only = false
15
+ @evals = []
16
+ @rest = []
17
+ end
18
+
19
+ def main(argv=ARGV)
20
+ options(argv)
21
+ evals unless @evals.empty?
22
+ script unless @rest.empty?
23
+ compile if @compile_only
24
+ end
25
+
26
+ # Batch compile all brainfuck files given as arguments.
27
+ def compile
28
+ @rest.each do |bf|
29
+ begin
30
+ Compiler.compile_file bf, nil, @print
31
+ rescue Compiler::Error => e
32
+ e.show
33
+ end
34
+ end
35
+ end
36
+
37
+ # Evaluate code given on command line
38
+ def evals
39
+ bnd = Object.new
40
+ def bnd.get; binding; end
41
+ bnd = bnd.get
42
+ mod = nil
43
+ @evals.each do |code|
44
+ CodeLoader.execute_code code, bnd, mod, @print
45
+ end
46
+ end
47
+
48
+ # Run the given script if any
49
+ def script
50
+ CodeLoader.execute_file @rest.first, nil, @print
51
+ end
52
+
53
+ # Parse command line options
54
+ def options(argv)
55
+ options = Rubinius::Options.new "Usage: brainfuck [options] [program]", 20
56
+ options.doc "Brainfuck is a Brainfuck implementation for the Rubinius VM."
57
+ options.doc ""
58
+ options.doc "OPTIONS:"
59
+
60
+ # options.on "-", "Read and evalute code from STDIN" do
61
+ # @evals << STDIN.read
62
+ # end
63
+
64
+ # options.on "--print-ast", "Print the Brainfuck AST" do
65
+ # @print.ast = true
66
+ # end
67
+
68
+ # options.on "--print-asm", "Print the Rubinius ASM" do
69
+ # @print.asm = true
70
+ # end
71
+
72
+ # options.on "--print-sexp", "Print the Brainfuck Sexp" do
73
+ # @print.sexp = true
74
+ # end
75
+
76
+ # options.on "--print-all", "Print Sexp, AST and Rubinius ASM" do
77
+ # @print.ast = @print.asm = @print.sexp = true
78
+ # end
79
+
80
+ options.on "-C", "--compile", "Just batch compile dont execute." do
81
+ @compile_only = true
82
+ end
83
+ #
84
+ # options.on "-e", "CODE", "Execute CODE" do |e|
85
+ # @evals << e
86
+ # end
87
+
88
+ options.on "-h", "--help", "Display this help" do
89
+ puts options
90
+ exit 0
91
+ end
92
+
93
+ options.doc ""
94
+
95
+ @rest = options.parse(argv)
96
+ end
97
+
98
+ end
99
+ end
@@ -1,31 +1,16 @@
1
1
  module Brainfuck
2
- class Parser < Parslet::Parser
3
- INSTRUCTIONS = %w{> < + - [ ] . ,}
2
+ class Parser < Parslet::Transform
3
+ rule(:fwd => simple(:fwd)) { AST::FwdNode.new }
4
+ rule(:bwd => simple(:bwd)) { AST::BwdNode.new }
4
5
 
5
- class << self
6
- def clean code
7
- code.chars.select {|c| INSTRUCTIONS.include? c }.join
8
- end
9
- end
6
+ rule(:inc => simple(:inc)) { AST::IncNode.new }
7
+ rule(:dec => simple(:dec)) { AST::DecNode.new }
10
8
 
11
- rule(:lparen) { str('[') >> space? }
12
- rule(:rparen) { str(']') >> space? }
9
+ rule(:puts => simple(:puts)) { AST::PutsNode.new }
10
+ rule(:gets => simple(:gets)) { AST::GetsNode.new }
13
11
 
14
- rule(:space) { match('\s').repeat(1) }
15
- rule(:space?) { space.maybe }
12
+ rule(:iteration => subtree(:iteration)) { AST::IterationNode.new(iteration) }
16
13
 
17
- rule(:fwd) { str('>') >> space? }
18
- rule(:bwd) { str('<') >> space? }
19
-
20
- rule(:inc) { str('+') >> space? }
21
- rule(:dec) { str('-') >> space? }
22
-
23
- rule(:puts) { str('.') >> space? }
24
- rule(:gets) { str(',') >> space? }
25
-
26
- rule(:iteration) { lparen >> expression >> rparen }
27
-
28
- rule(:expression) { (iteration.as(:iteration) | fwd.as(:fwd) | bwd.as(:bwd) | inc.as(:inc) | dec.as(:dec) | puts.as(:puts) | gets.as(:gets)).repeat.as(:exp) }
29
- root :expression
14
+ rule(:exp => subtree(:exp)) { AST::Script.new(exp) }
30
15
  end
31
16
  end
@@ -0,0 +1,170 @@
1
+ require 'pp'
2
+ module Brainfuck
3
+ class Stage
4
+
5
+
6
+ # This stage takes a tree of Brainfuck::AST nodes and
7
+ # simply calls the bytecode method on them.
8
+ class Generator < Rubinius::Compiler::Stage
9
+ next_stage Rubinius::Compiler::Encoder
10
+ attr_accessor :variable_scope, :root
11
+
12
+ def initialize(compiler, last)
13
+ super
14
+ @compiler = compiler
15
+ @variable_scope = nil
16
+ compiler.generator = self
17
+ end
18
+
19
+ def run
20
+ @output = Rubinius::Generator.new
21
+
22
+ @output.set_line Integer(1)
23
+
24
+ # ENVIRONMENT INITIALIZATION
25
+ # --------------------------
26
+
27
+ # Initialize the heap as a one-cell array containing zero,
28
+ # and the pointer as a zero integer.
29
+ @output.meta_push_0
30
+ @output.set_local 1
31
+ @output.cast_array
32
+ @output.set_local 0
33
+
34
+ @input.bytecode @output
35
+
36
+ bottom = @output.new_label
37
+
38
+ @output.push_const :ENV
39
+ @output.push_literal "DEBUG"
40
+ @output.send :[], 1, false
41
+
42
+ @output.gif bottom
43
+
44
+ # Print the heap and the pointer if ENV['DEBUG']
45
+ @output.push_literal "Heap: "
46
+
47
+ @output.push_local 0
48
+ @output.send :inspect, 0
49
+
50
+ @output.push_literal "\nPointer: "
51
+ @output.push_local 1
52
+ @output.push_literal "\n"
53
+ @output.send :print, 5, true
54
+ # end Print
55
+
56
+ bottom.set!
57
+
58
+ @output.use_detected
59
+ # Return the heap
60
+ @output.push_local 0
61
+ @output.ret
62
+ @output.close
63
+
64
+ run_next
65
+ end
66
+
67
+ end
68
+
69
+ # AST trasnformation for evaling source string.
70
+ #
71
+ # This stage removes the ModuleNode from root of AST tree if
72
+ # the code being compiled is going to be used for eval. We remove
73
+ # the module, because in eval we must not return the module object
74
+ # Also if the last statement is a DiscardNode, we replace it with
75
+ # its inner expression, in order to return a value.
76
+ #
77
+ # If the source is not being compiled for eval, then output is
78
+ # the same AST given as input.
79
+ class EvalExpr < Rubinius::Compiler::Stage
80
+ next_stage Generator
81
+
82
+ def initialize(compiler, last)
83
+ @compiler = compiler
84
+ super
85
+ end
86
+
87
+ def run
88
+ @output = @input
89
+ run_next
90
+ end
91
+ end
92
+
93
+ # This stage takes a ruby array as produced by the lexer
94
+ # and produces a tree of Brainfuck::AST nodes.
95
+ class BfAST < Rubinius::Compiler::Stage
96
+ next_stage EvalExpr
97
+
98
+ def initialize(compiler, last)
99
+ @compiler = compiler
100
+ super
101
+ end
102
+
103
+ def run
104
+ @output = Brainfuck::Parser.new.apply @input
105
+ pp(@output) if @compiler.parser.print.ast?
106
+ run_next
107
+ end
108
+ end
109
+
110
+ # This stage takes python code and produces a ruby array
111
+ # containing representation of the python source.
112
+ # We are currently using python's own parser, so we just
113
+ # read the sexp as its printed by bin/astpretty.py
114
+ class BfCode < Rubinius::Compiler::Stage
115
+
116
+ stage :brainfuck_code
117
+ next_stage BfAST
118
+ attr_reader :filename, :line
119
+ attr_accessor :print
120
+
121
+ def initialize(compiler, last)
122
+ super
123
+ @print = Compiler::Print.new
124
+ compiler.parser = self
125
+ end
126
+
127
+ def input(code, filename = "eval", line = 1)
128
+ @code = code
129
+ @filename = filename
130
+ @line = line
131
+ end
132
+
133
+ def run
134
+ code = Lexer.clean(@code)
135
+ @output = Lexer.new.tokenize(code)
136
+ pp(@output) if @print.sexp?
137
+ run_next
138
+ end
139
+ end
140
+
141
+ # This stage takes a brainfuck filename and produces a ruby array
142
+ # containing representation of the brainfuck source.
143
+ class BfFile < Rubinius::Compiler::Stage
144
+
145
+ stage :brainfuck_file
146
+ next_stage BfAST
147
+ attr_reader :filename, :line
148
+ attr_accessor :print
149
+
150
+ def initialize(compiler, last)
151
+ super
152
+ @print = Compiler::Print.new
153
+ compiler.parser = self
154
+ end
155
+
156
+ def input(filename, line = 1)
157
+ @filename = filename
158
+ @line = line
159
+ end
160
+
161
+ def run
162
+ code = Lexer.clean(File.read(@filename))
163
+ @output = Lexer.new.tokenize(code)
164
+ pp(@output) if @print.sexp?
165
+ run_next
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -1,3 +1,3 @@
1
1
  module Brainfuck
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ class BrainfuckAcceptanceTest < MiniTest::Unit::TestCase
4
+
5
+ def test_ok
6
+ assert_evaluates [2,1], "++++>++++---.<--."
7
+ end
8
+
9
+ def test_without_loops_nor_user_input
10
+ STDOUT.expects(:putc).times(2)
11
+ assert_evaluates [2,1], "++++>++++---.<--."
12
+ end
13
+
14
+ def test_with_user_input
15
+ STDIN.expects(:getc).returns 97
16
+
17
+ assert_evaluates [101], ",++++"
18
+ end
19
+
20
+ def test_with_loops
21
+ assert_evaluates [1], "++++[-]+-+"
22
+ end
23
+
24
+ def test_transfers_from_one_cell_to_another
25
+ assert_evaluates [0, 10], "++++++++++ [>+<-]"
26
+ end
27
+
28
+ def test_transfers_from_one_cell_to_the_third
29
+ assert_evaluates [0, 0, 10], "++++++++++ [>+<-]>[>+<-]"
30
+ end
31
+
32
+ def test_transfers_from_one_cell_to_the_third_and_back_to_the_second
33
+ assert_evaluates [0, 10, 0], "++++++++++ [>+<-]>[>+<-]>[<+>-]"
34
+ end
35
+
36
+ def test_nested_loop_examples
37
+ assert_evaluates [0], "[++++++++++[-]+-+-]"
38
+ end
39
+
40
+ def test_hello_world
41
+ assert_evaluates [0, 87, 100, 33, 10], <<-EOS
42
+ +++++ +++++
43
+ [
44
+ > +++++ ++
45
+ > +++++ +++++
46
+ > +++
47
+ > +
48
+ <<<< -
49
+ ]
50
+ > ++ .
51
+ > + .
52
+ +++++ ++ .
53
+ .
54
+ +++ .
55
+ > ++ .
56
+ << +++++ +++++ +++++ .
57
+ > .
58
+ +++ .
59
+ ----- - .
60
+ ----- --- .
61
+ > + .
62
+ > .
63
+ EOS
64
+ end
65
+
66
+ private
67
+
68
+ def assert_evaluates(expected, code)
69
+ bnd = Object.new
70
+ def bnd.get; binding; end
71
+ bnd = bnd.get
72
+ mod = nil
73
+ assert_equal expected, Brainfuck::CodeLoader.execute_code(code, bnd, mod)
74
+ end
75
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ module Brainfuck
4
+ class ASTTest < MiniTest::Unit::TestCase
5
+
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ module Brainfuck
4
+ class LexerTest < MiniTest::Unit::TestCase
5
+
6
+ def test_INSTRUCTIONS_returns_valid_symbols
7
+ assert_equal %w{> < + - [ ] . ,}, Lexer::INSTRUCTIONS
8
+ end
9
+
10
+ def test_clean_cleans_all_invalid_symbols
11
+ assert_equal '><+-[-].,', Lexer.clean(">3< 223+fn - ()()[r23-] .bdn*& ,")
12
+ end
13
+
14
+ %w{lparen rparen space space? fwd bwd inc dec puts gets iteration expression}.each do |rule|
15
+ define_method("test_implements_a_#{rule}_rule") do
16
+ assert Lexer.new.respond_to?(rule), "Parser should implement a #{rule} rule"
17
+ end
18
+ end
19
+
20
+ end
21
+ end