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.
@@ -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