Напишіть перекладача Clem


11

Clem - це мінімальна мова програмування на основі стека, що має функції першого класу. Ваша мета - написати перекладача для мови Clem. Він повинен належним чином виконати всі приклади, включені до посилання на реалізацію, яка доступна тут .

Мова Клема

Clem - це мова програмування на основі стека з функціями першого класу. Найкращий спосіб навчитися Клему - запустити clemперекладача без аргументів. Він запуститься в інтерактивному режимі, що дозволяє грати з доступними командами. Для запуску прикладних програм введіть, clem example.clmде приклад - ім'я програми. Цього короткого підручника має бути достатньо для початку роботи.

Є два основні класи функцій. Атомні функції та складові функції. Складені функції - це списки, що складаються з інших складних функцій та атомних функцій. Зауважте, що складна функція не може містити себе.

Атомні функції

Перший тип атомної функції - константа . Константа це просто ціле значення. Наприклад, -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) дублюється. Тепер введіть %підказку (команда drop). Ви повинні побачити

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

Щоб натиснути команду на стек, просто укладіть її в круглі дужки. Введіть (-)підказку. Це підштовхне оператора decrement до стеку. Ви повинні побачити

> (-)
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 для Клема

Довідкова реалізація в С


Ви кажете, що ще не написали мовної специфікації. Я вважаю, що ви творець мови?
COTO

@COTO Це правильно. Я створив мову.
Орбі

5
Дуже важливе запитання: ви вимовляєте це "клем" чи "бачити-лем"?
Мартін Ендер

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

2
Ви можете вказати напрямок, в якому команда @ обертає 3 верхні функції. (001 -> 002 -> 003 -> 001, або 003 -> 002 -> 001 -> 003)
kwokkie

Відповіді:


1

Хаскелл, 931 921 875

це ще не повністю гольф, але це, мабуть, ніколи не буде. І все-таки він вже коротший, ніж усі інші рішення. Я незабаром пограю в гольф. Мені більше не подобається гольфу.

напевно, є кілька тонких помилок, тому що я не грав з реалізацією посилань на С.

це рішення використовує тип StateT [String] IO ()для зберігання програми "clenable". Більшість програми - це аналізатор, який аналізує "програму, яку можна виконати".

щоб запустити це використання 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: Я впевнено! Це цікава мова, безумовно, дивна. Я сподіваюся, що зможу отримати перекладача, який становить <1k. Не впевнений, чого очікувати від інших робіт.
Клавдіу

4

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

Спробуйте в Інтернеті!

Менш гольф-версія мого оригіналу (на відміну від версії golfed, ця друкує стек, коли він закінчується, якщо він не порожній і приймає параметр -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 Я теж думав, але я не міг зрозуміти, як.
Джеррі Єремія
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.