Compilador Befunge em Python

Na preparação para o curso "Fundamentos de compiladores" para alunos do 4º ano, estudei várias linguagens de programação esotérica. Aqui está um bom artigo sobre este tópico . A linguagem Befunge (Chris Press, 1993) me pareceu a mais interessante no artigo, notando especialmente três de suas características:

  1. O campo do programa é um toro bidimensional, ou seja, fisicamente, essa é uma matriz retangular de comandos de símbolo fechados ao longo do limite superior (inferior) e ao longo da coluna esquerda (direita). O ponteiro de comando se move pelo campo (cada comando é um determinado caractere com coordenadas x, y), executa o comando e segue em frente. O movimento pode estar em todas as 4 direções (por padrão, diretamente do ponto 0,0) e, quando você ultrapassa o "campo", o ponteiro aparece no lado oposto.
  2. Existem dois comandos (p, g) no idioma que alteram o próprio campo, ou seja, o programa é "auto-reescrito" durante a execução. O código do programa no início pode não ser igual ao código no final. O programa "123pgpg ## @" foi iniciado e o programa "ABC @ 1 @ 2 @ 3.14" (não é um exemplo correto) terminou de funcionar.
  3. Chris Pressy observou que ele queria criar uma linguagem o mais complexa possível para compilar. De fato, é verdade, ao criar um compilador que torna extremamente difícil o arquivo exe para o programa, encontrei informações de que alguém poderia fazê-lo em C ... É melhor criar um tradutor da linguagem para o código Python, que ainda chamo de compilador para simplificar.


O campo do programa de 1993 consiste em 25 linhas de 80 caracteres cada. Existem 36 equipes no idioma, cada uma delas com um caractere de tabela ASCII. Para obter mais informações, consulte Wikipedia , darei uma breve descrição a partir daí:

Mover comandos (9):

> Mover para a direita
<Mover para a esquerda
^ Mover para cima
v Mover para baixo
_ Mover para a direita se a parte superior da pilha for 0, caso contrário, à esquerda.
| Mova para baixo se estiver no topo da pilha 0, caso contrário, para cima.
? Mover em uma direção aleatória
# Pule a próxima célula (“trampolim”)
@ Fim do programa

Comandos de manipulação de pilha (3)

:: Coloque uma cópia do vértice na pilha
\ Trocar vértice e vértice
$ Delete vertex

Comandos de modificação do código do programa (2):
p “PUT”: as coordenadas da célula e o código ASCII do caractere colocado nessas coordenadas
são extraídos da pilha g “GET”: as coordenadas da célula são extraídas da pilha; O código ASCII de um símbolo nestas coordenadas é empurrado na pilha.

Comandos Constantes (2):

0-9 Coloque um número no
Início / Fim do modo de símbolo de pilha , em que códigos ASCII de todos os personagens do programa atuais são

empurrados para a pilha . As operações aritméticas (5):

+ Adição de um vértice e de um vértice
- Subtração de um vértice e de um vértice
* Multiplicação de um vértice e uma
divisão de vértices / inteiro
% Restante de divisão

Comandos para a pilha e operações lógicas (2)

:! Negação: zero no vértice é substituído por 1, valor diferente de zero é substituído por 0
`Comparação“ maior que ”: se o vértice for maior que o vértice, 1 é colocado na pilha, caso contrário, 0

comandos de E / S (4):

& Solicite ao usuário um número e coloque-o na pilha
~ Peça ao usuário um caractere e coloque na pilha seu código ASCII
. Imprima a parte superior da pilha como um número inteiro
, imprima o caractere correspondente ao código ASCII na parte superior da pilha

Decidi escrever um compilador Befunge (intérprete) em Python, de acordo com as regras de 1993, com algumas restrições: 1) o campo não tem caracteres de 25x80, mas o mínimo em largura e altura do bloco de texto, 2) o campo não está em loop no toro, ou seja, ir além das fronteiras com pular para o lado oposto não é processado. Isso não é preguiça (no entanto, com quem estou brincando?). Para pequenos exemplos, tudo funciona bem, e terminar o campo com um toro real é bastante simples, haveria um desejo.

O código saiu desnecessariamente em alguns lugares "na testa", mas isso se deve ao fato de ser para os alunos e sua tarefa ser a mais clara possível, e não encurtada para algumas linhas em chinês.

Parte 1


O código é fornecido do começo ao fim com uma exceção (que será especificada), pode ser copiado para um arquivo e executado. O texto completo está disponível no link rentry.co/ivansedov-befunge . É muito cedo para dar o melhor do GitHub. A propósito, existem cerca de 20 implementações da linguagem Befunge, mas o código está em C (não na minha linguagem) ou Python, mas é tão complicado que não me atrevi a mergulhar. No entanto, você pode obter exemplos de programas para testes, por exemplo, aqui github.com/causal-agent/befungee

from sys import *
import time
import random

Importe as bibliotecas:

  1. A biblioteca sys é necessária para obter o nome do arquivo do programa. Meu compilador foi chamado bbb.py, um exemplo de teste no mesmo diretório 1.bf, versão Python 3.7 e, portanto, a chamada do programa no console ficou assim: python3 bbb.py 1.bf
  2. time , , 0,5-1,0 .
  3. random «?» ( ), . -, Befunge - , « » (1 , 2 , 3 , 4 ). « » .

class Pointer:
    def __init__(self, x=0, y=0, vector=2, value=None):
        self.x = x
        self.y = y
        self.vector = vector
        self.value = value
        self.stack = []
        self.stack_sf = 0

    def __str__(self):
        return 'Point ({},{}) vektor:{} value:{} stack_sf:{} stack:{}'.format(self.x, self.y, self.vector, self.value, self.stack_sf, self.stack)

    def step(self):
        if self.vector == 1:
            self.x -= 1
        elif self.vector == 2:
            self.y += 1
        elif self.vector == 3:
            self.x += 1
        elif self.vector == 4:
            self.y -= 1

    def action(self):
	#   ,   

A classe principal usada no programa: Ponteiro (ponteiro), possui 6 propriedades e 2 métodos. Propriedades: 1) coordenada x (inicialmente = 0), 2) coordenada y (inicialmente = 0), 3) vetor (direção do movimento, inicialmente = 2 à direita), 4) valor (valor do campo, localizado na coordenada x, y, inicialmente = Nenhum), esse é, de fato, o comando a ser executado, 5) pilha (pilha do programa, inicialmente = []) e 6) pilha_st (sinalizador para inserir linhas, inicialmente = 0).

A classe possui dois métodos: 1) etapa () a etapa do ponteiro, sem nenhuma verificação e teste, altera as coordenadas x, y, dependendo da direção do vetor e 2) action () é o coração do compilador, executando o comando do programa atual. Vou dar o código action () na segunda parte, para que ele não se distraia da lógica.

# ===============================
# =                      =
# ===============================

def open_file(name):
    data = open(name, 'r').read()
    return data

def field_print():
    for row in A:
        for elem in row:
            print(elem, end='')
        print()  

Duas funções auxiliares: 1) open_file (nome) abre o arquivo pelo nome enviado, lê e retorna o conteúdo e 2) field_print () imprime o conteúdo da matriz A, na qual os caracteres do programa estão localizados. A criação da matriz A é mostrada abaixo.

# ===========================================
# =                       =
# ===========================================

numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
operators = ['+', '-', '*', '/', '%']
point = Pointer()                           #  
text = open_file(argv[1]).split("\n")       #  
n = len(text)                               # n =  
m = 0                                       # m =  

#    (   m)
for line in text:
    if len(line) > m:
        m = len(line)

#   ( n  m )
A = [' '] * n
for i in range(n):
    A[i] = [' '] * m

#    
for i in range(len(text)):
    for j in range(len(text[i])):
        A[i][j] = text[i][j]

Configurações básicas do programa. Na lista de números, colocamos todos os números de 0 a 9, que podem ser escritos no programa (10 não caberão mais, dois caracteres). Na lista de operadores, colocamos os operadores aritméticos básicos. Crie um objeto point = Pointer (configurações padrão), com o qual trabalharemos o tempo todo ...

No texto variável, colocamos o texto lido do programa executável, quebrado pelo símbolo "nova linha". Como resultado, o texto consiste em várias linhas de texto de diferentes comprimentos, que devem ser colocadas em uma matriz retangular de espaços em seus lugares. É fácil encontrar o número de linhas n = len (texto), mas o número de colunas deve ser calculado com base no comprimento máximo das linhas incluídas no texto. Não encontrei outra maneira de fazer essa testa: percorra todas as linhas e encontre a que tem o comprimento máximo. Tendo em mãos n e m (o número de linhas e o número de colunas do campo futuro), é possível criar uma matriz bidimensional, preenchê-la com espaços e passar o texto para colocar os caracteres em seus lugares.

O resultado é um retângulo de caracteres entre quais espaços e tudo isso é uma matriz bidimensional n por m. Após essas configurações, você pode chamar a função field_print () e garantir que tudo fique bonito, nada tenha flutuado e não tenha violado a situação.

# ==========================================
# =                       =
# ==========================================

# field_print()

while point.value != '@':
    try:
        point.value = A[point.x][point.y]       # 1)    point
        point.action()                          # 2)  
        # print(point)                            # 3) :  
        # time.sleep(0.5)                         # 4) :  0.5 c
    except IndexError:                          #     =  
        print('     ')
        break

# field_print()
print()

Tudo termina com o programa principal (ciclo), antes e depois do qual você pode exibir o campo (às vezes útil). O ciclo gira até que o ponteiro aponte para o símbolo "@" (e comercial, cachorro, final do programa). Dentro do ciclo, são executadas 4 ações por vez:

  1. Na propriedade point.value, o caractere localizado na matriz A nas coordenadas point.x, point.y é lido
  2. O método point.action () é chamado, no qual o comando atual (apenas leitura) é executado
  3. Um ponteiro é exibido na tela (todas as propriedades)
  4. Um atraso é feito antes da próxima iteração (0,1 s - 0,5 s)

Os itens 3 e 4 são completamente opcionais (eles até são comentados), mas eu recomendo usá-los para testes. Todas as ações dentro do loop ocorrem com a captura de um erro IndexError (erro que excede os limites do índice), permitindo interceptar dois erros principais do compilador:

  1. Viramos para a pilha e não há valores
  2. Acidentalmente fomos além do programa (array) em largura ou altura

A última impressão vazia () é necessária para que, depois que a saída seja exibida, o console funcione a partir de uma nova linha.

Parte 2


Agora é hora do código mais importante - o conteúdo do método point.action () da classe Pointer. Tudo abaixo deve ser inserido onde foi escrito:

    def action(self):
	#   ,   

Preste atenção ao recuo:

        if self.value == '"' and self.stack_sf == 0:                # ,  "
            self.stack_sf = 1
        elif self.value == '"' and self.stack_sf == 1:
            self.stack_sf = 0
        elif self.stack_sf == 1:                                    # "Hello"   
            self.stack.append(ord(self.value))
        elif self.value in numbers and self.stack_sf == 0:
            # 123   
            self.stack.append(int(self.value))

De fato, o código dentro de action () possui muitas condições, cada uma das quais é executada quando esse comando está sob o ponteiro. Tudo começa com a condição "se o comando atual = aspas e o sinalizador do início da linha stack_sf = 0", nesse caso, o sinalizador é elevado para 1. Entramos na linha.

(Caso contrário), se o comando atual = aspas e o sinalizador forem aumentados para 1, isso significa que as aspas foram encontradas uma segunda vez e você deve parar de inserir a sequência (o sinalizador stack_sf é reduzido para 0). Estamos fora da linha.

(Caso contrário), se as duas primeiras condições não funcionarem e o sinalizador stack_sf = 1, estaremos "localizados dentro da linha" e precisaremos adicionar o código do símbolo atual à pilha. Não é o próprio personagem, mas seu código ASCII.

(Caso contrário) se o caractere atual estiver entre os elementos dos números e o sinalizador for stack_sf = 0, então este é, primeiro, um dígito e, em segundo lugar, não estamos dentro da linha, precisamos adicionar o caractere atual = dígito à pilha. Adicione não o código do caractere, mas o próprio caractere. Ainda lembre-se de que existe o número 1 e ela tem um código = 49. Portanto, se estamos dentro da linha, precisamos adicionar 49 à pilha e, se estiver apenas no programa, o comando 1 deve ser adicionado à pilha 1. A

seguir, todas as condições serão elif (caso contrário, se ...), então vou escrevê-las "E se". Além disso, todas as condições são duplas, você precisa verificar a igualdade do caractere atual com o comando e o fato de não estarmos dentro da string (dentro da string, todos os caracteres são processados ​​de maneira diferente). Você pode escrever tudo isso de uma maneira mais otimizada, mas esta solução permite que você concentre a atenção nessa testa.

        elif self.value in operators and self.stack_sf == 0:
            b = self.stack.pop()
            a = self.stack.pop()
            if self.value == '+':
                res = a + b                                         # a+b  
            elif self.value == '-':
                res = a - b                                         # a-b  
            elif self.value == '*':
                res = a * b                                         # a*b  
            elif self.value == '/':
                if b == 0:
                    res = 0
                else:
                    res = a // b                                    # a//b  
            elif self.value == '%':
                res = a % b                                         # a%b  
            self.stack.append(res)

Se o caractere atual estiver entre operadores (e stack_sf = 0), significa que entramos em uma operação aritmética. Todos eles são exatamente iguais: 1) o número b é removido (com exclusão), 2) o número a é removido (com exclusão), 3) res = o valor da operação entre aeb, 4) res é empurrado para a pilha. Ao dividir por 0, a resposta é 0, embora o autor do idioma tenha fornecido a opção de 0 ou 1.

        elif self.value == '!' and self.stack_sf == 0:
            a = self.stack.pop()
            if a == 0:
                a = 1
            else:
                a = 0
            # 0->1, 1->0
            self.stack.append(a)

        elif self.value == '`' and self.stack_sf == 0:
            a = self.stack.pop()        # 
            b = self.stack.pop()        # 
            if b > a:
                res = 1
            else:
                res = 0
            # b>a -> 1|0
            self.stack.append(res)

