Scrivi un interprete a turni


10

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 funzione fe un valore xdallo stack e si applica fa x. Se fha arity 1, l'elenco f(x)viene aggiunto all'inizio dello stack. Se ha arity n > 1, una nuova (n-1)funzione -ary gviene inserita nello stack. Prende input e restituisce .x1,x2,...,xn-1f(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 valore xviene mappato [x,x].
  • >( shift ) ninserisce nello stack una funzione unaria che accetta una funzione -ary fe restituisce una (n+1)funzione -ary gche ignora il suo primo argomento x, chiama fi rimanenti e punta xdi fronte al risultato. Ad esempio, shift(clone)è una funzione binaria che accetta input a,be resi [a,b,b].
  • /( fork ) inserisce nello stack una funzione ternaria che accetta tre input a,b,ce restituisce [b]se aè vuoto, e in caso [c]contrario.
  • $( Chiamata ) spinge allo stack una funzione binaria che si apre una funzione fe un valore x, e si applica fa xesattamente come !fa.
  • .( catena ) spinge nello stack una funzione binaria che apre due funzioni fe grestituisce la loro composizione: una funzione hche ha la stessa arità di fe che accetta normalmente i suoi input, si applica fa loro e quindi si applica completamenteg al risultato (chiamate tutte le volte che la sua arità lo impone), con oggetti inutilizzati dall'output di frimanere nel risultato di h. Ad esempio, supponiamo che fsia una funzione binaria che clona il suo secondo argomento ed gè 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, fviene prima applicato a,b, producendo[a,b,b], quindi gviene 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 stampa 0se era uno spazio vuoto e 1se 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 0s o 1s 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 0s e 1s. 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 ne restituire almeno ncaratteri 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 ae 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,xcome 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 1s aumenta sempre di uno:

@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!

Il repository contiene una versione annotata.


Sono un po 'confuso qui. Quando scrivi "accetta" come nel comando shift, intendi pop o intendi applicato dal comando apply?
tecywiz121,

1
Inoltre, non sono davvero sicuro dalle tue specifiche su come dovrebbe funzionare la catena. Potresti chiarirlo con un esempio per favore?
tecywiz121,

@ tecywiz121 Ecco come lo capisco: supponiamo che tu abbia due funzioni in cima allo stack 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 ntali applicazioni, la funzione rimanente aveva solo un argomento, e a quel punto calcola f(z1, z2, ..., zn)(vale fa dire applicata a tutti gli argomenti in cui hai scritto), che invia alcuni nuovi valori e quindi consuma immediatamente i mvalori dallo stack e gli chiama .
Martin Ender,

@ MartinBüttner Se Zgarb ritiene che sia in linea con le regole, è possibile utilizzare un secondo parametro di input che definisce la dimensione massima dell'output. Questa sarebbe anche una soluzione per il problema della valutazione pigra.
randomra,

@ tecywiz121 .funziona esattamente come descritto da Martin, tranne per il fatto che se frestituisce un elenco inferiore ai mvalori, il risultato è indefinito (la composizione ha arity n, quindi non può mangiare più argomenti dallo stack). In sostanza, l'output di fviene utilizzato come stack temporaneo, sul quale gviene spinto e applicato il mtempo impiegato !, e il risultato viene aggiunto allo stack principale.
Zgarb,

Risposte:


12

Python 2, 752 667 534 506 445 436 427 404 398 393 byte

Questo non è affatto breve ... ma ho fatto del mio meglio. Qualche suggerimento sul golf sarebbe molto apprezzato ...

EDIT6: questo è ora uno script anziché una funzione. Salvalo in un file (shift.py, forex), quindi eseguilo con $ python shift.py '<my_input>'. Assicurati di inserire l'input tra virgolette singole, altrimenti bash impazzirà con i caratteri di input.

EDIT7: Aaaaaaand ... non è più leggibile. Ma mi sono liberato di altri 23 byte, quindi va bene, immagino? Pubblicherò anche una versione non giocata.

EDIT8: un altro golf, grazie a @Zgarb.

k,d=[],[]
u=k.append
def z(f,a=1):f.a=a;return f
exec "i=!x:x(*map(k.pop,[-1]*x.a)));e=dict(zip('?+>/$.@',[0,!x:u(x)<u(x)),!x:u(!a,*_:x(*_)<u(a),x.a+1))),!x,y,z:u((z,y)[x<1]),3),!x,y:u(!*_:x(y,*_),x.a-1))if x.a>1 else x(y),2),!x,y:u(!*_:x(*_)<i(y),x.a)),2),!x:d.append(`+(x>0)`)<u(x))]))".replace('!',"z(lambda ")
for _ in raw_input():
 try:[i,u][_ in e](e.get(_,e['$']))
 except:break
print d

EDIT: grazie a @DLosc per l'aiuto del golf! Gestito per ridurlo di 85 byte.

EDIT2: ritaglia un sacco di involucri inutili e lascialo cadere di altri 133 byte!

EDIT3: ... e altri 28 grazie a @ Sp3000 e @orlp in chat!

EDIT4: con l'aiuto di @orlp & @ Sp3000, rimosso tutti i decoratori e ora è più corto di 61 byte.

EDIT5: aiuta meeeeee, non riesco a smettere di giocare a golf questo .... Altri 9 byte andati. Sbarazzarsi dell'istruzione di stampa finale salverebbe un altro 7, ma se si esegue m () in un ciclo, tutto l'output è sulla stessa riga ... va bene?

Ecco una versione non golfata:

stack = []
push = stack.append

def arity(func,a=1): #give each of our functions an arity
    func.arity = a
    return func

def do(func): ##pop the args off the stack, then call the function
    args = map(stack.pop,[-1]*func.arity)
    func(*args)

def call(func,arg): #apply is just do(call)
    if func.arity == 1:
        func(arg)
    else:
        def curried(*a): #a quick little currier
            func(arg, *a)
        curried = arity(curried, func.arity - 1)
        push(curried)

def clone(arg):
    push(arg)
    push(arg)

def shift(func):
    def shifted(a, *arg):
        func(*arg)
        push(a)
    shifted = arity(shifted, func.arity + 1)
    push(shifted)

def fork(a, b, c):
    if a == 0:
        push(b)
    else:
        push(c)

def chain(func, gunc):
    def composition(*args):
        func(*args)
        do(gunc)
    composition = arity(composition, func.arity)
    push(composition)

def say(arg):
    print '10'[arg == 0],
    push(arg)

commands = {'?': 0,
            '+': arity(clone),
            '>': arity(shift),
            '/': arity(fork, 3),
            '$': arity(call, 2),
            '.': arity(chain, 2),
            '@': arity(say)}

def interpret(input_string):
    for command in input_string:
        try:
            if command == '!':
                do(call)
            else:
                push(commands[command])
        except RuntimeError: #this handles max recursion depth errors
            break            # for infinite programs
    print

if __name__ == "__main__":
    interpret(raw_input())

L'idea di base è che la lista di Python funziona molto bene come una pila e, memorizzando u=k.append, non solo salvo i personaggi, ma posso anche usare @ucome decoratore per spingere le funzioni (non più!).

Poiché la coppia di funzioni che agiscono su funzioni di n-arity deve essere in grado di accettare un numero arbitrario di argomenti, ho dovuto usare *args, il che significava che il mio piano originale di tracciamento f.func_code.co_argcount doveva essere sostituito da un arity attributo decoratore .

In termini di gestione di infiniti programmi, l'interprete funziona fino a quando non raggiunge la massima profondità ricorsiva; il gestore RuntimeError in fondo ha l'uscita silenziosa in quel punto e quindi stampa la stringa di output corrente.

Casi test:

>>> tests
['?@!@@!', '$+.!!+!!', '?@$..!!+.!!+>!.!!!!+?/!!!@!@>!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!', '?@+$>!>!.!!+>!.!!///!!>!>!.!!+!!!!!!!!!!!!!', '@?/!@>!.!!??/!!>!.!!+.!!.+>!.!!$$.!!$.!!$.!!+.!!$>!>!.!!$>!>!.!!+>!.!!$>!>!>!.!!+>!>!.!!///!!>!>!>!.!!+!!!!!']
>>> for t in tests: m(t)
0 1

0 0 1 0
0
0 0
0 0 0
0 0 0 0
0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

1
La mia prima reazione: @ _ @ Scherzi a parte, bel lavoro: mettere le funzioni effettive in pila è una soluzione davvero accurata. Alcuni consigli: 1) Gli operatori ternari di solito possono essere abbreviati in un modo o nell'altro . 2) È possibile sostituire ['1','0'][...]con solo '10'[...]. 3) Perché x is 0e non x==0(o x<1)? 4) Non preoccuparti di specificare RuntimeError, exceptlo farò. 5) Dal momento che stai usando Python 2, le schede e gli spazi contano come diversi livelli di rientro - semplicemente, ma dovrebbero farti risparmiare ~ 25 byte.
DLosc,

