Scrivi un interprete Clem


11

Clem è un linguaggio di programmazione basato su stack minimo con funzioni di prima classe. Il tuo obiettivo è quello di scrivere un interprete per il linguaggio Clem. Dovrebbe eseguire correttamente tutti gli esempi inclusi nell'implementazione di riferimento, disponibile qui .

  • Come al solito, si applicano scappatoie standard .
  • Vince la voce più piccola per numero di byte.

La lingua clem

Clem è un linguaggio di programmazione basato su stack con funzioni di prima classe. Il modo migliore per imparare Clem è eseguire l' cleminterprete senza argomenti. Si avvierà in modalità interattiva, permettendoti di giocare con i comandi disponibili. Per eseguire i programmi di esempio, digitare clem example.clmdove esempio è il nome del programma. Questo breve tutorial dovrebbe essere sufficiente per iniziare.

Esistono due classi principali di funzioni. Funzioni atomiche e funzioni composte. Le funzioni composte sono elenchi composti da altre funzioni composte e funzioni atomiche. Si noti che una funzione composta non può contenere se stessa.

Funzioni atomiche

Il primo tipo di funzione atomica è la costante . Una costante è semplicemente un valore intero. Ad esempio, -10. Quando l'interprete incontra una costante , la spinge in pila. Corri clemadesso. Digitare -10al prompt. Tu dovresti vedere

> -10
001: (-10)
>

Il valore 001descrive la posizione della funzione nello stack ed (-10) è la costante appena inserita. Ora inserisci +11al prompt. Tu dovresti vedere

> +11
002: (-10)
001: (11)
>

Si noti che (-10)è passato alla seconda posizione nella pila e (11)ora occupa la prima. Questa è la natura di uno stack! Noterai che -è anche il comando di decremento. Ogni volta -che +precedono un numero, indicano il segno di quel numero e non il comando corrispondente. Tutte le altre funzioni atomiche sono comandi . Ce ne sono 14 in totale:

@  Rotate the top three functions on the stack
#  Pop the function on top of the stack and push it twice
$  Swap the top two functions on top of the stack
%  Pop the function on top of the stack and throw it away
/  Pop a compound function. Split off the first function, push what's left, 
   then push the first function.
.  Pop two functions, concatenate them and push the result
+  Pop a function. If its a constant then increment it. Push it
-  Pop a function. If its a constant then decrement it. Push it
<  Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
>  Pop a function and print its ASCII character if its a constant
c  Pop a function and print its value if its a constant
w  Pop a function from the stack. Peek at the top of the stack. While it is
   a non-zero constant, execute the function.

Digitando un comando al prompt verrà eseguito il comando. Digitare #al prompt (il comando duplicato). Tu dovresti vedere

> #
003: (-10)
002: (11)
001: (11)
> 

Si noti che il (11) è stato duplicato. Ora digita %al prompt (il comando drop). Tu dovresti vedere

> %
002: (-10)
001: (11)
> 

Per inviare un comando allo stack, racchiuderlo semplicemente tra parentesi. Digitare (-)al prompt. Ciò spingerà l'operatore di decremento nello stack. Tu dovresti vedere

> (-)
003: (-10)
002: (11)
001: (-)
> 

Funzioni composte

È inoltre possibile racchiudere tra parentesi più funzioni atomiche per formare una funzione composta. Quando si inserisce una funzione composta al prompt, viene inserita nello stack. Digitare ($+$)al prompt. Tu dovresti vedere

> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>

Tecnicamente, tutto nello stack è una funzione composta. Tuttavia, alcune delle funzioni composte nello stack sono costituite da un'unica funzione atomica (nel qual caso, le considereremo come funzioni atomiche per motivi di praticità). Quando si manipolano le funzioni composte nello stack, il .comando (concatenazione) è spesso utile. Digita .ora. Tu dovresti vedere

> . 
003: (-10)
002: (11)
001: (- $ + $)
> 

Si noti che la prima e la seconda funzione nello stack sono state concatenate e che la seconda funzione nello stack viene prima nell'elenco risultante. Per eseguire una funzione che si trova nello stack (sia esso atomico o composto), è necessario emettere il wcomando (while). Il wcomando farà apparire la prima funzione nello stack ed eseguirla ripetutamente finché la seconda funzione nello stack è una costante diversa da zero. Prova a prevedere cosa accadrà se digitiamo w. Ora digita w. Tu dovresti vedere