Se o caractere atual for “!”, Será necessário substituir a cabeça (vértice) da pilha: era 0 - se tornará 1, havia algo diferente de 0 - será 1. Se o caractere atual for “» ”(apóstrofo), será necessário verificar a parte superior e a coluna : 1) se o vértice for maior que o vértice, então 1, 2) se o vértice for menor que (ou igual a) o vértice, então 0. é colocado na pilha.Por favor, note que ao remover itens para comparação, eles são removidos (excluídos), não são copiados.

        elif self.value == '?' and self.stack_sf == 0:
            # ? ( )
            a = random.randint(1, 4)
            self.vector = a

        elif self.value == ':' and self.stack_sf == 0:              #  
            last = self.stack.pop()
            self.stack.append(last)
            self.stack.append(last)

        elif self.value == '\\' and self.stack_sf == 0:             # ab => ba
            a = self.stack.pop()
            b = self.stack.pop()
            self.stack.append(a)
            self.stack.append(b)

Se o caractere atual for "?", Será necessário escolher uma direção aleatória e segui-la. Utilizamos a função random.randint (1, 4), que gera números 1,2,3,4 e coloca o novo valor em point.vector.

Se o caractere atual for ":", colocaremos na pilha uma cópia do topo da pilha, ou seja, leia-o e adicione-o à pilha duas vezes.

