Traduire Prélude en Befunge

19

Il s'agit du défi hebdomadaire n ° 2. Thème: Traduction

Écrivez un programme ou une fonction qui prend le code source d'un programme dans Prelude et génère du code pour un programme équivalent dans Befunge-93 . Pour que le programme soit équivalent, il doit, pour toute entrée donnée, produire la même sortie que le programme Prelude, et s'arrêter si et seulement si le programme Prelude s'arrête.

Langue d'entrée: Prélude

Interpréteur Python:

Un programme Prelude se compose d'un certain nombre de "voix" qui exécutent des instructions simultanément. Les instructions pour chaque voix sont sur une ligne distincte. Chaque voix a une pile distincte, qui est initialisée avec une quantité infinie de zéros. L'exécution commence dans la colonne la plus à gauche et avance d'une colonne vers la droite à chaque tick, sauf lorsqu'elle est influencée par )ou par des (instructions. Le programme se termine lorsque la dernière colonne est atteinte.

Prélude spec pour ce défi:

Digits 0-9      Push onto the stack a number from 0 to 9. Only single-digit
                    numeric literals can be used.
^               Push onto the stack the top value of the stack of the above 
                    voice.
v               Push onto the stack the top value of the stack of the below 
                    voice.
#               Remove the top value from the stack.
+               Pop the top two integers from the stack and push their sum.
-               Pop the top two integers from the stack, subtract the topmost 
                    from the second, and push the result.
(               If the top of the stack is 0, jump to the column after the 
                    matching `)` after the current column executes.
)               If the top of the stack is not 0, jump to the column after 
                    the matching `(` after the current column executes.
?               Read an integer from STDIN.
!               Pop one value from the stack and print it to STDOUT as an
                    integer.
<space>         No-op

Remarques

  • vet ^agissez de manière cyclique, de sorte que la vvoix du bas copiera l'élément de pile de la voix du haut et ^la voix du haut copiera de la voix du bas. Corollaire: les deux vet ^dupliquez le haut de la pile dans un programme à une seule voix.
  • A (et sa correspondance )peuvent être situés sur des lignes différentes. Cependant , un )regardera toujours la pile de la voix où le correspondant a (été placé, pas la pile où le )lui-même est placé.
  • Les valeurs produites par les instructions ^et vopèrent sur les valeurs présentes avant la fin de toute autre opération dans la même colonne.
  • ?et !fonctionner différemment de la spécification trouvée sur esolangs.org, alors assurez-vous de tester avec l'interpréteur légèrement modifié fourni dans cet article.

L'entrée est garantie d'avoir:

  • Parenthèses correspondantes
  • Pas plus d'une parenthèse dans une colonne
  • Même nombre de caractères sur chaque ligne
  • Au moins une ligne
  • Aucune colonne avec plus d'une instruction d' E / S ( !ou ?)
  • Un caractère de saut de ligne après les instructions pour chaque voix
  • Pas de caractères autres que ceux mentionnés ci-dessus

Langue de sortie: Befunge-93

Befunge est un langage basé sur une pile dont le compteur de programme (PC; un pointeur vers l'instruction en cours) se déplace librement sur une grille à deux dimensions. Il commence dans le coin supérieur gauche, se déplaçant vers la droite. Le champ de jeu est toroïdal, c'est-à-dire que le mouvement du PC s'enroule autour des deux bords. Befunge possède également une pile qui est initialisée à un nombre infini de zéros. Befunge a les opérations suivantes:

Vous pouvez assumer les caractéristiques suivantes du compilateur / interprète Befunge-93:

  • Les entiers sont d'une précision illimitée.
  • Il permet des grilles de toute taille.
  • Les coordonnées de la grille (pour get p) sont basées sur 0.

Notation

Afin d'éviter les soumissions qui produisent simplement un interpréteur Prelude dans Befunge et codent en dur la source Prelude, l'objectif sera de minimiser la taille du code source Befunge résultant.

Vous trouverez ci-dessous un certain nombre de programmes Prelude. Votre traducteur sera exécuté sur tous ces éléments. Votre score est la somme des tailles des programmes Befunge, à condition que tous soient valides.

Votre traducteur ne doit pas être optimisé spécifiquement vers ces cas de test (par exemple en codant en dur des programmes Befunge manuscrits pour eux). Si je soupçonne une réponse, je me réserve le droit de modifier les entrées ou d'en créer d'autres.

Exemples d'entrées

Imprimer n-1jusqu'à 0:

?(1-^!)

ET logique:

?  (0)  
 ?(0  ) 
    1  !

OU logique:

 ?   (0) 
? (0)    
   1  1 !

Vérifier la parité d'entrée (ie modulo 2) du nombre non négatif:

?(1-)   
 ^  v   
 v1-^^-!

Mettre l'entrée au carré:

 ^    
  ^+ !
?(1-) 

Imprimez le n ème numéro de Fibonacci, où n = 0correspond à 0 et n = 1correspond à 1:

0 v+v!
1   ^ 
?(1-) 

Signum:

  1) v #  - !
 vv (##^v^+) 
?(#   ^   ## 

Division pour les entrées non négatives:

1 (#  1) v #  - 1+)   
     vv (##^v^+)      
?  v-(0 # ^   #       
 ?                    
   1+              1-!

Bien sûr, votre programme doit présenter le même comportement dans tous les cas, même si le comportement de l'exemple de programme pour les nombres négatifs n'est pas spécifié.

Enfin, votre traducteur ne doit pas être excessivement long:

  • Il doit être contenu dans une publication Stack Exchange
  • Il doit traiter les entrées d'échantillon en moins de 10 minutes sur un ordinateur de bureau typique.

Notez qu'une entrée numérique pour Prelude ou Befunge est donnée sous la forme d'un signe moins facultatif suivi d'un ou plusieurs chiffres décimaux, suivi d'un retour à la ligne. Une autre entrée est un comportement indéfini.

Vous pouvez écrire votre traducteur dans n'importe quelle langue. Le code Befunge traduit le plus court gagne.

Classement

  • Sp3000 : 16430 octets
feersum
la source
Je ne comprends pas: "Poussez sur la pile la valeur supérieure sur la pile de la voix ci-dessus." Ne doit-il pas être: "Poussez sur la pile la valeur maximale de la pile de la voix ci-dessus."
Def
Il dit que prélude exécute les voix simultanément, cela signifie-t-il qu'elles sont vraiment exécutées sur un thread séparé ou puis-je simplement exécuter les premières commandes sur toutes les voix (de haut en bas) puis les secondes commandes et ainsi de suite.
Def
@Deformyer Je l'ai changé de "on" en "of", mais je pensais que "top value on the stack" n'était pas faux non plus. Quant à la simultanéité, non, vous n'avez pas besoin de les interpréter en parallèle. Ce qui est important, c'est qu'ils agissent tous sur l'état précédent des piles, et aucune opération dans une colonne donnée ne peut affecter une autre opération dans cette colonne.
Martin Ender
Plusieurs des cas de test ne violent-ils pas "Aucune colonne avec plus d'une instruction d'E / S (! Ou?)?"
Fuwjax
@proudhaskeller Le se 1trouve dans une boucle, il ne peut donc pas être poussé. Un 0 peut provenir de la quantité infinie de 0 qui commencent sur les piles.
feersum

Réponses:

10

Python 3, marquera plus tard

from collections import defaultdict
from functools import lru_cache
import sys

NUMERIC_OUTPUT = True

@lru_cache(maxsize=1024)
def to_befunge_num(n):
    # Convert number to Befunge number, using base 9 encoding (non-optimal,
    # but something simple is good for now)

    assert isinstance(n, int) and n >= 0

    if n == 0:
        return "0"

    digits = []

    while n:
        digits.append(n%9)
        n //= 9

    output = [str(digits.pop())]

    while digits:
        output.append("9*")
        d = digits.pop()

        if d:
            output.append(str(d))
            output.append("+")

    output = "".join(output)

    if output.startswith("19*"):
        return "9" + output[3:]

    return output

def translate(program_str):
    if program_str.count("(") != program_str.count(")"):
        exit("Error: number of opening and closing parentheses do not match")

    program = program_str.splitlines()
    row_len = max(len(row) for row in program)
    program = [row.ljust(row_len) for row in program]
    num_stacks = len(program)


    loop_offset = 3
    stack_len_offset = program_str.count("(")*2 + loop_offset
    stack_offset = stack_len_offset + 1
    output = [[1, ["v"]], [1, [">"]]] # (len, [strings]) for each row
    max_len = 1 # Maximum row length so far

    HEADER_ROW = 0
    MAIN_ROW = 1
    FOOTER_ROW = 2
    # Then stack lengths, then loop rows, then stacks

    # Match closing parens with opening parens
    loop_map = {} # {column: (loop num, stack number to check, is_start)}
    loop_stack = []
    loop_num = 0

    for col in range(row_len):
        col_str = "".join(program[stack][col] for stack in range(num_stacks))

        if col_str.count("(") + col_str.count(")") >= 2:
            exit("Error: more than one parenthesis in a column")

        if "(" in col_str:
            stack_num = col_str.index("(")

            loop_map[col] = (loop_num, stack_num, True)
            loop_stack.append((loop_num, stack_num, False))
            loop_num += 1

        elif ")" in col_str:
            if loop_stack:
                loop_map[col] = loop_stack.pop()

            else:
                exit("Error: mismatched parentheses")


    def pad_max(row):
        nonlocal max_len, output

        while len(output) - 1 < row:
            output.append([0, []])

        if output[row][0] < max_len:
            output[row][1].append(" "*(max_len - output[row][0]))
            output[row][0] = max_len


    def write(string, row):
        nonlocal max_len, output

        output[row][1].append(string)
        output[row][0] += len(string)

        max_len = max(output[row][0], max_len)


    def stack_len(stack, put=False):
        return (to_befunge_num(stack) + # x
                str(stack_len_offset) + # y
                "gp"[put])


    def get(stack, offset=0):
        assert offset in [0, 1] # 1 needed for 2-arity ops

        # Check stack length
        write(stack_len(stack) + "1-"*(offset == 1) + ":0`", MAIN_ROW)

        pad_max(HEADER_ROW)
        pad_max(MAIN_ROW)
        pad_max(FOOTER_ROW)

        write(">" + to_befunge_num(stack + stack_offset) + "g", HEADER_ROW)
        write("|", MAIN_ROW)
        write(">$0", FOOTER_ROW)

        pad_max(HEADER_ROW)
        pad_max(MAIN_ROW)
        pad_max(FOOTER_ROW)

        write("v", HEADER_ROW)
        write(">", MAIN_ROW)
        write("^", FOOTER_ROW)


    def put(stack, value=""):
        put_inst = (value +
                    stack_len(stack) +
                    to_befunge_num(stack + stack_offset) +
                    "p")

        post_insts.append(put_inst)


    def pop(stack):
        put(stack, "0")


    def inc_stack_len(stack):
        post_insts.append(stack_len(stack) + "1+")
        post_insts.append(stack_len(stack, put=True))


    def dec_stack_len(stack):
        post_insts.append(stack_len(stack) + ":0`-") # Ensure nonnegativity
        post_insts.append(stack_len(stack, put=True))


    # Technically not necessary to initialise stack lengths per spec, but it makes it
    # more portable and easier to test against other Befunge interpreters

    for stack in range(num_stacks):
        write("0" + stack_len(stack, put=True), MAIN_ROW)

    for col in range(row_len):
        post_insts_all = []

        loop_start = False
        loop_end = False

        if col in loop_map:
            if loop_map[col][2]:
                loop_start = True
            else:
                loop_end = True

        if loop_start:
            loop_row = loop_offset + 2*loop_map[col][0]
            get(loop_map[col][1])

        elif loop_end:
            get(loop_map[col][1])
            write("!", MAIN_ROW)


        for stack in range(num_stacks-1, -1, -1):
            char = program[stack][col]
            post_insts = [] # Executed after the gets in reverse order, i.e. last added first

            if char in " ()":
                continue

            # Pre-inc, post-dec
            elif char.isdigit():
                inc_stack_len(stack)
                put(stack, char)

            elif char == "?":
                inc_stack_len(stack)
                put(stack, "&")

            elif char == "!":
                get(stack)
                post_insts.append(".91+," if NUMERIC_OUTPUT else ",")
                pop(stack)
                dec_stack_len(stack)

            elif char == "#":
                pop(stack)
                dec_stack_len(stack)

            elif char in "+-":
                get(stack, 1)
                get(stack)
                post_insts.append(char)
                pop(stack) # This one first in case of ! or 1!
                post_insts.append(stack_len(stack) + ":1`-:1\\`+") # Ensure >= 1
                post_insts.append(stack_len(stack, put=True))
                put(stack)                

            elif char in "^v":
                offset = -1 if char == "^" else 1

                get((stack + offset) % num_stacks)
                inc_stack_len(stack)
                put(stack)

            else:
                exit("Error: invalid character " + char)

            post_insts_all.append(post_insts)


        while post_insts_all:
            write("".join(post_insts_all.pop()), MAIN_ROW)

        if loop_start or loop_end:
            loop_row = loop_offset + 2*loop_map[col][0]

            pad_max(HEADER_ROW)
            pad_max(MAIN_ROW)
            pad_max(loop_row)
            pad_max(loop_row + 1)

            write(">v", HEADER_ROW)
            write("|>", MAIN_ROW)

            if loop_start:
                write(" ^", loop_row)
                write(">", loop_row + 1)

            else:
                write("<", loop_row)
                write(" ^", loop_row + 1)


    write("@", MAIN_ROW)
    return "\n".join("".join(row) for row_len, row in output)

if __name__ == '__main__':
    if len(sys.argv) < 3:
        exit("Usage: py -3 prefunge.py <input filename> <output filename>")

    with open(sys.argv[1]) as infile:
        with open(sys.argv[2], "w") as outfile:
            outfile.write(translate(infile.read()))

Courez comme py -3 prefunge.py <input filename> <output filename>.

La semaine a été lente pour moi, donc je me suis finalement assez ennuyé pour aborder cette question vieille de six mois. Je demanderais pourquoi personne d'autre n'a essayé, mais je ressens toujours la douleur du débogage (et il y a probablement encore des bogues pour tout ce que je sais).

La question ne fournit pas d'interprète Befunge-93, j'ai donc utilisé celui-ci , qui est légèrement différent de la spécification. Les deux principales différences sont les suivantes:

  • Si un caractère n'existe pas dans une ligne donnée du programme, vous ne pouvez pas écrire sur cette ligne. Cela signifie que vous devrez appuyer sur Entrée plusieurs fois pour introduire suffisamment de nouvelles lignes à la fin . Si vous voyez des NaNs dans la sortie, c'est la cause la plus probable.

  • Les cellules de la grille ne sont pas préinitialisées à zéro - pour plus de commodité, j'ai inclus une préinitialisation dans les sorties Befunge, mais comme ce n'est pas nécessaire, je pourrais l'enlever lorsque je commence à marquer.

La disposition de base des programmes de sortie est la suivante:

v [header row]
> [main row]
  [footer row]
  ---
   |
   | rows for loops (2 per loop)
   |
  ---
  [stack length row]
  ---
   |
   | rows for stack space (1 per voice)
   |
  ---

L'espace de la pile est en dehors du programme, d'où le commentaire de nouvelle ligne Enter-spamming précédent.

L'idée centrale est d'attribuer à chaque voix une ligne qui lui sert de pile. Pour conserver ces piles, nous avons également une ligne spéciale de longueur de pile où la longueur de chaque pile est enregistrée dans une cellule le long de la ligne. Le programme est alors beaucoup de gets et puts, par exemple pour l'impression du processus est:

  • Obtenez la cellule à y = stack_row[stack], x = stack_length[stack]
  • Effectuer .91+,, c'est-à-dire imprimer sous forme d'entier puis imprimer une nouvelle ligne
  • Remplacez la cellule des coordonnées ci-dessus par 0 (pour simuler l'éclatement)
  • Décrémenter stack_length[stack]

Pour effectuer l'évaluation simultanée d'une colonne, toutes les cellules nécessaires sont lues et leurs valeurs sont conservées sur la pile avant l'écriture des cellules (par exemple, pour l'exemple d'impression, il peut y avoir plus d'instructions entre la première et la deuxième étape).

`, qui est supérieur à, est utilisé pour s'assurer que les longueurs de pile ne deviennent jamais négatives et pour pousser les 0 lorsque la pile est vide. C'est de là que vient la ramification clairement visible, mais j'ai une idée qui supprimera la ramification, ce qui devrait supprimer une grande partie des espaces blancs des première et troisième lignes.

Pour les boucles, car les boucles Prelude peuvent sauter dans les deux sens, nous utilisons deux lignes par boucle dans une configuration comme celle-ci:

       >v                     >v
(cond) |>  (program)  (cond) !|>

        ^                     <
       >                       ^

Ces boucles constituent actuellement la majorité des octets, mais peuvent être facilement analysées en les plaçant dans la boîte à codes avec plaquelle je prévois de faire une fois que je suis satisfait que le traducteur fonctionne correctement.

Voici un exemple de sortie pour ?(1-^!), c.-à-d. Imprimer n-1jusqu'à 0:

v                        >6gv>v                      >6gv      >6gv                                 >6gv                   >6gv                           >6gv >v
>005p05g1+05p&05g6p05g:0`|  >|>05g1+05p105g6p05g1-:0`|  >05g:0`|  >-005g6p05g:1`-:1\`+05p05g6p05g:0`|  >05g1+05p05g6p05g:0`|  >.91+,005g6p05g:0`-05p05g:0`|  >!|>@
                         >$0^                        >$0^      >$0^                                 >$0^                   >$0^                           >$0^
                              ^                                                                                                                                <
                             >                                                                                                                                  ^

Place à l'entrée:

v                                >8gv      >8gv             >v      >6gv                                   >8gv      >8gv        >7gv      >7gv                                                            >8gv >v      >7gv
>005p015p025p25g1+25p&25g8p25g:0`|  >25g:0`|  >05g1+05p05g6p|>05g:0`|  >15g1+15p15g7p25g1+25p125g8p25g1-:0`|  >25g:0`|  >15g1-:0`|  >15g:0`|  >+015g7p15g:1`-:1\`+15p15g7p-025g8p25g:1`-:1\`+25p25g8p25g:0`|  >!|>15g:0`|  >.91+,015g7p15g:0`-15p@
                                 >$0^      >$0^                     >$0^                                   >$0^      >$0^        >$0^      >$0^                                                            >$0^         >$0^
                                                             ^                                                                                                                                                  <
                                                            >                                                                                                                                                    ^

Division (de petites entrées sont recommandées):

v                                                                          >91+gv>v      >94+gv                                                         >95+gv      >95+gv        >93+gv      >93+gv                                                                    >93+gv      >93+gv               >v      >93+gv                                                     >93+gv >v      >92+gv                  >v      >92+gv                                       >92+gv                                       >91+gv                                       >93+gv                     >91+gv                       >92+gv      >92+gv        >91+gv      >91+gv                                                                                      >92+gv >v                        >91+gv      >91+gv                                     >91+gv >v                        >95+gv      >95+gv                                     >95+gv
>009p019p029p039p049p09g1+09p109g91+p29g1+29p&29g93+p39g1+39p&39g94+p09g:0`|    >|>39g:0`|    >009g91+p09g:0`-09p29g1+29p29g93+p49g1+49p149g95+p49g1-:0`|    >49g:0`|    >29g1-:0`|    >29g:0`|    >-029g93+p29g:1`-:1\`+29p29g93+p+049g95+p49g:1`-:1\`+49p49g95+p29g:0`|    >29g:0`|    >19g1+19p19g92+p|>29g:0`|    >09g1+09p109g91+p19g1+19p19g92+p29g1+29p029g93+p29g:0`|    >!|>19g:0`|    >029g93+p29g:0`-29p|>19g:0`|    >09g1+09p09g91+p019g92+p19g:0`-19p19g:0`|    >019g92+p19g:0`-19p29g1+29p29g93+p09g:0`|    >009g91+p09g:0`-09p19g1+19p19g92+p29g:0`|    >19g1+19p19g92+p09g:0`|    >19g1+19p19g92+p19g1-:0`|    >19g:0`|    >09g1-:0`|    >09g:0`|    >-009g91+p09g:1`-:1\`+09p09g91+p+019g92+p19g:1`-:1\`+19p19g92+p029g93+p29g:0`-29p19g:0`|    >!|>09g1+09p109g91+p09g1-:0`|    >09g:0`|    >+009g91+p09g:1`-:1\`+09p09g91+p09g:0`|    >!|>49g1+49p149g95+p49g1-:0`|    >49g:0`|    >-049g95+p49g:1`-:1\`+49p49g95+p49g:0`|    >.91+,049g95+p49g:0`-49p@
                                                                           >$0  ^        >$0  ^                                                         >$0  ^      >$0  ^        >$0  ^      >$0  ^                                                                    >$0  ^      >$0  ^                       >$0  ^                                                     >$0  ^         >$0  ^                          >$0  ^                                       >$0  ^                                       >$0  ^                                       >$0  ^                     >$0  ^                       >$0  ^      >$0  ^        >$0  ^      >$0  ^                                                                                      >$0  ^                           >$0  ^      >$0  ^                                     >$0  ^                           >$0  ^      >$0  ^                                     >$0  ^
                                                                                  ^                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        <
                                                                                 >                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          ^
                                                                                                                                                                                                                                                                                                          ^                                                                        <
                                                                                                                                                                                                                                                                                                         >                                                                          ^
                                                                                                                                                                                                                                                                                                                                                                                                                    ^                                                                                                                                                                                                                                                                                                                                              <
                                                                                                                                                                                                                                                                                                                                                                                                                   >                                                                                                                                                                                                                                                                                                                                                ^

Il y a aussi un tas d'autres optimisations mineures qui me viennent à l'esprit, comme le remplacement 07p07gpar :07p, mais je prends cette étape à la fois :)

Sp3000
la source
Donc. Beaucoup. Gratuit. Temps.
Optimizer
1
Will score later2 ans et ça continue! :)
HyperNeutrino