Написать переводчика Clem

11

Clem - это минимальный стековый язык программирования с функциями первого класса. Ваша цель - написать переводчика для языка Clem. Следует правильно выполнить все примеры, включенные в справочную реализацию, которая доступна здесь .

Клемский язык

Clem - это стековый язык программирования с первоклассными функциями. Лучший способ выучить Клема - запустить clemинтерпретатор без аргументов. Он начнется в интерактивном режиме, что позволит вам играть с доступными командами. Чтобы запустить примеры программ, введите clem example.clmгде example - название программы. Этого краткого урока должно быть достаточно, чтобы вы начали.

Есть два основных класса функций. Атомарные функции и составные функции. Составные функции - это списки, составленные из других составных функций и атомарных функций. Обратите внимание, что составная функция не может содержать себя.

Атомные функции

Первый тип атомарной функции является константой . Константа это просто целое значение. Например, -10. Когда интерпретатор встречает константу , он помещает ее в стек. Беги clemсейчас. Введите -10в командной строке. Тебе следует увидеть

> -10
001: (-10)
>

Значение 001описывает положение функции в стеке и (-10) является константой, которую вы только что ввели. Теперь введите +11в подсказке. Тебе следует увидеть

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

Обратите внимание, что (-10)переместился на вторую позицию в стеке и (11)теперь занимает первую. Это природа стека! Вы заметите, что -это также команда декремента. Всякий раз, когда -или +перед числом, они обозначают знак этого числа, а не соответствующую команду. Все остальные элементарные функции являются командами . Всего 14:

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

Ввод команды в командной строке выполнит команду. Введите #в командной строке (дублирующая команда). Тебе следует увидеть

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

Обратите внимание, что (11) было продублировано. Теперь введите %в командной строке (команда сброса). Тебе следует увидеть

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

Чтобы поместить команду в стек, просто заключите ее в скобки. Введите (-)в командной строке. Это перенесет оператор декремента в стек. Тебе следует увидеть

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

Составные функции

Вы также можете заключить несколько атомарных функций в круглые скобки, чтобы сформировать составную функцию. Когда вы вводите составную функцию в приглашении, она помещается в стек. Введите ($+$)в командной строке. Тебе следует увидеть

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

Технически все в стеке является составной функцией. Однако некоторые составные функции в стеке состоят из одной атомарной функции (в этом случае мы будем считать их атомарными функциями для удобства). При манипулировании составными функциями в стеке .команда (сцепление) часто бывает полезна. Введите .сейчас. Тебе следует увидеть

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

Обратите внимание, что первая и вторая функции в стеке были объединены, и что вторая функция в стеке стоит первой в результирующем списке. Чтобы выполнить функцию, находящуюся в стеке (атомарную или составную), мы должны выполнить wкоманду (while). Команда wвставляет первую функцию в стек и выполняет ее многократно, если вторая функция в стеке является ненулевой константой. Попробуйте предсказать, что произойдет, если мы введем w. Теперь введите w. Тебе следует увидеть

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

Это то, что вы ожидали? Два числа, находящиеся на вершине стека, были добавлены, и их сумма остается. Давайте попробуем это снова. Сначала мы опустим ноль и нажмем 10, набрав %10. Тебе следует увидеть

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

Теперь мы напечатаем всю функцию за один раз, но %в конце добавим дополнительную, чтобы избавиться от нуля. Введите (-$+$)w%в командной строке. Тебе следует увидеть

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

(Обратите внимание, что этот алгоритм работает, только если первая константа в стеке положительна).

Струны

Строки также присутствуют. Они в основном синтаксические сахара, но могут быть весьма полезными. Когда интерпретатор встречает строку, он помещает каждый символ от последнего к первому в стек. Тип, %чтобы отбросить 11 из предыдущего примера. Теперь введите 0 10 "Hi!"приглашение. 0Будет вставить NULL терминатора и 10вставляет символ новой строки. Тебе следует увидеть

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

Печатайте (>)wсимволы из стека, пока мы не встретим терминатор NULL. Тебе следует увидеть

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

Выводы

Надеюсь, этого будет достаточно, чтобы вы начали работать с переводчиком. Дизайн языка должен быть относительно простым. Дайте мне знать, если что-то ужасно неясно :) Несколько вещей были намеренно оставлены неопределенными: значения должны быть подписаны и не менее 16 бит, стек должен быть достаточно большим, чтобы запускать все справочные программы и т. Д. Многие детали не были вырезаны здесь, потому что полная спецификация языка была бы слишком большой для публикации (а я еще не написал: P). Если есть сомнения, имитируйте эталонную реализацию.

Страница esolangs.org для Clem

Эталонная реализация в C

