Skip to content

Instantly share code, notes, and snippets.

@gabteles
Created July 14, 2013 22:16
Show Gist options
  • Save gabteles/5996335 to your computer and use it in GitHub Desktop.
Save gabteles/5996335 to your computer and use it in GitHub Desktop.
Segunda versão do meu interpretador/tradutor de Brainfuck em/para Ruby. Ainda está meio bruto, mas funciona relativamente bem.
# Interpretador de Brainfuck V2
# Autor: Gabriel Teles <[email protected]>
require 'io/console' # Necessário para usar STDIN.getch
module Brainfuck2
# Classe de controle
class ProgramData
attr_reader :bufferSize, :buffer, :stack, :commands, :position
def initialize(bufferSize, commands)
@bufferSize = bufferSize
@buffer = Array.new(bufferSize, 0)
@stack = []
@commands = commands
@position = 0
@commandPos = -1
end
def finished
@commandPos >= @commands.size
end
def nextInstruction
@commands[@commandPos += 1]
end
def increasePoint(combo)
@buffer[@position] = (@buffer[@position] + combo) % 256
end
def decreasePoint(combo)
@buffer[@position] = (@buffer[@position] - combo) % 256
end
def moveRight(combo)
@position = (@position + combo) % @bufferSize
end
def moveLeft(combo)
@position = (@position - combo) % @bufferSize
end
def printFromBuffer(combo)
combo.times { print(@buffer[@position].chr) }
end
def readToBuffer(combo)
combo.times { @buffer[@position] = STDIN.getch.ord }
end
def openCondition
@stack.push(@commandPos)
end
def closeCondition
if (@buffer[@position] == 0)
@stack.pop
else
@commandPos = @stack.last
end
end
def debug(combo)
combo.times {
print "\n"
print "Memory Size: #{@bufferSize}\n"
print "Memory Position: #{@position}\n"
print "Stack Level: #{@stack.size}\n"
print "\n"
print "MEMORY DECIMAL DUMP"
@buffer.each_with_index{|ch, index|
print "\n" if (index % 20) == 0
print ch.to_s.rjust(3, '0')
print " "
}
print "\n"
}
end
end
# Comandos
Commands = {
# CHAR => [MEAN, COMBO]
'+' => [:Plus, true],
'-' => [:Minus, true],
'>' => [:MoveRight, true],
'<' => [:MoveLeft, true],
'.' => [:Print, true],
',' => [:Read, true],
'[' => [:ConditionOpen, false],
']' => [:ConditionClose, false],
'@' => [:Debug, true],
}
ValidInput = Commands.keys
module_function
# Roda interpretador a partir de string
def runString(string, bufferSize = 256)
# Informação de runtime
programData = ProgramData.new(bufferSize, Brainfuck2.parse(string))
# Roda programa
Brainfuck2.run(programData)
end
# Roda interpretador a partir de classe de controle
def run(programData)
until programData.finished
command, combo = programData.nextInstruction
case command
when :Plus
programData.increasePoint(combo)
when :Minus
programData.decreasePoint(combo)
when :MoveRight
programData.moveRight(combo)
when :MoveLeft
programData.moveLeft(combo)
when :Print
programData.printFromBuffer(combo)
when :Read
programData.readToBuffer(combo)
when :ConditionOpen
programData.openCondition
when :ConditionClose
programData.closeCondition
when :Debug
programData.debug(combo)
end
end
end
# Traduz o código para Ruby
def translateToRuby(string, bufferSize = 256)
# Comandos
commands = Brainfuck2.parse(string)
# Ajuda para tabulação
stackLevel = 0
lineStart = "\t" * stackLevel
# Necessidade de incluir 'io/console'
needIoConsole = false
# Necessidade de incluir método de debug
needDebugMethod = false
# Traduz
baseResult = ""
baseResult << "buffer = Array.new(#{bufferSize.to_i}, 0)\n"
baseResult << "position = 0\n\n"
commands.each{|(command, combo)|
baseResult << lineStart
case command
when :Plus
baseResult << "buffer[position] = (buffer[position] + #{combo}) % 256\n"
when :Minus
baseResult << "buffer[position] = (buffer[position] - #{combo}) % 256\n"
when :MoveRight
baseResult << "position = (position + #{combo}) % buffer.size\n"
when :MoveLeft
baseResult << "position = (position - #{combo}) % buffer.size\n"
when :Print
if (combo == 1)
baseResult << "print buffer[position].chr\n"
else
baseResult << "#{combo}.times{ print buffer[position].chr }\n"
end
when :Read
needIoConsole = true
if (combo == 1)
baseResult << "buffer[position] = STDIN.getch.ord\n"
else
baseResult << "#{combo}.times{ buffer[position] = STDIN.getch.ord }\n"
end
when :ConditionOpen
baseResult << "until buffer[position] == 0\n"
stackLevel += 1
lineStart = "\t" * stackLevel
when :ConditionClose
baseResult[-1] = ''
baseResult << "end\n"
stackLevel -= 1
lineStart = "\t" * stackLevel
when :Debug
needDebugMethod = true
if (combo == 1)
baseResult << "debug(buffer, position, #{stackLevel})\n"
else
baseResult << "#{combo}.times{ debug(buffer, position, stack) }\n"
end
end
}
# Resultado final
result = ""
result << "# Brainfuck translated program\n"
result << "# Translation by: Gab Brainfuck Interpreter & Translator\n"
result << "# \n"
result << "# Generated at: "
result << Time.now.strftime("%Y/%m/%d (%H:%M:%S)")
result << "\n\n"
if (needIoConsole)
result << "require 'io/console'\n\n"
end
if (needDebugMethod)
endl = "\n\t"
result << "def debug(buffer, position, stack)" << endl
result << 'print "\n"' << endl
result << 'print "Memory Size: #{buffer.size}\n"' << endl
result << 'print "Memory Position: #{position}\n"' << endl
result << 'print "Stack Level: #{stack}\n"' << endl
result << 'print "\n"' << endl
result << 'print "MEMORY DECIMAL DUMP"' << endl
result << 'buffer.each_with_index{|ch, index|' << endl
result << "\t" + 'print "\n" if (index % 20) == 0' << endl
result << "\t" + 'print ch.to_s.rjust(3, "0")' << endl
result << "\t" + 'print " "' << endl
result << '}' << endl
result << 'print "\n"' << endl
result[-1] = "end\n\n"
end
result << baseResult
# Retorna
return result
end
# Trata comandos recebidos de string e traduz para comandos de controle, agrupando
# os comandos que podem ser agrupados.
def parse(string)
# Comandos
commands = []
# Registrador de stack
stackLevel = 0
# Auxiliares para evitar repetições
lastCommand = nil
comboSize = 1
# Chars ignorados
ignore = [" ", "\t", "\n"]
# Verifica linha por linha
string.each_line.with_index{|line, index|
# Verifica caractere por caractere
line.each_char{|char|
# Ignora espaços em branco e novas linhas
next if ignore.include?(char)
# Ignora tudo a partir do caractere = (comentário)
break if char == '='
# Verifica validade do comando
if Brainfuck2::ValidInput.include?(char)
command, combo = Brainfuck2::Commands[char]
if command == :ConditionClose
if stackLevel == 0
raise("Error on line #{index}: Unexpected condition close")
end
stackLevel -= 1
elsif command == :ConditionOpen
stackLevel += 1
end
if (command == lastCommand)
if (combo)
comboSize += 1
else
commands << [command, 1]
end
elsif lastCommand
commands << [lastCommand, comboSize]
comboSize = 1
end
lastCommand = command
else
raise("Error on line #{index}: Unexpected command '#{char}'")
end
}
}
# Finaliza comandos inserindo o último possível combo
commands << [lastCommand, comboSize]
# Checa se todas as condições foram fechadas
unless stackLevel.zero?
raise("#{stackLevel} unclosed condition#{stackLevel == 1 ? '' : 's'} at the end of file!")
end
# Retorna comandos tratados
return commands
end
end
# TEST CASE
# 1 - Imprime alfabeto minúsculo
Brainfuck2.runString("++++++++++[>++++++++++>+++<<-]>--->----[<.+>-]")
# 2 - Multiplica x e y (x = x * y)
Brainfuck2.runString("
+++++ = x:5
>+++ = y:3
>[-]>[-]<< = t0:t1:0
<[ = itera x vezes
>[>+>+<<-] = transfere y para t0 & t1
>[<+>-] = transfere t0 para y
<< = volta para x
-] = fim do loop
>>>[<<<+>>>-] = copia t1 para x
@ = debug para verificar
")
# 3 - Tradução
rb = Brainfuck2.translateToRuby("+++++++++[>++++++++++<-]>+++++++<+++++[>>+++++<<-]>>+[<.+>-]")
File.open('brainfuckTestCase.rb', 'w'){|f| f << rb}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment