Suporte a loops aninhados em meu interpretador BrainFuck
Continuando o projeto de fazer um interpretador brainfuck em GO
O Problema
É algo simples, "eu já tinha pensado nisso", mas não me animava a implementar. Se temos loops aninhados, precisamos de um tratamento especial. Eis um exemplo:
+++++[<<,-----[>]>>[<<+++>>[-]]]>.
E finalmente tomei coragem para fazer essa implementação. Na tag 0.0.4, eu tentei resolver isso implementado um stack, mas claro que não deu certo. Como eu sei quando um ]
é relativo ao [
em que estou? Esse era o problema que eu estava ignorando.
Aqui um diff completo em relação ao último texto.
Nem só isso
Uma importante alteração foi o controle sobre o valor do ponteiro de memória. Eu não tinha feito nada para controlar underflow ou overflow, até que me deparei com códigos que funcionam como se não houvesse amanhã!
if l.pos < 0 {
l.pos = len(l.memory) - 1
} else if l.pos >= len(l.memory) {
l.pos = 0
}
Não importa o que se faça, sempre se estará apontando para uma região válida da memória.
Também resolvi imprimir o caractere correspondente ao número armazenado, para pode rodar os programas BF que tem por aí e comparar a saída:
func main() {
@@ -84,7 +107,7 @@ func main() {
'<': func(l *Language) { l.pos-- },
'+': func(l *Language) { l.memory[l.pos]++ },
'-': func(l *Language) { l.memory[l.pos]-- },
- '.': func(l *Language) { fmt.Println(l.memory[l.pos]) },
+ '.': func(l *Language) { fmt.Print(string(l.memory[l.pos])) },
'[': func(l *Language) { open_bracket(l) },
']': func(l *Language) { close_bracket(l) },
}
O interessante aqui é a função string
, que eu estava procurando sem saber que existia :).
Finalmente
Foram implementados os cuidados para quando se depara com um [
:
func open_bracket(l *Language) {
if l.memory[l.pos] != 0 {
return
}
for depth := 1; depth > 0; {
l.instruction++
instruction := l.source[l.instruction]
if instruction == '[' {
depth++
} else if instruction == ']' {
depth--
}
}
}
Se o valor na posição corrente de memória for zero, nem entramos no loop, procurando seu final.
E quando se depara com um ]
, eis os cuidados:
func close_bracket(l *Language) {
for depth := 1; depth > 0; {
l.instruction--
instruction := l.source[l.instruction]
if instruction == '[' {
depth--
} else if instruction == ']' {
depth++
}
}
l.instruction--
}
Simplesmente procuramos o [
correspondente, onde a verificação do loop está.
As ideias para a busca dos limitadores de loop foram copiados dessas implementações:
Torture Tests para seu interpretador BF podem ser encontrados nesse link.
O Futuro
Está quase no ponto em que essa série deixará de ser sobre GO e o mais importante será a linguagem BF. Algumas coisas que pretendo resolver logo:
- A leitura do código fonte ser por arquivo, e não pelo stdin.
- Implementar a entrada de dados com o comando
,
. - Opção, em linha de comando, para tamanho da memória e tempo máximo de execução, ou quantidade máxima de instruções executadas (minimizar os efeitos de loops infinitos).
- Um jeito visual de apresentar a execução dos programas, com apresentação dos valores em memória.
- Testes, muitos testes.