EDIT: Comme certains d'entre vous le soupçonnaient, il y avait un bug dans l'interprète officiel: l'ordre de composition .
était inversé. J'avais deux versions de l'interprète et j'ai utilisé la mauvaise ici. Les exemples ont également été écrits pour cette version incorrecte. J'ai corrigé l'interpréteur dans le référentiel et les exemples ci-dessous. La description de >
était également un peu ambiguë, j'ai donc corrigé cela. De plus, je m'excuse d'avoir pris si longtemps, j'ai été pris dans des trucs réels.
EDIT2: Mon interprète avait un bug dans l'implémentation .
qui se reflétait dans les exemples (ils s'appuyaient sur un comportement non défini). Le problème est désormais résolu.
introduction
Shift est un langage de programmation fonctionnel ésotérique que j'ai créé il y a quelques années, mais que j'ai publié aujourd'hui. Il est basé sur la pile, mais possède également un curry automatique comme Haskell.
spécification
Il existe deux types de données dans Shift:
- Fonctions, qui ont une arité positive arbitraire (nombre d'entrées), et qui renvoient une liste de sorties. Par exemple, une fonction qui duplique sa seule entrée a l'arité 1 et une fonction qui échange ses deux entrées a l'arité 2.
- Les blancs, qui sont tous identiques et n'ont d'autre but que de ne pas être des fonctions.
Un programme Shift se compose de zéro ou plusieurs commandes , dont chacune est un seul caractère ASCII. Il y a 8 commandes au total:
!
( appliquer ) extrait une fonctionf
et une valeurx
de la pile et s'appliquef
àx
. Sif
a l'arité 1, la listef(x)
est ajoutée au début de la pile. S'il a de l'aritén > 1
, une nouvelle(n-1)
fonction -aryg
est poussée dans la pile. Il faut des entrées et des retours .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( vide ) pousse un blanc dans la pile.+
( clone ) pousse dans la pile une fonction unaire qui duplique son entrée: toute valeurx
est mappée[x,x]
.>
( shift ) pousse dans la pile une fonction unaire qui prend unen
fonction -aryf
, et retourne une(n+1)
fonction -aryg
qui ignore son premier argumentx
, appellef
les autres et claquex
devant le résultat. Par exemple,shift(clone)
est une fonction binaire qui prend des entréesa,b
et des retours[a,b,b]
./
( fork ) pousse dans la pile une fonction ternaire qui prend trois entréesa,b,c
, et retourne[b]
sia
est un blanc, et[c]
sinon.$
( Appel ) pousse sur la pile une fonction binaire qui apparaît une fonctionf
et une valeurx
, et l' appliquef
àx
exactement comme le!
fait..
( chaîne ) pousse dans la pile une fonction binaire qui fait apparaître deux fonctionsf
etg
, et renvoie leur composition: une fonctionh
qui a la même arité quef
, et qui prend ses entrées normalement, leur s'appliquef
, puis s'applique entièrementg
au résultat (appels autant de fois que son arité le dicte), avec les éléments inutilisés de la sortie def
rester dans le résultat deh
. Par exemple, supposons qu'ilf
s'agit d'une fonction binaire qui clone son deuxième argument et qu'elleg
soit appelée . Si la pile contient[f,g,a,b,c]
et nous le faisons.!!
, alors elle contient[chain(f,g),a,b,c]
; si nous faisons!!
ensuite,f
est d'abord appliqué àa,b
, produisant[a,b,b]
,g
est ensuite appliqué aux deux premiers éléments de celui-ci puisque son arité est 2, produisant[a(b),b]
, et la pile sera finalement[a(b),b,c]
.@
( disons ) pousse une fonction unaire qui retourne simplement son entrée, et affiche0
s'il s'agissait d'un blanc et1
s'il s'agissait d'une fonction.
Notez que toutes les commandes, sauf !
simplement pousser une valeur dans la pile, il n'y a aucun moyen d'effectuer une entrée, et la seule façon de sortir quoi que ce soit est d'utiliser @
. Un programme est interprété en évaluant les commandes une par une, en imprimant 0
s ou 1
s chaque fois que "say" est appelé et en quittant. Tout comportement non décrit ici (appliquer un blanc, appliquer une pile de longueur 0 ou 1, appeler "chaîne" sur un blanc, etc.) n'est pas défini: l'interpréteur peut se bloquer, échouer en silence, demander une entrée, ou autre chose.
La tâche
Votre tâche consiste à écrire un interprète pour Shift. Il doit extraire de STDIN, de la ligne de commande ou de l'argument de fonction un programme Shift à interpréter et l'imprimer vers STDOUT ou renvoyer la sortie résultante (éventuellement infinie) de 0
s et 1
s. Si vous écrivez une fonction, vous devez pouvoir accéder aux sorties de longueur infinie d'une certaine manière (générateur en Python, liste paresseuse en Haskell, etc.). Alternativement, vous pouvez prendre une autre entrée, un nombre n
, et renvoyer au moins des n
caractères de la sortie si elle est plus longue que n
.
Le nombre d'octets le plus bas gagne et les failles standard sont interdites.
Cas de test
Ce programme Shift imprime 01
:
?@!@@!
En partant de la gauche: appuyez sur un blanc, appuyez sur dire , puis appliquez le mot sur le blanc. Cela sort 0
. Ensuite, appuyez deux fois sur say et appliquez le deuxième say au premier. Cela sort 1
.
Ce programme boucle pour toujours, ne produisant aucune sortie:
$+.!!+!!
Poussez call et clone , puis appliquez-leur une chaîne (nous avons besoin de deux !
s car la chaîne est une fonction binaire). Maintenant, la pile contient une fonction qui prend un argument, le duplique et appelle la première copie sur le second. Avec +!!
, nous dupliquons cette fonction et l'appelons sur elle-même.
Ce programme imprime 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Poussez un blanc et dites . Ensuite, composez une fonction binaire qui copie son deuxième argument b
, puis copie le premier a
et le compose avec lui-même, puis applique la composition à la copie de b
, en retournant [a(a(b)),b]
. Appliquez-le à dire et vide, puis appliquez dire aux deux éléments restants sur la pile.
Ce programme s'imprime 0
. Pour chacun !!!
que vous y ajoutez, il en imprime un supplémentaire 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Poussez un blanc et dites . Ensuite, composez une fonction ternaire qui prend f,g,x
en entrée et en retour [f,f,g,g(x)]
. Clonez cette fonction et appliquez-la à elle-même, disons , et au blanc. Cette application ne modifie pas la pile, nous pouvons donc appliquer à nouveau la fonction autant de fois que nous le souhaitons.
Ce programme imprime la séquence infinie 001011011101111...
, où le nombre de 1
s augmente toujours de un:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Le référentiel contient une version annotée.
f(x1, x2, ..., xn)
et g(y1, y2, ..., ym)
. L'appel .
apparaît tous les deux et pousse une fonction h(z1, z2, ..., zn)
. Maintenant, vous pouvez manger tous ces arguments en les étirant progressivement avec !
. Après de n
telles applications, la fonction restante n'avait qu'un seul argument, et à ce stade, elle calcule f(z1, z2, ..., zn)
(c'est- f
à- dire qu'elle s'applique à tous les arguments que vous avez analysés), ce qui pousse de nouvelles valeurs, puis consomme immédiatement les m
valeurs de la pile et les appelle g
.
.
fonctionne exactement comme Martin l'a décrit, sauf que si f
renvoie une liste de m
valeurs inférieures à , le résultat n'est pas défini (la composition a une arité n
, donc elle ne peut pas manger plus d'arguments de la pile). Essentiellement, la sortie de f
est utilisée comme une pile temporaire, sur laquelle g
sont poussées et appliquées des m
temps en utilisant !
, et le résultat est ajouté à la pile principale.