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.
Files changed (57) hide show
  1. data/.travis.yml +6 -0
  2. data/Gemfile +1 -1
  3. data/README.md +4 -0
  4. data/lib/brainclusterfuck/compile_error.rb +10 -0
  5. data/lib/brainclusterfuck/compiler.rb +57 -35
  6. data/lib/brainclusterfuck/error.rb +5 -0
  7. data/lib/brainclusterfuck/interpreter.rb +84 -0
  8. data/lib/brainclusterfuck/interpreter_error.rb +4 -0
  9. data/lib/brainclusterfuck/lexer.rb +2 -2
  10. data/lib/brainclusterfuck/loop_error.rb +6 -0
  11. data/lib/brainclusterfuck/memory.rb +33 -0
  12. data/lib/brainclusterfuck/memory_error.rb +6 -0
  13. data/lib/brainclusterfuck/opcodes.rb +5 -0
  14. data/lib/brainclusterfuck/opcodes/base.rb +19 -0
  15. data/lib/brainclusterfuck/opcodes/loop_base.rb +27 -0
  16. data/lib/brainclusterfuck/opcodes/loop_end.rb +10 -0
  17. data/lib/brainclusterfuck/opcodes/loop_end_placeholder.rb +6 -0
  18. data/lib/brainclusterfuck/opcodes/loop_placeholder.rb +20 -0
  19. data/lib/brainclusterfuck/opcodes/loop_start.rb +10 -0
  20. data/lib/brainclusterfuck/opcodes/loop_start_placeholder.rb +6 -0
  21. data/lib/brainclusterfuck/opcodes/modify_pointer.rb +9 -0
  22. data/lib/brainclusterfuck/opcodes/modify_value.rb +6 -0
  23. data/lib/brainclusterfuck/opcodes/modifying_base.rb +27 -0
  24. data/lib/brainclusterfuck/opcodes/print.rb +13 -0
  25. data/lib/brainclusterfuck/terminal.rb +4 -0
  26. data/lib/brainclusterfuck/version.rb +1 -1
  27. data/spec/compiler_spec.rb +82 -16
  28. data/spec/integration/basic_loops_spec.rb +55 -0
  29. data/spec/integration/basic_print_spec.rb +65 -0
  30. data/spec/integration/boundary_conditions_spec.rb +81 -0
  31. data/spec/interpreter_spec.rb +154 -0
  32. data/spec/lexer_spec.rb +2 -2
  33. data/spec/memory_spec.rb +33 -0
  34. data/spec/opcodes/base_spec.rb +26 -0
  35. data/spec/opcodes/loop_end_placeholder_spec.rb +5 -0
  36. data/spec/opcodes/loop_end_spec.rb +7 -0
  37. data/spec/opcodes/loop_start_placeholder_spec.rb +5 -0
  38. data/spec/opcodes/loop_start_spec.rb +7 -0
  39. data/spec/opcodes/modify_pointer_spec.rb +6 -0
  40. data/spec/opcodes/modify_value_spec.rb +6 -0
  41. data/spec/{opcode → opcodes}/print_spec.rb +3 -3
  42. data/spec/spec_helper.rb +2 -0
  43. data/spec/support/loop_opcode_shared_behavior.rb +22 -0
  44. data/spec/support/loop_placeholder_shared_behavior.rb +12 -0
  45. data/spec/{opcode/modify_value_spec.rb → support/modifying_opcode_shared_behavior.rb} +4 -7
  46. data/spec/terminal_spec.rb +11 -0
  47. metadata +60 -20
  48. data/lib/brainclusterfuck/cells.rb +0 -9
  49. data/lib/brainclusterfuck/opcode.rb +0 -11
  50. data/lib/brainclusterfuck/opcode/modify_pointer.rb +0 -27
  51. data/lib/brainclusterfuck/opcode/modify_value.rb +0 -27
  52. data/lib/brainclusterfuck/opcode/print.rb +0 -13
  53. data/lib/brainclusterfuck/vm.rb +0 -11
  54. data/spec/cells_spec.rb +0 -8
  55. data/spec/opcode/modify_pointer_spec.rb +0 -37
  56. data/spec/opcode_spec.rb +0 -10
  57. data/spec/vm_spec.rb +0 -22
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ notifications:
6
+ email: false
data/Gemfile CHANGED
@@ -3,6 +3,6 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in brainclusterfuck.gemspec
4
4
  gemspec
5
5
 