1
Dovresti essere in grado di tagliarlo: x.a==1and x(y)or u(a(x.a-1)(b.partial(x,y)))gli operatori logici sono ancora in corto circuito, ma usano meno caratteri rispetto al ternario. Quindi salvare un altro byte utilizzando x.a-1come condizione (0 / false se xè 1, diverso da zero / true in caso contrario) e scambiare il 'poi' e 'altro' espressioni: x.a-1and u(a(x.a-1)(b.partial(x,y)))or x(y). (Devo giocare a golf un po 'di più ora che mi hai superato ...; ^))
DLosc,

1
Dopo essermi imbattuto in un problema simile al mio, capisco cosa non funziona ora - se x.a==1è vero ma x(y)restituisce qualcosa di falso, prova anche a valutare u(...). Ma sembra che non sia necessario salvare i 3 byte miserabili che ti avrebbero dato! Ammetto, signore: mi hai superato.
DLosc,

1
Solo un cavillo: il formato di output specificato non ha spazi - puoi risolvere con varie strategie , non sei sicuro di quale sia il più breve. Certo, il tuo programma gestisce RuntimeErrormentre il mio chiede all'utente di reindirizzare stderr ... quindi probabilmente siamo anche su cavilli. ; ^)
DLosc,

1
A cosa serve *_il lambdas?
mbomb007,

4

Ghostscript

Non ho ancora giocato a golf, dato che ho ancora bisogno di elaborare la funzione di analisi.

Questa implementazione utilizza _e :invece di >e /, e richiede che tutti i caratteri del programma siano separati da spazi. Questo perché >e /non sono nomi validi in Postscript e gli operatori non si auto-delimitano, ma questo verrà risolto quando scrivo il parser.

La prima parte del codice dovrebbe essere abbastanza trasparente, in quanto si limita a ripetere le definizioni delle funzioni dell'operatore. La magia accade nella definizione di !.

/switch {
    /y exch def
    /x exch def
    {x} {y} ifelse
} bind def

/unwrap {
    dup type (arraytype) eq {aload pop} if
} bind def

/! { % IN: <x> <f> OUT: <g>|<f(x)>
    [ 3 1 roll unwrap] cvx %prefix argument into function
    dup /fun exch def %bind

    [ count 1 roll ] { %run the function sandboxed so it can't take any additional args
        2 dict begin
        /=only {} def % suppress output
            {
                fun
            } stopped /err exch def clear err
        end
    } .runandhide


    exch {
        $error /errorname get
        (stackunderflow) ne {
            handleerror
        } if

        $error /newerror false put

        unwrap
    } {
        unwrap exec
    } ifelse
} def

/? 0 def
/+ {{dup}} def
/_ {{/f exch def pop f}} def % using _ instead of >
/: {{? ne 3 1 roll switch}} def % using : instead of /
/$ {{!}} def
/. {{/g exch def exec g}} def 
/@ {{dup ? eq {0}{1} ifelse =only}} def

Il modo in cui !funziona è semplice: in primo luogo, si aggiunge tesi xa fpremettendo xai contenuti di f, spingendolo indietro sullo stack, e la denominazione di una copia del risultato fun.

Quindi avvolge l'intero stack come un array. .runandhideè un'estensione Ghostscript per l'esecuzione di codice sandbox, che nasconde il contenuto dell'array precedente dalla procedura su cui è invocato. Il dictcomando inserisce un nuovo dizionario nello stack di dict, restringendo l'ambito dei nomi definiti all'interno fino a quando endnon viene rimosso. Sostituisce anche =only(l'operatore di output che utilizzo @) con uno fittizio, sopprimendo l'output durante la prova. stoppedè l'equivalente PostScript dell'istruzione trytrovata in altre lingue e restituisce true se la sua procedura ha generato un errore e false se è stata eseguita fino al completamento.

Una volta completata la versione di prova fun, il programma ripristina lo stack originale dall'array nascosto e, se funcompletato senza errori, lo esegue per davvero, mantenendo l'output.


2

Python3, 685 670 634 633 byte

Sono abbastanza sicuro che questa sia la cosa più lunga che abbia mai giocato a golf. Un tempo era in qualche modo leggibile, ma seguire i consigli di @ sirpercival ha eliminato questo inconveniente!

from re import*
E,*k="E"
P="e(k.pop(),k.pop())"
def H(a,b):global k;k+=list(a)+[N(b)];exec("k+=%s;"%P*Z(N(b)));return[]
def e(a,b):a=sub("(?<!\d)0",repr(N(b,1)).replace("\\",r"\\"),a,1);return Z(a)and[a]or list(eval(a))
D=list(zip("ilhydsSNZ",[3,2,2]+[1]*6,sub("A","N(a)",',b,c:[N([b,c][a>E])]|,b:e(A,N(b))|,b:["H(%s,%s)"%(A,repr(b))]|:print(0+(a>E),end="")or[A]|:[A]*2|:["S(0,%s)"%A]|,b:b+[A]|,b=-1:sub("\d+",lambda m:str(int(m.group())+b),a)|:len(split("\D0",a))-1').split("|")))
for n,r,f in D:exec(n+"=lambda a"+f)
F=dict(zip("/$.@+>?!",D))
for z in input():n,r,f=F[z];k+=z!="!"and[[n+"(%s)"%",".join("0"*r),E][z=="?"]]or eval(P)

kè lo stack, che contiene funzioni rappresentate come stringhe come "h(0,0)"(che è c h ain ). Quando una funzione viene passato come parametro a un'altra funzione, diventa repr'd e tutti i numeri incrementato: "h('h(1,1)',0)". Una volta che tutte le 0s vengono sostituite in una funzione, l'intera cosa viene passata eval, chiamando così la funzione Python appropriata - la maggior parte delle quali sono funzioni lambda generate dalla grande stringa nella riga 6 execnella riga 7.

Ottenere più livelli di funzioni nidificate incrementate, quotate ed evase correttamente era il più grande mal di testa. Potrei risparmiare un po 'di più sulle operazioni regex se potessi supporre che l'annidamento delle funzioni non procederà più in profondità di 9 livelli, ma come sottolineato nei commenti che probabilmente non è un presupposto sicuro.

Versione precedente non modificata del codice:

from re import *
E="E"
stack=[]

clone=lambda a:[unnest(a)]*2
shift=lambda a:["shifted(0,%s)"%unnest(a)]
fork=lambda a,b,c:[unnest(c if a!=E else b)]
call=lambda a,b:apply(unnest(a),unnest(b))
chain=lambda a,b:["chained(%s,%s)"%(unnest(a),repr(b))]
def say(a):
 print(1 if a!=E else 0,end="")
 return [unnest(a)]

shifted=lambda a,b:b+[unnest(a)]
def chained(a,b):
 global stack
 stack+=list(a)+[unnest(b)]
 exec("stack+=apply(stack.pop(),stack.pop());"*zeros(unnest(b)))
 return []

nest=lambda a,direction:sub("\d+",lambda m:str(int(m.group())+direction),a)
unnest=lambda a:nest(a,-1)
zeros=lambda a:len(split("\D0",a))-1
def apply(a,b):
 a=sub("(?<!\d)0",repr(nest(b,1)).replace("\\",r"\\"),a,1)
 return [a] if zeros(a) else list(eval(a))

functions=dict(zip("+>/$.@",zip(["clone","shift","fork","call","chain","say"],[1,1,3,2,2,1])))

for cmd in input():
 if"!"==cmd:
  stack+=apply(stack.pop(),stack.pop())
 elif"?"==cmd:
  stack+=[E]
 else:
  name,arity=functions[cmd]
  stack+=[name+"(%s)"%",".join("0"*arity)]

L'unico potenziale difetto di questa implementazione è che utilizza la ricorsione, quindi i programmi che dovrebbero essere infiniti raggiungono la profondità massima di ricorsione abbastanza rapidamente. (Probabilmente vuoi reindirizzare stderr quando esegui un programma infinito, altrimenti la traccia dello stack sommergerà l'output reale.) A parte questo, tutto sembra funzionare.


Potresti scrivere un programma che genera il programma sopra e quindi lo esegue? Hai un sacco di codice ricorrente che dovrebbe essere comprimibile come lambda ae k.pop().
mbomb007,

@ mbomb007 ... Penso che il mio cervello esploderebbe. (Ma vedi modifica recente - Ho reso la k.pop()situazione un po 'meno ripetitiva, comunque.)
DLosc

puoi fare il trucco exec / translate per tutti quei lambda? incollarli tutti in una stringa?
sirpercival

un altro commento: dubito che tu possa fare affidamento sulla nidificazione delle funzioni <= 9 con questa lingua
sirpercival

@sirpercival Sì, stavo pensando di provarlo. E no, suppongo di no. : ^ P
DLosc,

1

Ceylon, 1167 1057 1031

Non capisco così brevemente come le versioni in pitone monocromatiche ...

import ceylon.language.meta.model{N=Function}import ceylon.collection{H=HashMap}interface D of F|b{}object b satisfies D{}class F(shared Integer a,[D+](D+)f,[D*]c=[])satisfies D{shared[D+]o(D i){[D+]s=[i].prepend(c);return a==1then f(*s)else[F(a-1,f,s)];}shared[D+]y([D+]i){return f(*i.prepend(c));}}F m<A>(N<[D+],A>f)given A satisfies[D+]=>F(f.parameterTypes.size,(D+i)=>f.apply(*i));[D,D]e(D x)=>[x,x];[F]t(F f){[D+]g(D+i){assert(is[D+]r=i.rest);return[i[0],*f.y(r)];}return[F(f.a+1,g)];}[D]k(D a,D d,D c)=>a==b then[d]else[c];[D+]l(F a,D x)=>a.o(x);[F]n(F f,F g){[D+]h(D+i){[D+]r=f.y(i);assert(is[D+]d=r[0:g.a]);return g.y(d).append(r[g.a...]);}return[F(f.a,h)];}[D]y(D x){process.write(x==b then"0"else"1");return[x];}class I(){variable D[]s=[];value c=H{'?'->b,'+'->m(`e`),'>'->m(`t`),'/'->m(`k`),'$'->m(`l`),'.'->m(`n`),'@'->m(`y`)};shared void r(Character i){if(i=='!'){assert(is F f=s[0],is D x=s[1]);s=f.o(x).append(s[2...]);}else{assert(is D d=c[i]);s=[d].append(s);}}}shared void z(){process.readLine()?.collect(I().r);}

Ecco una versione formattata (e commentata) dello stesso codice (con spazi / newline / commenti diventa 4867 byte):

import ceylon.language.meta.model {
    N=Function
}
import ceylon.collection {
    H=HashMap
}
//↑ Import of stuff we need – with a shorter alias.
// (The comment is down here due to a bug in my comment and space
//  remover – it doesn't remove a comment if it is the first token
//  at all.)

// Our data items are either functions or blanks.
interface D of F | b {}

// There is no point in having many blanks – so here a singleton.
object b satisfies D {}

// The function class. Our functions take a number of data items,
// and return a number of data items.
// We know the arity a, and have also an actual function f, and a number
// or already collected arguments.
class F(shared Integer a, [D+](D+) f, [D*] c = [])
        satisfies D {
    // apply once (= collect one parameter). Returns either the result,
    // or a function with arity one less.
    shared [D+] o(D i) {
        [D+] s = [i].prepend(c);
        return a == 1 then f(*s) else [F(a - 1, f, s)];
    }
    // apply fully (= with all needed parameters).
    // The input size should equal the arity.
    shared [D+] y([D+] i) {
        // merge collected and input arguments.
        return f(*i.prepend(c));
    }
}
// creates a shift function from a ceylon function,
// deriving the arity using reflection.
F m<A>(N<[D+],A> f)
        given A satisfies [D+]
        => F(f.parameterTypes.size, (D+ i) => f.apply(*i));

//
// clone: a unary function that duplicates its input: any value x is mapped to [x,x].
//
[D, D] e(D x) => [x, x];

//
// shift: a unary function that takes in an n-ary function f, and returns an
// (n+1)-ary function g that ignores its first argument x, calls f on the
// remaining ones, and tacks x in front of the result. For example,
// shift(clone) is a binary function that takes inputs a,b and returns [a,b,b].
//
[F] t(F f) {
    [D+] g(D+ i) {
        assert (is [D+] r = i.rest);
        return [i[0], *f.y(r)];
    }
    return [F(f.a + 1, g)];
}

//
// fork: a ternary function that takes three inputs a,d,c, and returns [d] if a is a blank,
// and [c] otherwise.
//
[D] k(D a, D d, D c) => a == b then [d] else [c];

//
// call: a binary function that pops a function f and a value x,
//        and applies f to x exactly as ! does.
//
[D+] l(F a, D x) => a.o(x);

//
// chain:  a binary function that pops two functions f and g, and returns their composition:
//         a function h that has the same arity as f, and which takes its inputs normally, applies
//         f to them, and then fully applies g to the result (calls it as many times as its arity
//         dictates), with unused items from the output of f remaining in the result of h. For
//         example, suppose that f is a binary function that clones its second argument, and
//         g is call. If the stack contains [f,g,a,b,c] and we do .!!, then it contains
//         [chain(f,g),a,b,c]; if we do !! next, then f is first applied to a,b, producing
//         [a,b,b], then g is applied to the first two elements of that since its arity is 2,
//         producing [a(b),b], and the stack will finally be [a(b),b,c].
//
[F] n(F f, F g) {
    [D+] h(D+ i) {
        // call f, remember the results.
        [D+] r = f.y(i);
        // first some results from f are the arguments to g:
        assert (is [D+] d = r[0:g.a]);
        // remaining results from f are passed back directly, with the results from g.
        return g.y(d).append(r[g.a...]);
    }
    return [F(f.a, h)];
}

//
// say: a unary function that simply returns its input, and prints 0 if it was a blank,
//      and 1 if it was a function.
// 
[D] y(D x) {
    process.write(x == b then "0" else "1");
    return [x];
}

//
// Interpreter class, which manages the stack and interprets the commands.
// Just call the r method with the individual command characters.
//
class I() {
    // The stack. The only variable in the whole program.
    variable D[] s = [];

    // a hash map of items to be pushed by commands, most build using the m function.
    // The apply command is not here, this is handled separately by the interpreter. 
    value c = H {
        '?'->b,
        '+'->m(`e`),
        '>'->m(`t`),
        '/'->m(`k`),
        '$'->m(`l`),
        '.'->m(`n`),
        '@'->m(`y`)
    };

    // Interprets one command, indicated by a character.
    // Will throw an AssertionError for unknown commands.
    shared void r(Character i) {
        if (i == '!') {
            assert (
                is F f = s[0],
                is D x = s[1]);
            // apply f on x, push the result onto a shortened version of the stack.
            s = f.o(x).append(s[2...]);
        } else {
            assert (is D d = c[i]);
            // push d on top of the stack.
            s = [d].append(s);
        }
    }
}

shared void z() {
    process.readLine()?.collect(I().r);
}

Le funzioni clone e, shift t, fork k, call l, say ye chain nusano l'ultima lettera dei nomi per la versione abbreviata, perché ciò ha dato meno collisioni. (Curiosità: la forcella è stato originariamente definito in questo modo: [Data] fork(Data a, Data b, Data c) => a == blank then [b] else [c];- quando ho rinominato blanka b, questo ha rotto, perché ora confrontato i parametri ae binvece acon il vuoto mi ha richiesto del tempo per eseguire il debug..)

La zfunzione è condivisa perché il mio IDE esegue tali funzioni: lo strumento da riga di comando può anche eseguire quelle non condivise.


Le versioni in loop lanciano effettivamente StackOverflowError ad un certo punto, finendo quindi. La JVM non ha ottimizzazione dello stack di ricorsione (o almeno nessuna che funzioni per il mio programma).
Paŭlo Ebermann,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.