/lib/brain_fuck.rb

https://github.com/seandmccarthy/rbfk · Ruby · 237 lines · 204 code · 32 blank · 1 comment · 21 complexity · d452130bfdb14127dea97799a1a75bb0 MD5 · raw file

  1. #!/usr/bin/env ruby -w
  2. class BrainFuck
  3. DEFAULT_MEMORY_SIZE = 30_000
  4. DEFAULT_EXEC_LIMIT = 1_000_000 # Stop badly constructed scripts going forever
  5. OPS = {
  6. '+' => :increment,
  7. '-' => :decrement,
  8. '>' => :move_pointer_left,
  9. '<' => :move_pointer_right,
  10. ',' => :read_in,
  11. '.' => :print_out,
  12. '[' => :loop_start,
  13. ']' => :loop_end
  14. }
  15. DIALECTS = {
  16. :ook => {
  17. 'Ook. Ook.' => '+',
  18. 'Ook! Ook!' => '-',
  19. 'Ook. Ook?' => '>',
  20. 'Ook? Ook.' => '<',
  21. 'Ook! Ook?' => '[',
  22. 'Ook? Ook!' => ']',
  23. 'Ook! Ook.' => '.',
  24. 'Ook. Ook!' => ','
  25. },
  26. :spoon => {
  27. 1 => '+',
  28. 0 => {
  29. 0 => {
  30. 0 => '-',
  31. 1 => {
  32. 0 => {
  33. 0 => '[',
  34. 1 => {
  35. 0 => '.',
  36. 1 => {
  37. 0 => ','
  38. }
  39. }
  40. },
  41. 1 => ']'
  42. }
  43. },
  44. 1 => {
  45. 0 => '>',
  46. 1 => '<'
  47. }
  48. }
  49. }
  50. }
  51. attr_accessor :memory,
  52. :data_pointer,
  53. :instruction_pointer,
  54. :pointer_stack
  55. def initialize(program_input_stream, options={})
  56. @debug = options[:debug] || false
  57. @exec_limit = options[:execution_limit] || DEFAULT_EXEC_LIMIT
  58. @execution_count = 0
  59. @instruction_pointer = -1
  60. @memory_size = options[:memory_size] || DEFAULT_MEMORY_SIZE
  61. @memory = Array.new(@memory_size){ 0 }
  62. @data_pointer = 0
  63. @pointer_stack = []
  64. @input_stream = options[:input_stream] || STDIN
  65. @output_stream = options[:output_stream] || STDOUT
  66. @program = get_program(program_input_stream)
  67. @program_end = @program.size
  68. end
  69. def execute(op)
  70. return unless OPS.has_key?(op)
  71. puts op if @debug
  72. send(OPS[op])
  73. @execution_count += 1
  74. end
  75. def increment
  76. @memory[@data_pointer] += 1
  77. end
  78. def decrement
  79. @memory[@data_pointer] -= 1
  80. end
  81. def read_in
  82. @memory[@data_pointer] = @input_stream.getc.ord
  83. end
  84. def print_out
  85. @output_stream.putc @memory[@data_pointer]
  86. end
  87. def move_pointer_left
  88. @data_pointer += 1
  89. if @data_pointer == @memory_size
  90. @data_pointer = 0
  91. end
  92. end
  93. def move_pointer_right
  94. if @data_pointer > 0
  95. @data_pointer -= 1
  96. else
  97. @data_pointer = @memory_size - 1
  98. end
  99. end
  100. def loop_start
  101. if @memory[@data_pointer] > 0
  102. @pointer_stack.push(@instruction_pointer-1)
  103. else
  104. @instruction_pointer = matching_brace_position(@instruction_pointer)
  105. end
  106. end
  107. def loop_end
  108. if @pointer_stack.empty?
  109. raise "Bracket mismatch"
  110. end
  111. if @memory[@data_pointer] == 0
  112. @pointer_stack.pop
  113. else
  114. @instruction_pointer = @pointer_stack.pop
  115. end
  116. end
  117. def self.ook_to_bf(ook)
  118. bf = []
  119. ook.scan(/(Ook[\.\?\!])\s*(Ook[\.\?\!])/) do |m|
  120. command = "#{m[0]} #{m[1]}"
  121. unless DIALECTS[:ook].include?(command)
  122. raise "Got confused, thought it was Ook!"
  123. end
  124. bf << DIALECTS[:ook][command]
  125. end
  126. bf.join
  127. end
  128. def self.bf_to_ook(bf)
  129. ook = []
  130. ook_bf = DIALECTS[:ook].invert
  131. bf.each_char do |op|
  132. next unless op.match(/[\>\<\+\-\.,\[\]]/)
  133. ook << ook_bf[op]
  134. end
  135. ook.join(' ')
  136. end
  137. def self.spoon_to_bf(spoon)
  138. src = spoon.gsub(/[^01]/, '')
  139. bf = []
  140. current = DIALECTS[:spoon]
  141. spoon.each_char do |i|
  142. current = current[i.to_i]
  143. unless current.is_a?(Hash)
  144. bf << current
  145. current = DIALECTS[:spoon]
  146. end
  147. end
  148. bf.join
  149. end
  150. def run
  151. while not ended? do
  152. dump if @debug
  153. op = next_instruction
  154. puts "op = #{op}" if @debug
  155. execute(op)
  156. dump if @debug
  157. end
  158. end
  159. def next_instruction
  160. @instruction_pointer += 1
  161. @program[@instruction_pointer]
  162. end
  163. def ended?
  164. (@instruction_pointer >= @program_end or
  165. @execution_count >= @exec_limit)
  166. end
  167. def dump
  168. puts
  169. puts "instruction_pointer = #{@instruction_pointer}"
  170. puts "data_pointer = #{@data_pointer}"
  171. puts "memory@data_pointer = #{@memory[@data_pointer]}"
  172. puts "pointer_stack = #{@pointer_stack.inspect}"
  173. puts
  174. end
  175. private
  176. def get_program(program_input_stream)
  177. src = program_input_stream.read
  178. if ook?(src) # Probably Ook
  179. BrainFuck.ook_to_bf(src) #.gsub(/[\r\n]/, ' '))
  180. elsif spoon?(src) # Probably Spoon
  181. BrainFuck.spoon_to_bf(src)
  182. else
  183. clean_source(src)
  184. end
  185. end
  186. def clean_source(src)
  187. src.gsub(/[^\>\<\+\-\.,\[\]]/, '')
  188. end
  189. def ook?(src)
  190. src.match(/(Ook[\.\?\!]\s*){2}/)
  191. end
  192. def spoon?(src)
  193. src.match(/^[01]+\s*$/m)
  194. end
  195. def matching_brace_position(pointer)
  196. begin
  197. pointer += 1
  198. fail "Bracket mismatch" if pointer >= @program_end
  199. if @program[pointer] == '['
  200. pointer = matching_brace_position(pointer) + 1
  201. end
  202. end until @program[pointer] == ']'
  203. pointer
  204. end
  205. end