brainclusterfuck 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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