> w
002: (1)
001: (0)
> 

È quello che ti aspettavi? Sono stati aggiunti i due numeri in cima alla pila e la loro somma rimane. Proviamo di nuovo. Per prima cosa lasciamo cadere lo zero e spingiamo un 10 digitando %10. Tu dovresti vedere

> %10
002: (1)
001: (10)
> 

Ora digiteremo l'intera funzione in un colpo solo, ma aggiungeremo un extra %alla fine per sbarazzarci dello zero. Digitare (-$+$)w%al prompt. Tu dovresti vedere

> (-$+$)w%
001: (11)
> 

(Nota che questo algoritmo funziona solo se la prima costante nello stack è positiva).

stringhe

Sono anche presenti stringhe. Sono principalmente zucchero sintattico, ma possono essere abbastanza utili. Quando l'interprete incontra una stringa, spinge ogni personaggio dall'ultimo al primo nella pila. Digitare %per eliminare l'11 dall'esempio precedente. Ora digita 0 10 "Hi!"il prompt. Il 0inserirà un terminatore NULL e la 10inserirà un carattere di nuova linea. Tu dovresti vedere

> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
> 

Digitare (>)wper stampare i caratteri dalla pila fino a quando non si incontra il terminatore NULL. Tu dovresti vedere

> (>)w
Hi!
001: (0)
> 

conclusioni

Speriamo che questo sia sufficiente per iniziare con l'interprete. Il design del linguaggio dovrebbe essere relativamente semplice. Fammi sapere se qualcosa è terribilmente poco chiaro :) Alcune cose sono state lasciate intenzionalmente vaghe: i valori devono essere firmati e almeno 16 bit, lo stack deve essere abbastanza grande per eseguire tutti i programmi di riferimento, ecc. Molti dettagli non sono stati scolpiti qui fuori perché una specifica del linguaggio completo sarebbe proibizionalmente grande da pubblicare (e non ne ho ancora scritto uno: P). In caso di dubbio, imitare l'implementazione di riferimento.

La pagina esolangs.org per Clem

L'implementazione di riferimento in C


Dici di non aver ancora scritto le specifiche della lingua. Immagino che tu sia il creatore della lingua?
COTO

@COTO È corretto. Ho creato la lingua.
Orby,

5
Domanda molto importante: la pronunci "klem" o "see-lem"?
Martin Ender,

4
@ MartinBüttner: "klem" :)
Orby,

2
È possibile che si desideri specificare la direzione in cui il comando @ ruota le 3 funzioni principali. (001 -> 002 -> 003 -> 001 o 003 -> 002 -> 001 -> 003)
kwokkie,

Risposte:


1

Haskell, 931 921 875

questo non è ancora completamente golfato, ma probabilmente non lo sarà mai. Tuttavia, è già più corto di tutte le altre soluzioni. Giocherò a golf più presto. Non mi va di giocare a golf più di così.

probabilmente ha alcuni bug sottili perché non ho giocato con l'implementazione di riferimento C.

questa soluzione utilizza il tipo StateT [String] IO ()per memorizzare un programma clem "eseguibile". la maggior parte del programma è un parser che analizza il "programma eseguibile".

per eseguire questo uso r "<insert clem program here>".

import Text.Parsec
import Control.Monad.State
import Control.Monad.Trans.Class
import Data.Char
'#'%(x:y)=x:x:y
'%'%(x:y)=y
'@'%(x:y:z:w)=y:z:x:w
'$'%(x:y:z)=y:x:z
'/'%((a:b):s)=[a]:b:s
'+'%(a:b)=i a(show.succ)a:b
'.'%(a:b:c)=(a++b):c
_%x=x
b=concat&between(s"(")(s")")(many$many1(noneOf"()")<|>('(':)&((++")")&b))
e=choice[s"w">>c(do p<-t;let d=h>>= \x->if x=="0"then a else u p>>d in d),m&k,s"-">>(m&(' ':)&k<|>c(o(\(a:b)->i a(show.pred)a:b))),s"c">>c(do
 d<-t
 i d(j.putStr.show)a),o&(++)&map(show.ord)&between(s"\"")(s"\"")(many$noneOf"\""),(do
 s"<"
 c$j getChar>>=m.show.ord),(do
 s">"
 c$do
 g<-t
 i g(j.putChar.chr)a),m&b,o&(%)&anyChar]