Se o caractere atual for "\\" (barra invertida), será necessário trocar o vértice e o subvertex. Nós temos dois números, os colocamos na pilha na ordem inversa.

        elif self.value == '#' and self.stack_sf == 0:              #  ""
            self.step()

        elif self.value == ',' and self.stack_sf == 0:              # =65=A
            value = self.stack.pop()
            print(chr(value), end='')

        elif self.value == '.' and self.stack_sf == 0:              # Print 
            a = self.stack.pop()
            print(a, end='')

Se o símbolo atual for "#" (libra), você deverá pular o próximo comando (na direção). Observe que no final da action () existe um salto incondicional para a frente self.step (), que permite avançar para o próximo comando. Depois de escrevermos self.step () no processamento "#", na verdade fazemos dois saltos e "pulamos" o próximo comando após o "#".

Se o caractere atual for “,” (vírgula), será necessário imprimir um caractere cujo código ASCII esteja no topo da pilha. Se o número 65 estiver lá, “A” deve ser exibido.

Se o caractere atual for "." (ponto), é necessário imprimir o número que fica na parte superior da pilha, como um número. Se houver 65, será necessário exibir "65". Nos dois casos, o parâmetro end = '' é definido durante a saída para que não haja nova quebra de linha.

        elif self.value == '_' and self.stack_sf == 0:              #  "_"
            test = self.stack.pop()
            if test == 0:
                #  = 0, (2)
                self.vector = 2
            else:
                #  !=0, (4)
                self.vector = 4

        elif self.value == '|' and self.stack_sf == 0:              #  "|"
            test = self.stack.pop()
            if test == 0:
                self.vector = 3
            else:
                self.vector = 1

