Écrire un interprète Clem


11

Clem est un langage de programmation basé sur une pile minimale comportant des fonctions de première classe. Votre objectif est d'écrire un interprète pour la langue Clem. Il devrait exécuter correctement tous les exemples inclus dans l'implémentation de référence, qui est disponible ici .

Le langage Clem

Clem est un langage de programmation basé sur la pile avec des fonctions de première classe. La meilleure façon d'apprendre Clem est d'exécuter l' cleminterpréteur sans argument. Il démarrera en mode interactif, vous permettant de jouer avec les commandes disponibles. Pour exécuter les exemples de programmes, tapez clem example.clmoù exemple est le nom du programme. Ce bref didacticiel devrait suffire à vous aider à démarrer.

Il existe deux principales classes de fonctions. Fonctions atomiques et fonctions composées. Les fonctions composées sont des listes composées d'autres fonctions composées et de fonctions atomiques. Notez qu'une fonction composée ne peut pas se contenir.

Fonctions atomiques

Le premier type de fonction atomique est la constante . Une constante est simplement une valeur entière. Par exemple, -10. Lorsque l'interpréteur rencontre une constante , il la pousse vers la pile. Courez clemmaintenant. Tapez -10à l'invite. Tu devrais voir

> -10
001: (-10)
>

La valeur 001décrit la position de la fonction dans la pile et (-10) est la constante que vous venez de saisir. Entrez maintenant +11à l'invite. Tu devrais voir

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

Notez que ce dernier (-10)est passé à la deuxième position de la pile et (11)occupe maintenant la première. C'est la nature d'une pile! Vous remarquerez que -c'est également la commande de décrémentation. Chaque fois -ou +précédant un nombre, ils désignent le signe de ce nombre et non la commande correspondante. Toutes les autres fonctions atomiques sont des commandes . Il y en a 14 au total:

@  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.

La saisie d'une commande à l'invite exécutera la commande. Tapez #à l'invite (la commande en double). Tu devrais voir

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

Notez que le (11) a été dupliqué. Tapez maintenant %à l'invite (la commande drop). Tu devrais voir

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

Pour envoyer une commande à la pile, mettez-la simplement entre parenthèses. Tapez (-)à l'invite. Cela poussera l'opérateur de décrémentation vers la pile. Tu devrais voir

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

Fonctions composées

Vous pouvez également mettre plusieurs fonctions atomiques entre parenthèses pour former une fonction composée. Lorsque vous entrez une fonction composée à l'invite, elle est poussée dans la pile. Tapez ($+$)à l'invite. Tu devrais voir

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

Techniquement, tout sur la pile est une fonction composée. Cependant, certaines des fonctions composées de la pile consistent en une seule fonction atomique (dans ce cas, nous les considérerons comme des fonctions atomiques pour des raisons de commodité). Lors de la manipulation de fonctions composées sur la pile, la .commande (concaténation) est souvent utile. Tapez .maintenant. Tu devrais voir

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

Notez que les première et deuxième fonctions de la pile ont été concaténées et que la deuxième fonction de la pile vient en premier dans la liste résultante. Pour exécuter une fonction qui se trouve sur la pile (qu'elle soit atomique ou composée), nous devons émettre la wcommande (while). La wcommande fera apparaître la première fonction sur la pile et l'exécutera de manière répétée tant que la deuxième fonction sur la pile est une constante non nulle. Essayez de prédire ce qui se passera si nous tapons w. Maintenant, tapez w. Tu devrais voir

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

C'est bien ce que vous attendiez? Les deux numéros assis au sommet de la pile ont été ajoutés et leur somme reste. Essayons encore. Nous allons d'abord supprimer le zéro et pousser un 10 en tapant %10. Tu devrais voir

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

Maintenant, nous allons taper la fonction entière en une seule fois, mais nous ajouterons un supplément %à la fin pour se débarrasser du zéro. Tapez (-$+$)w%à l'invite. Tu devrais voir

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

(Notez que cet algorithme ne fonctionne que si la première constante de la pile est positive).

Cordes

Des cordes sont également présentes. Ils sont principalement du sucre syntaxique, mais peuvent être très utiles. Lorsque l'interpréteur rencontre une chaîne, il pousse chaque caractère du dernier au premier sur la pile. Tapez %pour supprimer le 11 de l'exemple précédent. Maintenant, tapez 0 10 "Hi!"à l'invite. Le 0va insérer un terminateur NULL et le 10va insérer un caractère de nouvelle ligne. Tu devrais voir

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

Tapez (>)wpour imprimer les caractères de la pile jusqu'à ce que nous rencontrions le terminateur NULL. Tu devrais voir

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

Conclusions

J'espère que cela devrait suffire pour vous aider à démarrer avec l'interprète. La conception du langage doit être relativement simple. Faites-moi savoir si quelque chose n'est pas très clair :) Quelques choses ont été laissées intentionnellement vagues: les valeurs doivent être signées et au moins 16 bits, la pile doit être suffisamment grande pour exécuter tous les programmes de référence, etc. De nombreux détails n'ont pas été gravés ici parce qu'une spécification complète du langage serait prohibitive à publier (et je n'en ai pas encore écrit: P). En cas de doute, imitez l'implémentation de référence.

La page esolangs.org pour Clem

L'implémentation de référence en C