k=many1 digit
i s f g|(reads s::[(Int,String)])>[]=f$(read s::Int)|0<1=g
t=h>>=(o tail>>).c
c n=return n
a=c()
h=head&get
(&)f=fmap f
m=o.(:)
o=modify
u=(\(Right r)->r).parse(sequence_&many e)""
r=(`runStateT`[]).u
s=string
j=lift

5

Python, 1684 1281 caratteri

Hai fatto tutte le cose di base per il golf. Esegue tutti i programmi di esempio e corrisponde all'output carattere per carattere.

import sys,os,copy as C
L=len
S=[]
n=[S]
Q=lambda:S and S.pop()or 0
def P(o):
 if o:n[0].append(o)
def X():x=Q();P(x);P(C.deepcopy(x))
def W():S[-2::]=S[-1:-3:-1]
def R():a,b,c=Q(),Q(),Q();P(a);P(c);P(b)
def A(d):
 a=Q()
 if a and a[0]:a=[1,a[1]+d,lambda:P(a)]
 P(a)
def V():
 a=Q();P(a)
 if a and a[0]-1and L(a[2])>1:r=a[2].pop(0);P(r)
def T():
 b,a=Q(),Q()
 if a!=b:P([0,0,(a[2],[a])[a[0]]+(b[2],[b])[b[0]]])
 else:P(a);P(b)
def r():a=os.read(0,1);F(ord(a)if a else-1)
def q(f):
 a=Q()
 if a and a[0]:os.write(1,(chr(a[1]%256),str(a[1]))[f])
def e(f,x=0):f[2]()if f[0]+f[1]else([e(z)for z in f[2]]if x else P(f))
def w():
 a=Q()
 while a and S and S[-1][0]and S[-1][1]:e(a,1)
def Y():n[:0]=[[]]
def Z():
 x=n.pop(0)
 if x:n[0]+=([[0,0,x]],x)[L(x)+L(n)==2]
D={'%':Q,'#':X,'$':W,'@':R,'+':lambda:A(1),'-':lambda:A(-1),'/':V,'.':T,'<':r,'>':lambda:q(0),'c':lambda:q(1),'w':w,'(':Y,')':Z}
def g(c):D[c]()if L(n)<2or c in'()'else P([0,1,D[c]])
N=['']
def F(x):a=[1,x,lambda:P(a)];a[2]()
def E():
 if'-'==N[0]:g('-')
 elif N[0]:F(int(N[0]))
 N[0]=''
s=j=""
for c in open(sys.argv[1]).read()+' ':
 if j:j=c!="\n"
 elif'"'==c:E();s and map(F,map(ord,s[:0:-1]));s=(c,'')[L(s)>0]
 elif s:s+=c
 elif';'==c:E();j=1
 else:
    if'-'==c:E()
    if c in'-0123456789':N[0]+=c
    else:E();c in D and g(c)

Test :

Raccogli clemint.py , clemtest_data.py , clemtest.py e un file clembinario compilato in una directory ed esegui clemtest.py.

Expanation :

La versione più ungolf è questa . Segui insieme a quello.

Sè lo stack principale. Ogni articolo dello stack è un elenco di 3, uno di:

Constant: [1, value, f]
Atomic: [0, 1, f]
Compound: [0, 0, fs]

Per le costanti, fè una funzione che spinge la costante nello stack. Ai atmoics, fè una funzione che esegue una delle operazioni (ad esempio -, +). Per i composti, fsè un elenco di elementi.