Se o caractere atual for "_" (sublinhado), verifique na horizontal. Nós removemos da pilha o número para verificar (teste), se = 0, depois movemos para a direita (vetor = 2), se! = 0, então movemos para a esquerda (vetor = 4).

Se o caractere atual = "|" (barra vertical), é necessário fazer uma verificação vertical. Nós removemos o número (teste) da pilha, se ele = 0, então movemos para baixo (vetor = 3), caso contrário, movemos para cima (vetor = 1).

        elif self.value == '$' and self.stack_sf == 0:              #  
            self.stack.pop()

        elif self.value == '~' and self.stack_sf == 0:              # Input: A => 65
            val = input(' : ')
            self.stack.append(ord(val[0]))

        elif self.value == '&' and self.stack_sf == 0:              # Input: 65 => 65
            val = int(input(' : '))
            self.stack.append((val))

        elif self.value == 'p' and self.stack_sf == 0:              # x, y, symcode
            x = self.stack.pop()                                    # A(x,y) = symcode
            y = self.stack.pop()
            symcode = self.stack.pop()
            A[x][y] = chr(symcode)

        # x, y, value=A(x,y)
        elif self.value == 'g' and self.stack_sf == 0:
            x = self.stack.pop()                                    # ord(value) => 
            y = self.stack.pop()
            value = A[x][y]
            self.stack.append(ord(value))

Se o caractere atual = "$", será necessário remover o vértice. Faça pop simples ().

Se o caractere atual = "~" (til), solicitamos ao usuário um caractere e colocamos seu código ASCII na pilha. O usuário "A" (em inglês) enviado, devemos colocar 65 na pilha.Neste caso, colocaremos val [0], caso contrário, o usuário poderá inserir "Apple" e traduzi-lo em código não funciona.

Se o caractere atual = "&" (e comercial), solicitamos ao usuário um número e colocamos o número na pilha. Você entrou 65, precisa colocar 65 na pilha,

agora as duas equipes mais difíceis.

