brainclusterfuck 0.0.1 → 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/.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
|
+
[](https://travis-ci.org/mark-rushakoff/brainclusterfuck)
|
4
|
+
[](https://codeclimate.com/github/mark-rushakoff/brainclusterfuck)
|
5
|
+
[](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
|