xecesegue un oggetto. Se è una costante o un atomico, esegue solo la funzione. Se è un composto, se non si è ancora verificata una ricorsione, esegue ciascuna funzione. Quindi, l'esecuzione di (10 20 - 30)eseguirà ciascuna delle funzioni 10, 20, -e 30, lasciando 10 19 30sullo stack. Se c'è stata una ricorsione, allora spinge semplicemente la funzione composta sullo stack. Ad esempio, durante l'esecuzione (10 20 (3 4) 30), il risultato dovrebbe essere 10 20 (3 4) 30, non 10 20 3 4 30.

La nidificazione è stata un po 'complicata. Cosa fai mentre leggi (1 (2 (3 4)))? La soluzione è avere una pila di pile. Ad ogni livello di nidificazione, un nuovo stack viene spinto sulla pila di stack e tutte le operazioni di push vanno su questo stack. Inoltre, se è stato annidato, le funzioni atomiche vengono spinte anziché eseguite. Quindi, se vedi 10 20 (- 30) 40, 10viene spinto, quindi 20, viene creato un nuovo stack, -che 30viene spinto sul nuovo stack e )espelle il nuovo stack, lo trasforma in un oggetto e lo spinge sullo stack di un livello in basso. endnest()maniglie ). È stato un po 'complicato poiché c'è un caso speciale in cui è stato spinto un solo oggetto e stiamo spingendo di nuovo nello stack principale. Cioè, (10)dovrebbe spingere la costante10, non un composto con l'unica costante, perché allora -e +non funzionano. Non sono sicuro che questo sia di principio, ma è il modo in cui funziona ...

Il mio interprete è un processore carattere per carattere - non crea token - quindi numeri, stringhe e commenti erano piuttosto fastidiosi da gestire. C'è uno stack separato N, per un numero attualmente in elaborazione, e ogni volta che viene elaborato un personaggio che non è un numero, devo chiamare endnum()per vedere se devo prima completare quel numero e metterlo in pila. Se siamo in una stringa o in un commento è tenuto traccia delle variabili booleane; quando una stringa viene chiusa, spinge tutte le interiora nello stack. I numeri negativi hanno richiesto anche un trattamento speciale.

Questo è tutto per la panoramica. Il resto è stato attuando tutte le built-in, e fare in modo di fare copie profonde +, -e #.


Complimenti! Ti sei divertito? :)
Orby,

@Orby: certo che l'ho fatto! È una lingua interessante, decisamente strana. Spero di ottenere un interprete <1k. Non so cosa aspettarmi da altri invii.
Claudiu,

4

C 837

Grazie a @ceilingcat per aver trovato una versione molto migliore (e più breve)

Questo tratta tutto come semplici stringhe: tutti gli elementi dello stack sono stringhe, anche le costanti sono stringhe.

#define Q strcpy
#define F(x)bcopy(b,f,p-b);f[p-b-x]=!Q(r,p);
#define C(x,y)Q(S[s-x],S[s-y]);
#define N[9999]
#define A Q(S[s++]
#define D sprintf(S[s++],"%d"
#define G(x)}if(*f==x){
#define H(x)G(x)s--;
#define R return
#define Z(x)T(t,u,v)-1||putchar(x);H(
char S N N;s;c;T(b,f,r)char*b,*f,*r;{char*p;strtol(b+=strspn(b," "),&p,0);if(p>b){F(0)R 1;}if(c=*b==40){for(p=++b;c;)c+=(*p==40)-(*p++==41);F(1)R-1;}p++;F(0)*r*=!!*b;R 0;}*P(char*p){if(*p==34)R++p;char*r=P(p+1);D,*p);R r;}E(char*x){char*p,c N,f N,r N,t N,u N,v N;for(Q(c,x);*c;Q(c,p)){Q(t,S[s-1]);if(T(c,f,p=r))A,f);else{{G(64)C(0,1)C(1,2)C(2,3)C(3,0)G(35)A,t);G(36)C(0,2)C(2,1)C(1,0)H(37)H(47)T(t,u,v);*v&&A,v);A,u);H(46)strcat(strcat(S[s-1]," "),t);H(43)D,atoi(t)+1);H(45)D,atoi(t)-1);G(60)D,getchar());H(62)Z(atoi(u))99)Z(*u)119)for(Q(u,t);atoi(S[s-1]);)E(u);G(34)p=P(p);}}}}

Provalo online!

