Escreva um intérprete Clem


11

Clem é uma linguagem de programação mínima baseada em pilha, com funções de primeira classe. Seu objetivo é escrever um intérprete para a linguagem Clem. Ele deve executar corretamente todos os exemplos incluídos na implementação de referência, disponível aqui .

A linguagem Clem

Clem é uma linguagem de programação baseada em pilha com funções de primeira classe. A melhor maneira de aprender o Clem é executar o clemintérprete sem argumentos. Ele começará no modo interativo, permitindo que você jogue com os comandos disponíveis. Para executar os programas de exemplo, digite clem example.clmonde exemplo é o nome do programa. Este breve tutorial deve ser suficiente para você começar.

Existem duas classes principais de funções. Funções atômicas e funções compostas. Funções compostas são listas compostas de outras funções compostas e funções atômicas. Observe que uma função composta não pode se conter.

Funções atômicas

O primeiro tipo de função atômica é a constante . Uma constante é simplesmente um valor inteiro. Por exemplo, -10. Quando o intérprete encontra uma constante , ele o empurra para a pilha. Corra clemagora. Digite -10no prompt. Você deveria ver

> -10
001: (-10)
>

O valor 001descreve a posição da função na pilha e (-10) é a constante que você acabou de inserir. Agora entre +11no prompt. Você deveria ver

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

Observe que (-10)foi para a segunda posição na pilha e (11)agora ocupa a primeira. Essa é a natureza de uma pilha! Você notará que -também é o comando de decremento. Sempre -ou +precedendo um número, eles indicam o sinal desse número e não o comando correspondente. Todas as outras funções atômicas são comandos . Existem 14 no 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.

Digitar um comando no prompt executará o comando. Digite #no prompt (o comando duplicado). Você deveria ver

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

Observe que o (11) foi duplicado. Agora digite %no prompt (o comando drop). Você deveria ver

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

Para enviar um comando para a pilha, basta colocá-lo entre parênteses. Digite (-)no prompt. Isso empurrará o operador de decremento para a pilha. Você deveria ver

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

Funções compostas

Você também pode incluir várias funções atômicas entre parênteses para formar uma função composta. Quando você insere uma função composta no prompt, ela é enviada para a pilha. Digite ($+$)no prompt. Você deveria ver

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

Tecnicamente, tudo na pilha é uma função composta. No entanto, algumas das funções compostas na pilha consistem em uma única função atômica (nesse caso, as consideraremos como funções atômicas por uma questão de conveniência). Ao manipular funções compostas na pilha, o .comando (concatenação) é frequentemente útil. Digite .agora. Você deveria ver

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

Observe que a primeira e a segunda funções na pilha foram concatenadas e que a segunda função na pilha vem em primeiro lugar na lista resultante. Para executar uma função que está na pilha (seja atômica ou composta), devemos emitir o wcomando (while). O wcomando exibirá a primeira função na pilha e a executará repetidamente, desde que a segunda função na pilha seja uma constante diferente de zero. Tente prever o que acontecerá se digitarmos w. Agora digite w. Você deveria ver

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

É isso que você esperava? Os dois números sentados no topo da pilha foram adicionados e sua soma permanece. Vamos tentar novamente. Primeiro, vamos largar o zero e pressionar 10, digitando %10. Você deveria ver

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

Agora digitaremos a função inteira de uma só vez, mas adicionaremos um extra %no final para eliminar o zero. Digite (-$+$)w%no prompt. Você deveria ver

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

(Observe que esse algoritmo funciona apenas se a primeira constante na pilha for positiva).

Cordas

Strings também estão presentes. Eles são principalmente açúcar sintático, mas podem ser bastante úteis. Quando o intérprete encontra uma sequência, ele coloca cada caractere do último ao primeiro na pilha. Digite %para descartar o 11 do exemplo anterior. Agora, digite 0 10 "Hi!"no prompt. Ele 0inserirá um terminador NULL e o 10caractere de nova linha. Você deveria ver

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

Digite (>)wpara imprimir caracteres da pilha até encontrarmos o terminador NULL. Você deveria ver

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

Conclusões

Espero que isso seja suficiente para você começar com o intérprete. O design da linguagem deve ser relativamente direto. Deixe-me saber se algo está terrivelmente claro :) Algumas coisas foram deixadas intencionalmente vagas: os valores devem ser assinados e pelo menos 16 bits, a pilha deve ser grande o suficiente para executar todos os programas de referência, etc. Muitos detalhes não foram gravados aqui porque uma especificação de linguagem completa seria proibitivamente grande para postar (e ainda não escrevi uma: P). Em caso de dúvida, imite a implementação de referência.

A página esolangs.org de Clem

A implementação de referência em C


