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
@@ -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
|
data/lib/brainfuck/parser.rb
CHANGED
@@ -1,31 +1,16 @@
|
|
1
1
|
module Brainfuck
|
2
|
-
class Parser < Parslet::
|
3
|
-
|
2
|
+
class Parser < Parslet::Transform
|
3
|
+
rule(:fwd => simple(:fwd)) { AST::FwdNode.new }
|
4
|
+
rule(:bwd => simple(:bwd)) { AST::BwdNode.new }
|
4
5
|
|
5
|
-
|
6
|
-
|
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(:
|
12
|
-
rule(:
|
9
|
+
rule(:puts => simple(:puts)) { AST::PutsNode.new }
|
10
|
+
rule(:gets => simple(:gets)) { AST::GetsNode.new }
|
13
11
|
|
14
|
-
rule(:
|
15
|
-
rule(:space?) { space.maybe }
|
12
|
+
rule(:iteration => subtree(:iteration)) { AST::IterationNode.new(iteration) }
|
16
13
|
|
17
|
-
rule(:
|
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
|
data/lib/brainfuck/version.rb
CHANGED
@@ -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,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
|