Una versione meno golfata del mio originale (a differenza della versione golfata questa stampa lo stack quando termina se non è vuota e accetta un parametro -e in modo da poter specificare lo script sulla riga di comando invece di leggere da un file):

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define FIRST_REST(x) memcpy(first, b, p - b); first[p - b - x] = '\0'; strcpy(rest, p);
#define COPY(dest,src) strcpy(stack[size + dest], stack[size + src]);
char stack[9999][9999]; int size = 0;
int token(char *b, char *first, char *rest)
{
    while (*b == 32) b++;
    char *p; int x = strtol(b, &p, 0);
    if (p > b) { FIRST_REST(0) return 1; }
    if (*b == '(') { int c = 1; for (p = ++b; c; ++p) c += (*p == '(') - (*p == ')'); FIRST_REST(1) return -1; }
    p++; FIRST_REST(0) if (!*b) *rest = '\0'; return 0;
}
char *push(char *pointer)
{
    if (*pointer == '\"') return pointer+1;
    char *result = push(pointer+1);
    sprintf(stack[size++], "%d", *pointer);
    return result;
}
void eval(char *x)
{
    char program[9999], first[9999], rest[9999], tos[9999], tmp1[9999], tmp2[9999];
    char *pointer;
    for (strcpy(program, x); *program; strcpy(program, pointer))
    {
        *stack[size] = '\0';
        strcpy(tos, stack[size-1]);
        if (token(program, first, rest))
        {
            pointer = rest;
            strcpy(stack[size++], first);
        }
        else
        {
            pointer = rest;
            if (*first == '@'){
                COPY(0, -1) COPY(-1, -2) COPY(-2, -3) COPY(-3, 0) }
            if (*first == '#')
                strcpy(stack[size++], tos);
            if (*first == '$'){
                COPY(0, -2) COPY(-2, -1) COPY(-1, 0) }
            if (*first == '%')
                size--;
            if (*first == '/'){
                size--; token(tos, tmp1, tmp2); if (*tmp2) strcpy(stack[size++], tmp2); strcpy(stack[size++], tmp1); }
            if (*first == '.'){
                size--; strcat(stack[size - 1], " "); strcat(stack[size - 1], tos); }
            if (*first == '+'){
                size--; sprintf(stack[size++], "%d", atoi(tos) + 1); }
            if (*first == '-'){
                size--; sprintf(stack[size++], "%d", atoi(tos) - 1); }
            if (*first == '<')
                sprintf(stack[size++], "%d", getchar());
            if (*first == '>'){
                size--; if (token(tos, tmp1, tmp2) == 1) putchar(atoi(tmp1)); }
            if (*first == 'c'){
                size--; if (token(tos, tmp1, tmp2) == 1) printf("%s", tmp1); }
            if (*first == 'w'){
                size--; strcpy(tmp1, tos); while (atoi(stack[size - 1])) eval(tmp1); }
            if (*first == '\"')
                pointer=push(pointer);
        }
    }
}
int main(int argc, char **argv)
{
    char program[9999] = "";
    int i = 0, comment = 0, quote = 0, space = 0;
    if (!strcmp(argv[1], "-e"))
        strcpy(program, argv[2]);
    else
    {
        FILE* f = fopen(argv[1], "r");
        for (;;) {
            char ch = fgetc(f);
            if (ch < 0) break;
            if (!quote) {
                if (ch == '\n') comment = 0;
                if (ch == ';') comment = 1;
                if (comment) continue;
                if (ch <= ' ') { ch = ' '; if (space++) continue; }
                else space = 0;
            }
            if (ch == '\"') quote = 1 - quote;
            program[i++] = ch;
        }
        fclose(f);
    }
    eval(program);
    for (int i = 0; i < size; i++) printf("%03d: (%s)\r\n",size-i,stack[i]);
    return 0;
}

Bello! Impressionante, hai battuto la soluzione Python in C. Devo caricare la mia versione più corta, sono riuscito a radere via 60 byte o giù di lì .. Mi chiedo ancora se c'è un approccio diverso che porterebbe molto meno di 1000 caratteri
Claudiu,

@Claudiu L'ho pensato anch'io, ma non sono riuscito a capire come.
Jerry Jeremiah,
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.