Se o caractere atual = "p", você precisará extrair as coordenadas da célula e o código ASCII do caractere da pilha e, em seguida, colocar esse caractere nessas coordenadas no campo A. Suponha que 1.2.65 estava na pilha e obtivemos (1.2) e 65, devemos colocar o símbolo "A" na célula (1.2). Mais uma vez, noto: conseguimos três números e colocamos um símbolo nas coordenadas.

Se o caractere atual = "g", as coordenadas da célula são recuperadas da pilha, a célula é pesquisada no campo, o caractere é retirado de lá e seu código ASCII é empurrado para a pilha. Suponha que o símbolo "B" estava no campo na célula (2,3), a equipe atual recebeu "g" e obtivemos 2,3 da pilha. Nesse caso, seguimos as coordenadas (2,3), obtemos o símbolo "B" de lá, convertemos para o número 66 (código de símbolo B) e colocamos 66 na pilha.

        elif self.value == '>' and self.stack_sf == 0:              # >
            self.vector = 2
        elif self.value == '<' and self.stack_sf == 0:              # <
            self.vector = 4
        elif self.value == '^' and self.stack_sf == 0:              # ^
            self.vector = 1
        elif self.value == 'v' and self.stack_sf == 0:              # v
            self.vector = 3
        self.step()                                                 #  

Bem, e as últimas linhas de código: a organização de mover o ponteiro pelo campo. Tudo é simples aqui: olhamos para o símbolo e mudamos a direção (vetor). No final da função action () está self.step (), que dá um passo na direção atual. Portanto, action () é a execução da ação e o passo para o próximo caractere.

Conclusão


Escrever este compilador foi um prazer. E quanta alegria trouxe os momentos em que você lança um determinado código no programa, e ele é executado (corretamente!) E você o observa! O site befungius.aurlien.net ajudou muito no trabalho , no qual o autor postou o intérprete on-line Befunge (em JavaScript). Aqui está o vídeo de uma conferência em www.youtube.com/watch?v=oCPT3L33848 , na qual ele fala sobre o idioma.

Para testar, você pode usar quase qualquer programa no Befunge, exceto aqueles que exigem um campo como um toro, ou seja, tem transições em diferentes direções do mundo. Para alguns programas, você precisa ver o próprio campo (por exemplo, o programa do contador altera a coordenada A [0] [1]), portanto, insira a saída dessa coordenada na tela ou exiba toda a matriz A após cada etapa.

De qualquer forma, não se trata de um programa que brilha, mas de um código de treinamento que pode ser adicionado e editado. Erros de digitação são possíveis, embora eu o tenha testado várias vezes. Críticas não são bem-vindas, mas não são proibidas. Boa sorte a todos e bom código.

Olá Mundo!

0"!dlrow olleH">:#,_@

Bazz

0> 1+:3%v
>^  v%5:_:5% v
,v.:_v     v0_0"zzub"v
"v         #
     >0"zzub"v
"   v"fizz"<         <
^<         $<>:#,_v
    >      #^^#   <

Fibonacci

62*1+v>01p001>+v>\:02p\:02gv
     0       ^             <
     .         :p
     "         .1
        v 0," "<0
     "  >1g12-+:|
     ,          @
     >^

Contagem

>91+:9`v
p   v  _v
    >$0 v
^ 01+*68<

Mas isso não funciona, saindo do campo

0>:00p58*`#@_0>:01p78vv$$<
@^+1g00,+55_v# !`\+*9<>4v$
@v30p20"?~^"< ^+1g10,+*8<$
@>p0\>\::*::882**02g*0v >^
`*:*" d":+*:-*"[Z"+g3 < |<
v-*"[Z"+g30*g20**288\--\<#
>2**5#>8*:*/00g"P"*58*:*v^
v*288 p20/**288:+*"[Z"+-<:
>*%03 p58*:*/01g"3"* v>::^
   \_^#!:-1\+-*2*:*85<^

Links e materiais adicionais:

  1. Código completo
  2. Versão online
  3. Ela está em JavaScript
  4. Documentação (inglês)
  5. Artigo do Wiki (inglês)

All Articles