6
- group [:test, :development] do
6
+ group :test, :development do
7
7
  gem 'should_not', '~> 1.0'
8
8
  end
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
@@ -0,0 +1,10 @@
1
+ module Brainclusterfuck
2
+ class CompileError < RuntimeError
3
+ end
4
+
5
+ class UnterminatedLoopError < CompileError
6
+ end
7
+
8
+ class PrematurelyTerminatedLoopError < CompileError
9
+ end
10
+ end
@@ -1,66 +1,88 @@
1
- require 'brainclusterfuck/opcode/modify_value'
2
- require 'brainclusterfuck/opcode/modify_pointer'
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
- compressed = [@bytecode.shift]
21
+ modifying_bytecode_length do
22
+ compressed = [@bytecode.shift]
18
23
 
19
- @bytecode.each do |opcode|
20
- last = compressed[-1]
21
- if opcode.can_squeeze_with?(last)
22
- compressed[-1] = opcode.squeeze_with(last)
23
- else
24
- compressed << opcode
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
- @bytecode = compressed
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
- Opcode::ModifyValue.new(1, 1)
70
+ Opcodes::ModifyValue.new(1, 1)
36
71
  when :v_decr
37
- Opcode::ModifyValue.new(-1, 1)
72
+ Opcodes::ModifyValue.new(-1, 1)
38
73
  when :p_incr
39
- Opcode::ModifyPointer.new(1, 1)
74
+ Opcodes::ModifyPointer.new(1, 1)
40
75
  when :p_decr
41
- Opcode::ModifyPointer.new(-1, 1)
76
+ Opcodes::ModifyPointer.new(-1, 1)
42
77
  when :print
43
- Opcode::Print.new
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,5 @@
1
+ module Brainclusterfuck
2
+ class Error < RuntimeError
3
+
4
+ end
5
+ 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,4 @@
1
+ module Brainclusterfuck
2
+ class InterpreterError < RuntimeError
3
+ end
4
+ end
@@ -26,9 +26,9 @@ module Brainclusterfuck
26
26
  when '.'
27
27
  :print
28
28
  when '['
29
- :loop
29
+ :loop_start
30
30
  when ']'
31
- :end_loop
31
+ :loop_end
32
32
  else
33
33
  raise 'wtf?'
34
34
  end
@@ -0,0 +1,6 @@
1
+ require 'brainclusterfuck/error'
2
+
3
+ module Brainclusterfuck
4
+ class LoopError < Error
5
+ end
6
+ 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,6 @@
1
+ require 'brainclusterfuck/error'
2
+
3
+ module Brainclusterfuck
4
+ class MemoryError < Error
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ require 'brainclusterfuck/opcodes/modify_value'
2
+ require 'brainclusterfuck/opcodes/modify_pointer'
3
+ require 'brainclusterfuck/opcodes/print'
4
+ require 'brainclusterfuck/opcodes/loop_start'
5
+ require 'brainclusterfuck/opcodes/loop_end'
@@ -0,0 +1,19 @@
1
+ module Brainclusterfuck
2
+ module Opcodes
3
+ class Base
4
+ attr_reader :cycles
5
+
6
+ def can_squeeze_with?(other)
7
+ false
8
+ end
9
+
10
+ def unresolve_loop
11
+ self
12
+ end
13
+
14
+ def resolve_loop
15
+ self
16
+ end
17
+ end
18
+ end
19
+ 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,10 @@
1
+ require 'brainclusterfuck/opcodes/loop_base'
2
+ require 'brainclusterfuck/opcodes/loop_end_placeholder'
3
+
4
+ module Brainclusterfuck::Opcodes
5
+ class LoopEnd < LoopBase
6
+ def placeholder_class
7
+ LoopEndPlaceholder
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ require 'brainclusterfuck/opcodes/loop_placeholder'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class LoopEndPlaceholder < LoopPlaceholder
5
+ end
6
+ 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
@@ -0,0 +1,10 @@
1
+ require 'brainclusterfuck/opcodes/loop_base'
2
+ require 'brainclusterfuck/opcodes/loop_start_placeholder'
3
+
4
+ module Brainclusterfuck::Opcodes
5
+ class LoopStart < LoopBase
6
+ def placeholder_class
7
+ LoopStartPlaceholder
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ require 'brainclusterfuck/opcodes/loop_placeholder'
2
+
3
+ module Brainclusterfuck::Opcodes
4
+ class LoopStartPlaceholder < LoopPlaceholder
5
+ end
6
+ end