Orby
источник
Вы говорите, что еще не написали спецификацию языка. Я так понимаю, что вы создатель языка?
COTO
@COTO Это правильно. Я создал язык.
Орби
5
Очень важный вопрос: вы произносите это "klem" или "see-lem"?
Мартин Эндер
4
@ MartinBüttner: "Клем" :)
Орби
2
Возможно, вы захотите указать направление, в котором команда @ вращает 3 верхние функции. (001 -> 002 -> 003 -> 001 или 003 -> 002 -> 001 -> 003)
kwokkie

Ответы:

1

Хаскелл, 931 921 875

это еще не полностью игра в гольф, но это, вероятно, никогда не будет. Тем не менее, он уже короче всех других решений. Я буду играть в гольф в ближайшее время. Мне не хочется играть в гольф больше, чем это.

вероятно, есть несколько тонких ошибок, потому что я не играл с эталонной реализацией Си.

это решение использует тип StateT [String] IO ()для хранения "работающей" программы clem. Большинство программ - это синтаксический анализатор, который анализирует «работающую программу».

для того, чтобы запустить это использование 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

Питон, 1684 1281 символов

Есть все основные вещи в гольф сделано. Он запускает все примеры программ и соответствует выводимому символу за символом.

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)

Тестирование :

Соберите clemint.py , clemtest_data.py , clemtest.py и скомпилированный clemдвоичный файл в каталог и запустите clemtest.py.

Расширение :

Самая безудержная версия - эта . Следуйте вместе с этим.

Sосновной стек Каждый элемент стека состоит из 3-х списков, один из:

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

Для констант, fэто функция, которая помещает константу в стек. Для атмосферы, fэто функция, которая выполняет одну из операций (например -, +). Для соединений, fsэто список предметов.

xecвыполняет элемент. Если это константа или атом, он просто выполняет функцию. Если это соединение, если рекурсии еще не было, выполняется каждая функция. Таким образом , выполнение (10 20 - 30)будет выполнять каждую из функций 10, 20, -и 30, оставляя 10 19 30в стеке. Если была рекурсия, то она просто помещает составную функцию в стек. Например, при выполнении (10 20 (3 4) 30), результат должен быть 10 20 (3 4) 30, а не10 20 3 4 30 .

Вложенность была немного хитрой. Что вы делаете во время чтения (1 (2 (3 4)))? Решение состоит в том, чтобы иметь стек стеков. На каждом уровне вложенности новый стек помещается в стек, и все операции переноса выполняются в этом стеке. Далее, если было вложение, то атомарные функции выдвигаются вместо выполнения. Так что если вы видите 10 20 (- 30) 40, 10толкается, затем 20, затем новый стек создается, -и 30помещаются в новый стек, и )выскакивает с нового стека, превращает его в объект и помещает его в стек на один уровень вниз. endnest()ручки ). Это было немного сложно, так как есть особый случай, когда только один элемент был перемещен, и мы возвращаемся в основной стек. То есть (10)должен давить постоянную10, а не композит с одной константой, потому что тогда -и +не сработает. Я не уверен, что это принципиально, но так оно и есть ...

Мой интерпретатор является посимвольным процессором - он не создает токены - поэтому с цифрами, строками и комментариями было несколько неприятно иметь дело. Существует отдельный стек, Nдля текущего обрабатываемого числа, и каждый раз, когда обрабатывается символ, который не является числом, я должен позвонить, endnum()чтобы узнать, должен ли я сначала заполнить это число и поместить его в стек. Будь мы в строке или комментарии отслеживаются логическими переменными; когда строка закрыта, она выталкивает все внутренности из стека. Отрицательные числа также требуют особой обработки.

Вот и все для обзора. Остальные реализовывались все встроенные модули, и убедившись , что делать глубокие копии в +, -и #.

Клаудиу
источник
Престижность! Тебе было весело? :)
Orby
@Orby: Я уверен, что сделал! Это интересный язык, определенно странный. Я надеюсь, что смогу получить переводчика <1k. Не уверен, что ожидать от других представлений.
Клавдиу
4

С 837

Спасибо @ceilingcat за то, что нашли намного лучшую (и более короткую) версию

Это рассматривает все как простые строки - все элементы стека являются строками, даже константы являются строками.

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

Попробуйте онлайн!

Версия моего оригинала с меньшим количеством полей для гольфа (в отличие от версии для гольфа, эта версия печатает стек, когда он заканчивается, если он не пустой, и принимает параметр -e, чтобы вы могли указать сценарий в командной строке вместо чтения из файла):

#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;
}
Джерри Иеремия
источник
Ницца! Впечатляет, что вы опередили решение Python на C. Мне нужно загрузить свою более короткую версию, мне удалось сбрить примерно 60 байт или около того ... Я все еще задаюсь вопросом, есть ли другой подход, который бы дал менее 1000 символов
Claudiu
@Claudiu Я тоже так думал - но я не мог понять, как.
Джерри Иеремия