Created
July 14, 2013 22:16
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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