sábado, 1 de agosto de 2009

Como implementar um interpretador de brainfuck - parte 5

Esta é a etapa final, e o programa já interpreta seis comandos, faltando apenas mais dois.

Adicione uma variável chamada colchetes, a qual guardará quantas estruturas de repetição (a estrutura “[” é o início e “]” é o fim, em brainfuck) há no arquivo. Em seguida, adicione uma estrutura de repetição antes da declaração da pilha, de preferência uma do tipo for, com a condição de repetir até o índice apontar para o último elemento da variável fonte e, dentro dela coloque uma condição, a qual se o elemento examinado for igual “[”, deverá aumentar o valor da variável colchetes. Isso será feito para contar as estruturas de repetição, para depois criar uma variável do tamanho exato, para guardar as localizações das estruturas de repetição. Também, a informação será utilizada para determinar se não há erros em relação as estruturas (o programa não pode fechar uma estrutura que não foi aberta, nem o programa pode terminar com uma estrutura aberta).

O código a seguir possui comentários que, acredito que sejam auto-explicativos:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
   int c;
   FILE *arquivo; // Deve-se usar o asterisco aqui, por ser ponteiro
   if (argc >= 2) // Se tiver 2 ou mais argumentos
   {
      arquivo = fopen(argv[1], "rb"); // Abre o arquivo especificado
      if (arquivo == NULL) // Se não conseguir abrir o arquivo
      {
         puts("ERRO: Não foi possível abrir o arquivo!");
         do { c = getchar(); } while ((c != '\n') && (c != EOF));
         return 1; // Encerra o programa
      }
   }
   else
   {
      return 0; // Encerra o programa
   }

   fseek(arquivo, 0, SEEK_END);
   int tamanho = ftell(arquivo); // Offset do fim do arquivo, que equivale ao tamanho do arquivo
   rewind(arquivo); // Rebobina o arquivo

   // Aloca memória, com um byte adicional para o nulo (usado em strings em C)
   char *fonte = (char *) malloc(tamanho + 1);
   if (fonte == NULL) // Verifica se consegui alocar memoria
   {
      puts("ERRO: Não há memória o suficiente!");
      do { c = getchar(); } while ((c != '\n') && (c != EOF));
      return 1;
   }

   memset(fonte, 0, tamanho * sizeof(char)); // Preencherá a variável fonte com zeros (para "limpar")
   size_t size = fread(fonte, 1, tamanho, arquivo);
   fonte[tamanho] = 0; // Põe o sinal de fim (nulo)
   if (size != tamanho * sizeof(char))
   {
      puts("ERRO: Não foi possível ler o arquivo!");
      do { c = getchar(); } while ((c != '\n') && (c != EOF));
      return 1;
   }

   int colchetes = 0, i;
   for (i = 0; i < tamanho; i++)
   {
      if (fonte[i] == '[')
      {
         colchetes++;
      }
      else if (fonte[i] == ']')
      {
         if (colchetes == 0) // Se nenhuma estrutura '[' está aberta
         {
            puts("ERRO: A estrutura de repetição ']' não foi aberta!");
            do { c = getchar(); } while ((c != '\n') && (c != EOF));
            return 1;
         }
         else
         {
            colchetes--;
         }
      }
   }
   if (colchetes > 0) // Se alguma estrutura '[' não foi fechada
   {
      puts("ERRO: A estrutura de repetição '[' não foi fechada!");
      do { c = getchar(); } while ((c != '\n') && (c != EOF));
      return 1;
   }

   unsigned char pilha[30000]; // Equivalente a aproximadamente 30 KB
   memset(pilha, 0, 30000 * sizeof(unsigned char));

   int backup = 0, ptr = 0, pos[tamanho];
   colchetes = 0;
   for (i = 0; i < tamanho; i++)
   {
      if (fonte[i] == '[')
      {
         backup = i;

         while (i < tamanho)
         {
            if (fonte[i] == '[')
            {
               colchetes++;
            }
            else if (fonte[i] == ']')
            {
               colchetes--;
            }
            if (colchetes == 0)
            {
               break;
            }
            i++;
         }
         pos[i] = backup;
         pos[backup] = i;
         i = backup;
      }
   }

   for (i = 0; i < tamanho; i++)
   {
      switch (fonte[i])
      {
         case '+':
            pilha[ptr]++;
            break;

         case '.':
            putchar(pilha[ptr]);
            break;

         case '-':
            pilha[ptr]--;
            break;

         case ',':
            backup = getchar();

            if (backup != -1)
            {
               pilha[ptr] = backup;
            }
            break;

         case '>':
            if (ptr == 30000 - 1) // Célula 30000
            {
               ptr = 0; // Célula 1
            }
            else
            {
               ptr++;
            }
            break;

         case '<':
            if (ptr == 0)
            {
               ptr = 30000 - 1; // Célula 30000
            }
            else
            {
               ptr--;
            }
            break;

         case '[':
            if (pilha[ptr] == 0)
            {
               i = pos[i];
            }
            break;

         case ']':
            i = pos[i] - 1; // Voltar ao início da repetição
            break;
      }
   }

   free(fonte);
   return 0;
}

Conclusão

O interpretador de brainfuck está completo e funcional e, deverá ser capaz de interpretar qualquer programa em brainfuck. Ele é simples e você pode melhorá-lo, adicionando novas funcionalidades. Experimente interpretar o código em brainfuck abaixo:

Programa Olá Mundo

++++++++++[>++++++++>+++++++++++>----
------>+++>++++++++>++++++++++++>++++
+++++++>++++++++++>+++++++++++>+++<<<
<<<<<<<-]>-.>--.>++++.>++++++++++++++
.------------.>---.>---.>.>.>+.>+++.,

Em um tutorial futuro, irei instruir o leitor a desenvolver um interpretador de uma linguagem mais complexa, de comandos de mais de um byte de comprimento, com suporte a compilação e interpretação de bytecode.

« parte anterior página principal »

Postagens relacionadas

0 comentários:

Postar um comentário