EDIT: Come alcuni di voi sospettavano, c'era un bug nell'interprete ufficiale: l'ordine di composizione in .
era invertito. Avevo due versioni dell'interprete e ho usato quella sbagliata qui. Gli esempi sono stati scritti anche per questa versione errata. Ho corretto l'interprete nel repository e gli esempi seguenti. Anche la descrizione >
era un po 'ambigua, quindi l'ho risolto. Inoltre, mi scuso per questo tempo, sono stato coinvolto in alcune cose della vita reale.
EDIT2: Il mio interprete aveva un bug nell'implementazione .
che si rifletteva negli esempi (si basavano su comportamenti indefiniti). Il problema è stato risolto.
introduzione
Shift è un linguaggio di programmazione funzionale esoterico che ho creato un paio di anni fa, ma pubblicato oggi. È basato sullo stack, ma ha anche il curry automatico come Haskell.
specificazione
Ci sono due tipi di dati in Shift:
- Funzioni che hanno un'arità positiva arbitraria (numero di ingressi) e che restituiscono un elenco di uscite. Ad esempio, una funzione che duplica il suo unico input ha arity 1 e una funzione che scambia i suoi due input ha arity 2.
- Gli spazi vuoti, che sono tutti identici e non hanno altro scopo che non essere funzioni.
Un programma Shift è composto da zero o più comandi , ognuno dei quali è un singolo carattere ASCII. Ci sono 8 comandi in totale:
!
( applica ) apre una funzionef
e un valorex
dallo stack e si applicaf
ax
. Sef
ha arity 1, l'elencof(x)
viene aggiunto all'inizio dello stack. Se ha arityn > 1
, una nuova(n-1)
funzione -aryg
viene inserita nello stack. Prende input e restituisce .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( vuoto ) inserisce uno spazio vuoto nella pila.+
( clone ) inserisce nello stack una funzione unaria che duplica il suo input: qualsiasi valorex
viene mappato[x,x]
.>
( shift )n
inserisce nello stack una funzione unaria che accetta una funzione -aryf
e restituisce una(n+1)
funzione -aryg
che ignora il suo primo argomentox
, chiamaf
i rimanenti e puntax
di fronte al risultato. Ad esempio,shift(clone)
è una funzione binaria che accetta inputa,b
e resi[a,b,b]
./
( fork ) inserisce nello stack una funzione ternaria che accetta tre inputa,b,c
e restituisce[b]
sea
è vuoto, e in caso[c]
contrario.$
( Chiamata ) spinge allo stack una funzione binaria che si apre una funzionef
e un valorex
, e si applicaf
ax
esattamente come!
fa..
( catena ) spinge nello stack una funzione binaria che apre due funzionif
eg
restituisce la loro composizione: una funzioneh
che ha la stessa arità dif
e che accetta normalmente i suoi input, si applicaf
a loro e quindi si applica completamenteg
al risultato (chiamate tutte le volte che la sua arità lo impone), con oggetti inutilizzati dall'output dif
rimanere nel risultato dih
. Ad esempio, supponiamo chef
sia una funzione binaria che clona il suo secondo argomento edg
è call . Se lo stack contiene[f,g,a,b,c]
e lo facciamo.!!
, allora contiene[chain(f,g),a,b,c]
; se lo facciamo!!
dopo,f
viene prima applicatoa,b
, producendo[a,b,b]
, quindig
viene applicato ai primi due elementi di ciò poiché la sua arity è 2, producendo[a(b),b]
, e lo stack sarà finalmente[a(b),b,c]
.@
( diciamo ) spinge una funzione unaria che restituisce semplicemente il suo input e stampa0
se era uno spazio vuoto e1
se era una funzione.
Si noti che tutti i comandi, tranne che !
semplicemente spingono un valore nello stack, non c'è modo di eseguire l'input e l'unico modo per produrre qualcosa è usare @
. Un programma viene interpretato valutando i comandi uno per uno, stampando 0
s o 1
s ogniqualvolta "dice" si chiama, e l'uscita. Qualsiasi comportamento non descritto qui (applicazione di uno spazio vuoto, applicazione di uno stack di lunghezza 0 o 1, chiamata "catena" su uno spazio vuoto ecc.) È indefinito: l'interprete potrebbe bloccarsi, fallire silenziosamente, chiedere input o altro.
L'obiettivo
Il tuo compito è scrivere un interprete per Shift. Dovrebbe prendere da STDIN, riga di comando o argomento di funzione un programma Shift da interpretare e stampare su STDOUT o restituire l'output (forse infinito) risultante di 0
s e 1
s. Se scrivi una funzione, devi essere in grado di accedere agli output a lunghezza infinita in qualche modo (generatore in Python, lista pigra in Haskell, ecc.). In alternativa, puoi prendere un altro input, un numero n
e restituire almeno n
caratteri dell'output se è più lungo di n
.
Vince il conteggio di byte più basso e non sono consentite scappatoie standard.
Casi test
Questo programma Shift stampa 01
:
?@!@@!
A partire da sinistra: spingere uno spazio vuoto, premere dire , quindi applicare la parola allo spazio vuoto. Questo produce 0
. Poi, spinta dire due volte, e applicare la seconda parola al primo. Questo produce 1
.
Questo programma esegue un ciclo continuo, senza produrre output:
$+.!!+!!
Invia chiamata e clona , quindi applica loro la catena (abbiamo bisogno di due !
secondi poiché la catena è una funzione binaria). Ora lo stack contiene una funzione che accetta un argomento, lo duplica e chiama la prima copia sul secondo. Con +!!
, dupliciamo questa funzione e la chiamiamo su se stessa.
Questo programma stampa 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Spingere uno spazio vuoto e dire . Quindi, componi una funzione binaria che copia il suo secondo argomento b
, quindi copia il primo a
e lo compone con se stesso, quindi applica la composizione alla copia di b
, ritornando [a(a(b)),b]
. Applicalo per dire e vuoto, quindi applica dire ai due elementi rimanenti nella pila.
Questo programma stampa 0
. Per ognuno di !!!
quelli che aggiungi, ne stampa un ulteriore 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Spingere uno spazio vuoto e dire . Quindi, componi una funzione ternaria che accetta f,g,x
come input e restituisce [f,f,g,g(x)]
. Clona quella funzione e applicala a se stessa, diciamo , e al vuoto. Questa applicazione non modifica lo stack, quindi possiamo applicare nuovamente la funzione tutte le volte che vogliamo.
Questo programma stampa la sequenza infinita 001011011101111...
, dove il numero di 1
s aumenta sempre di uno:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Il repository contiene una versione annotata.
f(x1, x2, ..., xn)
e g(y1, y2, ..., ym)
. La chiamata .
fa scoppiare entrambi e spinge una funzione h(z1, z2, ..., zn)
. Ora puoi sgretolare tutti questi argomenti valutandoli gradualmente !
. Dopo n
tali applicazioni, la funzione rimanente aveva solo un argomento, e a quel punto calcola f(z1, z2, ..., zn)
(vale f
a dire applicata a tutti gli argomenti in cui hai scritto), che invia alcuni nuovi valori e quindi consuma immediatamente i m
valori dallo stack e g
li chiama .
.
funziona esattamente come descritto da Martin, tranne per il fatto che se f
restituisce un elenco inferiore ai m
valori, il risultato è indefinito (la composizione ha arity n
, quindi non può mangiare più argomenti dallo stack). In sostanza, l'output di f
viene utilizzato come stack temporaneo, sul quale g
viene spinto e applicato il m
tempo impiegato !
, e il risultato viene aggiunto allo stack principale.