Você diz que ainda não escreveu a especificação da linguagem. Entendo que você é o criador do idioma?
COTO

@ COTO Isso está correto. Eu criei o idioma.
Orby

5
Pergunta muito importante: você o pronuncia "klem" ou "see-lem"?
Martin Ender

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

2
Você pode especificar a direção na qual o comando @ gira as três principais funções. (001 -> 002 -> 003 -> 001 ou 003 -> 002 -> 001 -> 003)
kwokkie

Respostas:


1

Haskell, 931 921 875

isso ainda não foi totalmente jogado, mas provavelmente nunca será. Ainda assim, já é mais curto que todas as outras soluções. Vou jogar isso mais em breve. Não me apetece jogar mais do que isso.

provavelmente tem alguns erros sutis porque não brinquei com a implementação de referência C.

esta solução usa o tipo StateT [String] IO ()para armazenar um programa clem "executável". a maioria do programa é um analisador que analisa o "programa executável".

para executar esse 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 caracteres

Temos todo o material básico de golfe feito. Ele executa todos os programas de exemplo e corresponde à saída caractere por caractere.

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)

Teste :

Reúna clemint.py , clemtest_data.py , clemtest.py e um clembinário compilado em um diretório e execute clemtest.py.

Expansão :

A versão mais não destruída é essa . Siga junto com esse.

Sé a pilha principal. Cada item da pilha é uma lista de três, um dos seguintes:

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

Para as constantes, fé uma função que empurra a constante para a pilha. Para os atmoics, fé uma função que executa uma das operações (por exemplo -, +). Para os compostos, fsé uma lista de itens.

xecexecuta um item. Se é uma constante ou um atômico, apenas executa a função. Se é um composto, se ainda não houve recursão, ele executa cada função. Assim, a execução (10 20 - 30)irá executar cada uma das funções 10, 20, -e 30, deixando 10 19 30na pilha. Se houve recursão, ela simplesmente envia a função composta para a pilha. Por exemplo, ao executar (10 20 (3 4) 30), o resultado deve ser10 20 (3 4) 30 , não 10 20 3 4 30.

Aninhar era um pouco complicado. O que você faz durante a leitura (1 (2 (3 4)))? A solução é ter uma pilha de pilhas. Em cada nível de aninhamento, uma nova pilha é empurrada na pilha de pilhas e todas as operações de envio vão para essa pilha. Além disso, se houver um aninhamento, as funções atômicas são pressionadas em vez de executadas. Portanto, se você vê 10 20 (- 30) 40, 10é pressionado, então 20, uma nova pilha é criada, -e 30é empurrada para a nova pilha, e )sai da nova pilha, a transforma em um item e a empurra para a pilha um nível abaixo. , não um composto com a constante, porque então e não funciona. Não tenho certeza se isso é baseado em princípios, mas é assim que funciona ...endnest()alças ). Foi um pouco complicado, pois existe um caso especial em que apenas um item foi enviado e estamos retornando à pilha principal. Ou seja, (10)deve empurrar a constante10-+

Meu intérprete é um processador caractere por caractere - ele não cria tokens -, portanto, números, strings e comentários foram um pouco irritantes de se lidar. Há uma pilha separada N, para um número que está sendo processado no momento, e sempre que um caractere que não é um número é processado, tenho que ligar endnum()para ver se devo primeiro completar esse número e colocá-lo na pilha. Quer estejamos em uma string ou em um comentário, é mantido o controle por variáveis ​​booleanas; quando uma corda é fechada, empurra todas as entranhas da pilha. Os números negativos também exigiam um tratamento especial.

É sobre isso para a visão geral. O resto estava implementando todas as built-ins, e certificando-se de fazer cópias de profundidade de +, -e #.


Parabéns! Você se divertiu? :)
Orby

@ Orby: Com certeza! É uma linguagem interessante, definitivamente estranha. Espero conseguir um intérprete <1k. Não sabe o que esperar de outros envios.
Claudiu

4

C 837

Obrigado ao @ceilingcat por encontrar uma versão muito melhor (e mais curta)

Isso trata tudo como strings simples - todos os itens da pilha são strings, mesmo as constantes são strings.

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

Experimente online!

Uma versão com menos golfe do meu original (ao contrário da versão com golfe, essa imprime a pilha quando termina se não estiver vazia e usa um parâmetro -e para que você possa especificar o script na linha de comando em vez de ler em um arquivo):

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

Agradável! Impressionante você bater a solução Python em C. Eu tenho que carregar a minha versão mais curta, consegui raspar 60 bytes ou assim .. Eu ainda me pergunto se há uma abordagem diferente, que renderia maneira menos de 1000 caracteres
Claudiu

@ Claudiu Eu também pensava assim - mas não conseguia descobrir como.
Jerry Jeremiah
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.