Vous dites que vous n'avez pas encore écrit la spécification de langue. Je suppose que vous êtes à l'origine de la langue?
COTO du

@COTO C'est exact. J'ai créé la langue.
Orby

5
Question très importante: prononcez-vous "klem" ou "see-lem"?
Martin Ender

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

2
Vous souhaiterez peut-être spécifier la direction dans laquelle la commande @ fait pivoter les 3 fonctions supérieures. (001 -> 002 -> 003 -> 001, ou 003 -> 002 -> 001 -> 003)
kwokkie

Réponses:


1

Haskell, 931 921 875

ce n'est pas encore complètement joué mais ce ne sera probablement jamais le cas. Pourtant, il est déjà plus court que toutes les autres solutions. Je jouerai au golf plus bientôt. Je n'ai pas envie de jouer au golf plus que ça.

a probablement quelques bugs subtils car je n'ai pas joué avec l'implémentation de référence C.

cette solution utilise le type StateT [String] IO ()pour stocker un programme clem "exécutable". la plupart du programme est un analyseur qui analyse le "programme exécutable".

afin d'exécuter cette utilisation 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 caractères

Vous avez fait tout le golf de base. Il exécute tous les exemples de programmes et correspond à la sortie caractère par caractère.

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 :

Rassemblez clemint.py , clemtest_data.py , clemtest.py et un clembinaire compilé dans un répertoire et exécutez clemtest.py.

Expansion :

La version la plus non golfée est celle-ci . Suivez avec celui-là.

Sest la pile principale. Chaque élément de la pile est une liste de 3, parmi:

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

Pour les constantes, fest une fonction qui pousse la constante sur la pile. Pour l'atmosphère, fest une fonction qui exécute l'une des opérations (par exemple -, +). Pour les composés, fsest une liste d'éléments.

xecexécute un élément. S'il s'agit d'une constante ou d'un atomique, il exécute simplement la fonction. S'il s'agit d'un composé, s'il n'y a pas encore eu de récursivité, il exécute chaque fonction. Ainsi , l' exécution (10 20 - 30)exécutera chacune des fonctions 10, 20, -et 30, laissant 10 19 30sur la pile. S'il y a eu récursivité, il pousse simplement la fonction composée sur la pile. Par exemple, lors de l'exécution (10 20 (3 4) 30), le résultat devrait être 10 20 (3 4) 30non 10 20 3 4 30.

L'imbrication était un peu délicate. Que faites-vous en lisant (1 (2 (3 4)))? La solution est d'avoir une pile de piles. À chaque niveau d'imbrication, une nouvelle pile est poussée sur la pile de piles, et toutes les opérations de poussée vont sur cette pile. De plus, s'il y a eu imbrication, les fonctions atomiques sont poussées au lieu d'être exécutées. Donc, si vous voyez 10 20 (- 30) 40, 10est poussé, alors 20, alors une nouvelle pile est créée, -et 30est poussée sur la nouvelle pile, et )saute la nouvelle pile, la transforme en élément et la pousse sur la pile un niveau plus bas. endnest()poignées ). C'était un peu délicat car il y a un cas spécial où un seul élément a été poussé et nous repoussons sur la pile principale. Autrement dit, (10)devrait pousser la constante10, pas un composite avec une constante, car alors -et +ne fonctionne pas. Je ne sais pas si c'est fondé sur des principes, mais c'est la façon dont cela fonctionne ...

Mon interprète est un processeur caractère par caractère - il ne crée pas de jetons - donc les nombres, les chaînes et les commentaires étaient quelque peu ennuyeux à gérer. Il y a une pile distincte N, pour un numéro en cours de traitement, et chaque fois qu'un caractère qui n'est pas un numéro est traité, je dois appeler endnum()pour voir si je dois d'abord compléter ce numéro et le mettre sur la pile. Que nous soyons dans une chaîne ou un commentaire est gardé par des variables booléennes; quand une chaîne est fermée, elle pousse tous les entrailles de la pile. Les nombres négatifs ont également nécessité une manipulation spéciale.

C'est à peu près tout pour l'aperçu. Le reste a mis en œuvre tous les Encastrements et en veillant à faire des copies en profondeur +, -et #.


Gloire! T'es-tu amusé? :)
Orby

@Orby: Bien sûr! C'est une langue intéressante, certainement étrange. J'espère pouvoir trouver un interprète <1k. Je ne sais pas à quoi s'attendre des autres soumissions.
Claudiu

4

C 837

Merci à @ceilingcat d'avoir trouvé une version bien meilleure (et plus courte)

Cela traite tout comme de simples chaînes - tous les éléments de la pile sont des chaînes, même les constantes sont des chaînes.

#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);}}}}

Essayez-le en ligne!

Une version moins golfée de mon original (contrairement à la version golfée, celle-ci imprime la pile quand elle se termine si elle n'est pas vide et prend un paramètre -e pour que vous puissiez spécifier le script sur la ligne de commande au lieu de lire dans un fichier):

#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;
}

Agréable! Impressionnant que vous ayez battu la solution Python en C. Je dois télécharger ma version plus courte, j'ai réussi à raser environ 60 octets .. Je me demande toujours s'il existe une approche différente qui donnerait bien moins de 1000 caractères
Claudiu

@Claudiu Je le pensais aussi - mais je n'arrivais pas à comprendre comment.
Jerry Jeremiah du
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.