brainclusterfuck 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +6 -0
- data/Gemfile +1 -1
- data/README.md +4 -0
- data/lib/brainclusterfuck/compile_error.rb +10 -0
- data/lib/brainclusterfuck/compiler.rb +57 -35
- data/lib/brainclusterfuck/error.rb +5 -0
- data/lib/brainclusterfuck/interpreter.rb +84 -0
- data/lib/brainclusterfuck/interpreter_error.rb +4 -0
- data/lib/brainclusterfuck/lexer.rb +2 -2
- data/lib/brainclusterfuck/loop_error.rb +6 -0
- data/lib/brainclusterfuck/memory.rb +33 -0
- data/lib/brainclusterfuck/memory_error.rb +6 -0
- data/lib/brainclusterfuck/opcodes.rb +5 -0
- data/lib/brainclusterfuck/opcodes/base.rb +19 -0
- data/lib/brainclusterfuck/opcodes/loop_base.rb +27 -0
- data/lib/brainclusterfuck/opcodes/loop_end.rb +10 -0
- data/lib/brainclusterfuck/opcodes/loop_end_placeholder.rb +6 -0
- data/lib/brainclusterfuck/opcodes/loop_placeholder.rb +20 -0
- data/lib/brainclusterfuck/opcodes/loop_start.rb +10 -0
- data/lib/brainclusterfuck/opcodes/loop_start_placeholder.rb +6 -0
- data/lib/brainclusterfuck/opcodes/modify_pointer.rb +9 -0
- data/lib/brainclusterfuck/opcodes/modify_value.rb +6 -0
- data/lib/brainclusterfuck/opcodes/modifying_base.rb +27 -0
- data/lib/brainclusterfuck/opcodes/print.rb +13 -0
- data/lib/brainclusterfuck/terminal.rb +4 -0
- data/lib/brainclusterfuck/version.rb +1 -1
- data/spec/compiler_spec.rb +82 -16
- data/spec/integration/basic_loops_spec.rb +55 -0
- data/spec/integration/basic_print_spec.rb +65 -0
- data/spec/integration/boundary_conditions_spec.rb +81 -0
- data/spec/interpreter_spec.rb +154 -0
- data/spec/lexer_spec.rb +2 -2
- data/spec/memory_spec.rb +33 -0
- data/spec/opcodes/base_spec.rb +26 -0
- data/spec/opcodes/loop_end_placeholder_spec.rb +5 -0
- data/spec/opcodes/loop_end_spec.rb +7 -0
- data/spec/opcodes/loop_start_placeholder_spec.rb +5 -0
- data/spec/opcodes/loop_start_spec.rb +7 -0
- data/spec/opcodes/modify_pointer_spec.rb +6 -0
- data/spec/opcodes/modify_value_spec.rb +6 -0
- data/spec/{opcode → opcodes}/print_spec.rb +3 -3
- data/spec/spec_helper.rb +2 -0
- data/spec/support/loop_opcode_shared_behavior.rb +22 -0
- data/spec/support/loop_placeholder_shared_behavior.rb +12 -0
- data/spec/{opcode/modify_value_spec.rb → support/modifying_opcode_shared_behavior.rb} +4 -7
- data/spec/terminal_spec.rb +11 -0
- metadata +60 -20
- data/lib/brainclusterfuck/cells.rb +0 -9
- data/lib/brainclusterfuck/opcode.rb +0 -11
- data/lib/brainclusterfuck/opcode/modify_pointer.rb +0 -27
- data/lib/brainclusterfuck/opcode/modify_value.rb +0 -27
- data/lib/brainclusterfuck/opcode/print.rb +0 -13
- data/lib/brainclusterfuck/vm.rb +0 -11
- data/spec/cells_spec.rb +0 -8
- data/spec/opcode/modify_pointer_spec.rb +0 -37
- data/spec/opcode_spec.rb +0 -10
- data/spec/vm_spec.rb +0 -22
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Brainclusterfuck
|
2
2
|
|
3
|
+
[![Build Status](https://travis-ci.org/mark-rushakoff/brainclusterfuck.png)](https://travis-ci.org/mark-rushakoff/brainclusterfuck)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/mark-rushakoff/brainclusterfuck.png)](https://codeclimate.com/github/mark-rushakoff/brainclusterfuck)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/brainclusterfuck.png)](http://badge.fury.io/rb/brainclusterfuck)
|
6
|
+
|
3
7
|
A clean interface to controlling arrays of specialized Brainfuck VMs
|
4
8
|
|
5
9
|
## Installation
|
@@ -1,66 +1,88 @@
|
|
1
|
-
require 'brainclusterfuck/
|
2
|
-
require 'brainclusterfuck/
|
1
|
+
require 'brainclusterfuck/opcodes'
|
2
|
+
require 'brainclusterfuck/opcodes/loop_start_placeholder'
|
3
|
+
require 'brainclusterfuck/opcodes/loop_end_placeholder'
|
4
|
+
require 'brainclusterfuck/compile_error'
|
3
5
|
|
4
6
|
module Brainclusterfuck
|
5
7
|
class Compiler
|
6
8
|
attr_reader :bytecode
|
7
9
|
def initialize(tokens)
|
8
|
-
raise "No tokens supplied" if tokens.size == 0
|
10
|
+
raise CompileError, "No tokens supplied" if tokens.size == 0
|
9
11
|
|
10
12
|
@bytecode = tokens.map do |token|
|
11
13
|
process_token(token)
|
12
14
|
end
|
15
|
+
|
16
|
+
resolve_loops!
|
13
17
|
end
|
14
18
|
|
15
19
|
# Merge consecutive modify value or modify pointer operations
|
16
20
|
def squeeze_operations!
|
17
|
-
|
21
|
+
modifying_bytecode_length do
|
22
|
+
compressed = [@bytecode.shift]
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
@bytecode.each do |opcode|
|
25
|
+
last = compressed[-1]
|
26
|
+
if opcode.can_squeeze_with?(last)
|
27
|
+
compressed[-1] = opcode.squeeze_with(last)
|
28
|
+
else
|
29
|
+
compressed << opcode
|
30
|
+
end
|
25
31
|
end
|
26
|
-
end
|
27
32
|
|
28
|
-
|
33
|
+
@bytecode = compressed
|
34
|
+
end
|
29
35
|
end
|
30
36
|
|
31
37
|
private
|
38
|
+
def modifying_bytecode_length(&blk)
|
39
|
+
unresolve_loops!
|
40
|
+
yield
|
41
|
+
resolve_loops!
|
42
|
+
end
|
43
|
+
|
44
|
+
def resolve_loops!
|
45
|
+
loop_stack = []
|
46
|
+
|
47
|
+
@bytecode.each_with_index do |op, index|
|
48
|
+
if op.is_a?(Opcodes::LoopStartPlaceholder)
|
49
|
+
loop_stack.push(op: op, index: index)
|
50
|
+
elsif op.is_a?(Opcodes::LoopEndPlaceholder)
|
51
|
+
raise PrematurelyTerminatedLoopError if loop_stack.empty?
|
52
|
+
|
53
|
+
opening = loop_stack.pop
|
54
|
+
num_operations = index - opening[:index] - 1
|
55
|
+
@bytecode[opening[:index]] = Opcodes::LoopStart.new(num_operations)
|
56
|
+
@bytecode[index] = Opcodes::LoopEnd.new(num_operations)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
raise UnterminatedLoopError unless loop_stack.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
def unresolve_loops!
|
64
|
+
@bytecode.map! { |op| op.unresolve_loop }
|
65
|
+
end
|
66
|
+
|
32
67
|
def process_token(token)
|
33
68
|
case token
|
34
69
|
when :v_incr
|
35
|
-
|
70
|
+
Opcodes::ModifyValue.new(1, 1)
|
36
71
|
when :v_decr
|
37
|
-
|
72
|
+
Opcodes::ModifyValue.new(-1, 1)
|
38
73
|
when :p_incr
|
39
|
-
|
74
|
+
Opcodes::ModifyPointer.new(1, 1)
|
40
75
|
when :p_decr
|
41
|
-
|
76
|
+
Opcodes::ModifyPointer.new(-1, 1)
|
42
77
|
when :print
|
43
|
-
|
78
|
+
Opcodes::Print.new
|
79
|
+
when :loop_start
|
80
|
+
Opcodes::LoopStartPlaceholder.new
|
81
|
+
when :loop_end
|
82
|
+
Opcodes::LoopEndPlaceholder.new
|
44
83
|
else
|
45
|
-
raise "Don't know how to handle token: #{token}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def compress_bytecode(bytecode)
|
50
|
-
compressed = [bytecode.shift]
|
51
|
-
|
52
|
-
bytecode.each do |op, value|
|
53
|
-
case op
|
54
|
-
when :modify_value, :modify_pointer
|
55
|
-
if compressed[-1][0] == op
|
56
|
-
compressed[-1][1] += value
|
57
|
-
else
|
58
|
-
compressed << [op, value]
|
59
|
-
end
|
60
|
-
end
|
84
|
+
raise CompileError, "Don't know how to handle token: #{token}"
|
61
85
|
end
|
62
|
-
|
63
|
-
compressed
|
64
86
|
end
|
65
87
|
end
|
66
88
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'brainclusterfuck/interpreter_error'
|
2
|
+
|
3
|
+
module Brainclusterfuck
|
4
|
+
class Interpreter
|
5
|
+
attr_reader :terminal, :memory, :cycles, :instruction_pointer, :finished
|
6
|
+
def initialize(opts)
|
7
|
+
@terminal = opts.fetch(:terminal)
|
8
|
+
@memory = opts.fetch(:memory)
|
9
|
+
@bytecode = opts.fetch(:bytecode)
|
10
|
+
|
11
|
+
@cycles = 0
|
12
|
+
@instruction_pointer = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def step(cycles_allowed = 1)
|
16
|
+
raise ArgumentError, "cycles_allowed must be positive" unless cycles_allowed > 0
|
17
|
+
|
18
|
+
while cycles_allowed > 0
|
19
|
+
opcode = @bytecode[@instruction_pointer]
|
20
|
+
|
21
|
+
unless opcode
|
22
|
+
@finished = true
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
cycles_allowed -= opcode.cycles
|
27
|
+
@cycles += opcode.cycles
|
28
|
+
|
29
|
+
execute(opcode)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def finished?
|
34
|
+
!!@finished
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def execute(opcode)
|
39
|
+
@op_to_method ||= {
|
40
|
+
Opcodes::ModifyValue => :modify_value,
|
41
|
+
Opcodes::ModifyPointer => :modify_pointer,
|
42
|
+
Opcodes::LoopStart => :loop_start,
|
43
|
+
Opcodes::LoopEnd => :loop_end,
|
44
|
+
Opcodes::Print => :print
|
45
|
+
}
|
46
|
+
|
47
|
+
method = @op_to_method[opcode.class]
|
48
|
+
raise ArgumentError, "Don't know how to handle #{opcode}" if method.nil?
|
49
|
+
__send__(method, opcode)
|
50
|
+
@finished = @instruction_pointer >= @bytecode.size
|
51
|
+
end
|
52
|
+
|
53
|
+
def modify_value(opcode)
|
54
|
+
@memory.modify_value(opcode.modify_by)
|
55
|
+
@instruction_pointer += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def modify_pointer(opcode)
|
59
|
+
@memory.modify_pointer(opcode.modify_by)
|
60
|
+
@instruction_pointer += 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def print(_opcode)
|
64
|
+
@terminal.print(@memory.current_char)
|
65
|
+
@instruction_pointer += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def loop_start(opcode)
|
69
|
+
if @memory.current_value.zero?
|
70
|
+
@instruction_pointer += (opcode.num_operations + 2)
|
71
|
+
else
|
72
|
+
@instruction_pointer += 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def loop_end(opcode)
|
77
|
+
if @memory.current_value.zero?
|
78
|
+
@instruction_pointer += 1
|
79
|
+
else
|
80
|
+
@instruction_pointer -= opcode.num_operations
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'brainclusterfuck/memory_error'
|
2
|
+
|
3
|
+
module Brainclusterfuck
|
4
|
+
class Memory
|
5
|
+
attr_reader :size
|
6
|
+
|
7
|
+
def initialize(size)
|
8
|
+
@size = size.to_i
|
9
|
+
|
10
|
+
@pointer = 0
|
11
|
+
@cells = Hash.new { 0 }
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_char
|
15
|
+
current_value.chr
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_value
|
19
|
+
@cells[@pointer]
|
20
|
+
end
|
21
|
+
|
22
|
+
def modify_value(amount)
|
23
|
+
# keep value in one byte
|
24
|
+
@cells[@pointer] = (@cells[@pointer] + amount.to_i) & 0xFF
|
25
|
+
end
|
26
|
+
|
27
|
+
def modify_pointer(amount)
|
28
|
+
@pointer += amount
|
29
|
+
|
30
|
+
raise MemoryError if (@pointer < 0 || @pointer >= @size)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'brainclusterfuck/opcodes/base'
|
2
|
+
require 'brainclusterfuck/loop_error'
|
3
|
+
|
4
|
+
module Brainclusterfuck
|
5
|
+
module Opcodes
|
6
|
+
class LoopBase < Base
|
7
|
+
attr_reader :num_operations
|
8
|
+
|
9
|
+
def initialize(num_operations)
|
10
|
+
@num_operations = num_operations.to_i
|
11
|
+
@cycles = 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
other.class == self.class && other.num_operations == num_operations
|
16
|
+
end
|
17
|
+
|
18
|
+
def unresolve_loop
|
19
|
+
placeholder_class.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve_loop
|
23
|
+
raise LoopError, 'Loop already resolved'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'brainclusterfuck/opcodes/base'
|
2
|
+
require 'brainclusterfuck/loop_error'
|
3
|
+
|
4
|
+
module Brainclusterfuck
|
5
|
+
module Opcodes
|
6
|
+
class LoopPlaceholder < Base
|
7
|
+
def initialize
|
8
|
+
@cycles = 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def unresolve_loop
|
12
|
+
raise LoopError, "Already unresolved"
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
other.class == self.class
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|