Napisz interpreter ~ ATH

12

Popularny komiks Homestuck korzysta z języka programowania zwanego ~ATHniszczeniem wszechświatów. Podczas gdy wyzwaniem dla golfa kodowego nie jest napisanie programu anihilującego nasze istnienie, zniszczymy nieco więcej oswojonych (choć mniej interesujących) bytów: zmiennych

~ATH(wymawiane „aż do śmierci”, zwróć uwagę, jak ~athto jest „tylda ath”), tworząc zmienną o nazwie THIS, wykonując polecenie EXECUTEi kończąc program THIS.DIE(). Strona wiki dotycząca użycia języka w Homestuck znajduje się tutaj . Celem tego wyzwania będzie stworzenie ~ATHtłumacza.

Ze względu na wyzwanie zamierzam stworzyć pewne szczegóły ~ATH, które tak naprawdę nie istnieją, ale uczynię je (w pewnym stopniu) użytecznymi.

  • Język będzie działał tylko z liczbami całkowitymi zadeklarowanymi za pomocą import <variable name>;. Zmienna zostanie automatycznie ustawiona na wartość 0. Można importować tylko jedną zmienną na raz.
  • Zmienną xmożna skopiować, pisząc bifurcate x[y,z];, co spowoduje jej usunięcie xi zastąpienie identycznymi zmiennymi yoraz z. Zauważ, że nie można utworzyć zmiennej o tej samej nazwie co usunięta. Zasadniczo nazwa zmiennej jest zmieniana, a następnie tworzona jest kopia zmiennej o innej nazwie. To wydaje się głupią cechą, ale głupota jest bardzo głęboko zakorzeniona w Homestuck.
  • Składnia do pisania programu wykonującego kod xjest następująca ~ATH(x){EXECUTE(<code>)}. Jeśli chcesz wykonać kod na dwóch zmiennych jednocześnie, kod zostaje zagnieżdżona w następujący sposób: ~ATH(x){~ATH(y){EXECUTE(<code>)}}. Wszystkie polecenia <code>będą wykonywane zarówno na, jak xi na y.
  • Teraz przejdźmy do poleceń. +zwiększa odpowiednie zmienne o 1 i -zmniejsza je o 1. I ... to wszystko.
  • Ostatnią cechą ~ATHjest to, że zabija wszystko, z czym współpracuje. Zmienne są drukowane w formacie <name>=<value>(po którym następuje nowa linia) na polecenie [<name>].DIE();. Następnie program wypisuje słowo DIE <name>i nowy wiersz kilka razy równe wartości bezwzględnej wartości zmiennej. Kiedy zmienne są zabijane jednocześnie z [<name1>,<name2>].DIE();(możesz zabić tyle zmiennych, ile chcesz, o ile istnieją), DIE()polecenie jest wykonywane na zmiennych sekwencyjnie.

Przykładowe programy

Program 1:

import sollux;                  //calls variable "sollux"
import eridan;                  //calls variable "eridan"
~ATH(sollux){EXECUTE(--)}       //sets the value of "sollux" to -2
~ATH(eridan){EXECUTE(+++++)}    //sets the value of "eridan" to 5
[sollux].DIE();                 //kills "sollux", prints "DIE sollux" twice
~ATH(eridan){EXECUTE(+)}        //sets the value of "eridan" to 6
[eridan].DIE();                 //kills "eridan", prints "DIE eridan" 6 times

Wynik:

sollux=-2
DIE sollux
DIE sollux
eridan=6
DIE eridan
DIE eridan
DIE eridan
DIE eridan
DIE eridan
DIE eridan

Program 2:

import THIS;                    //calls variable "THIS"
~ATH(THIS){EXECUTE(++++)}       //sets the value of "THIS" to 4
bifurcate THIS[THIS1,THIS2];    //deletes "THIS", creates variables "THIS1" and "THIS2" both equal to 4
~ATH(THIS1){EXECUTE(++)}        //sets the value of "THIS1" to 6
[THIS1,THIS2].DIE();            //kills "THIS1" and "THIS2", prints "DIE THIS1" 6 times then "DIE THIS2" 4 times

import THAT;                                         //calls variable "THAT"
bifurcate THAT[THESE,THOSE];                         //deletes "THAT", creates variables "THESE" and "THOSE"
~ATH(THESE){~ATH(THOSE){EXECUTE(+++)}EXECUTE(++)}    //sets the value of "THESE" and "THOSE" to 3, then sets the value of "THESE" to 5
[THESE,THOSE].DIE();                                 //kills "THESE" and "THOSE", prints "DIE THESE" 5 times then "DIE THOSE" 3 times

Wynik:

THIS1=6
DIE THIS1
DIE THIS1
DIE THIS1
DIE THIS1
DIE THIS1
DIE THIS1
THIS2=4
DIE THIS2
DIE THIS2
DIE THIS2
DIE THIS2
THESE=5
DIE THESE
DIE THESE
DIE THESE
DIE THESE
DIE THESE
THOSE=3
DIE THOSE
DIE THOSE
DIE THOSE

To jest kod golfowy, więc obowiązują standardowe zasady. Najkrótszy kod w bajtach wygrywa.

Arktur
źródło
2
Do śmierci. Widzę, co tu zrobiłeś.
Digital Trauma
3
@DigitalTrauma Muszę podziękować Andrew Hussie (facetowi, który pisze Homestuck) za wymyślenie tego imienia.
Arcturus,
1
@sysreq ~ATHwykorzystuje średników jako linia zakończeń dla osób import, bifurcatei DIEpoleceń. Zarówno REPL, jak i pliki są w porządku. Czułość wielkości liter jest wymagana zarówno na wejściu, jak i na wyjściu (staram się ~ATHjak najlepiej dopasować do rzeczywistych ).
Arcturus,
1
@sysreq Musiałem zmienić kilka rzeczy, aby język rzeczywiście zrobił coś w prawdziwym życiu, opisywane przeze mnie elementy są w porządku.
Arcturus,
2
Jestem szczerze zaskoczony, że to pytanie nie uzyskało więcej odpowiedzi, a jeszcze bardziej zaskakujące, że nie ma hordy czarodziejów Perla uzbrojonych w regexy różdżki magii
kot

Odpowiedzi:

3

Python 2.7.6, 1244 1308 1265 1253 1073 1072 1071 1065 1064 1063 bajtów

W porządku, nie biję tu żadnych rekordów, ale chodzi o to, że najmniejszy Python przejdzie na tyle, na ile odczytuje dane wejściowe naraz z pliku, a nie sekwencyjnie z czasem. Spróbuję to poprawić później w innym języku (i tłumaczu, nie tylko parserze). Do tego czasu ciesz się obrzydliwie okropną potwornością.

Uwaga : otwiera plik o nazwie tw katalogu roboczym. Aby otworzyć argument linii poleceń, dodaj import sysna górę pliku i zmień 't'nasys.argv[1]

n=s='\n';m=',';X='[';Y=']';c=';';A='~ATH';D='import';b,g,k=[],[],[];r=range;l=len;f=open('t','r').read().split(n)
def d(j,u):
 p=[]
 for e in j:
  if e!=u:p.append(e)
 return''.join(p)
for h in r(l(f)):f[h]=f[h].split('//')[0].split()
while[]in f:f.remove([])
for h in r(l(f)):
 i=f[h]
 if i[0]==D and l(i)==2and i[1][l(i[1])-1]==c and d(i[1],c)not in b:g.append(0);b.append(d(i[1],c))
 elif i[0].startswith(A):
  i=i[0].split('){')
  for e in r(l(i)):
   if i[e].startswith(A):
    i[e]=i[e].split('(')
    if i[0][1]in b:g[b.index(i[0][1])]+=(i[1].count('+')-i[1].count('-'))
 elif i[0].startswith('bifurcate')and l(i)==2and i[1][l(i[1])-1]==c:
  i=i[1].split(X)
  if i[0] in b:
   z=d(d(i[1],c),Y).split(m)
   for e in r(l(z)):g.append(g[b.index(i[0])]);b.append(z[e])
   g.remove(g[b.index(i[0])]);b.remove(i[0])
 elif i[0].startswith(X)and i[0].endswith('.DIE();')and l(i)==1:
  z=d(i[0],X).split(Y)[0].split(m)
  for e in r(l(z)):
   k.append((z[e],g[b.index(z[e])]))
for e in r(l(k)):k0=k[e][0];k1=k[e][1];s+=k0+'='+str(k1)+n+('DIE '+k0+n)*abs(k1)
print s
kot
źródło
2

Python 2, 447 475 463 443 bajty

exec("eNp1UUtrAjEQvu+vCEshiYnrxl7KbqOUVmjvCoUkxUdiG7BRkpW2iP3tTVwrReppMsx8r4l936x9A8JXoN5kmu/2WeCxK0KjrSu8mWmEs0Ad96YI27lDPu/1is7wKqcQ0kBLenM+ty0nilu4zqnPtYCSQcXL2P2LmNvl1i9mjWlBUhwKbRt14uhHjlSvjzVy1tqswO/7AjsSpKtwIpGvt2zALqyNnkf3k/FIolb2ACjlpe2jR6lk8fAUQbKNulx7YIF1IDkqwmZlGwQpxNXGW9cASyCHZKqFVVOCoJQOEhjxABKLO7N5QGmET5qOs/Qfoqq6TGUfb3ZlgKvOnOxTwJKpDq6HSLzsVfK1k7g1iB7Hd9/JWh3T9wclkYwTlY4odP0nnvk0C3RUwj95/ZUq".decode('base64').decode('zip'))

Okazuje się, że kompresowanie i kodowanie programu base64 nadal powoduje zapisanie bajtów w stosunku do normalnej wersji. Dla porównania, oto normalny:

import sys,re
d={}
s=sys.stdin.read()
s,n=re.subn(r"//.*?$",'',s,0,8)
s,n=re.subn(r"import (.*?);",r"d['\1']=0;",s,0,8)
s,n=re.subn(r"bifurcate (.*?)\[(.*?),(.*?)\];",r"d['\2']=d['\3']=d['\1'];del d['\1'];",s,0,8)
s,n=re.subn(r"([+-])",r"\g<1>1",s,0,8)
s,n=re.subn(r"EXECUTE\((.*?)\)",r"0\1",s,0,8)
s,n=re.subn(r"\[(.*?)\]\.DIE\(\);",r"for i in '\1'.split(','):print i+'='+`d[i]`+('\\n'+'DIE '+i)*abs(d[i])",s,0,8)
n=1
s=s[::-1]
while n:s,n=re.subn(r"\}([+-01]*);?([^}]*?)\{\)(.*?)\(HTA~",r";\g<2>0+\1=+]'\3'[d;\1",s,0,8)
exec(s[::-1])

Zasadniczo pożądane rozwiązanie „regexy różdżki magii”. Wczytuje cały program ze standardowego wejścia jako pojedynczy ciąg, zastępuje wyrażenia ~ ATH wyrażeniami Python wykonującymi opisaną semantykę, a exec () s otrzymany ciąg.

Aby zobaczyć, co robi, spójrz na program w języku Python, na który jest tłumaczony drugi podany program testowy:

d['THIS']=0;                    
0+1+1+1+1;d['THIS']+=0+1+1+1+1+0;       
d['THIS1']=d['THIS2']=d['THIS'];del d['THIS'];    
0+1+1;d['THIS1']+=0+1+1+0;        
for i in 'THIS1,THIS2'.split(','):print i+'='+`d[i]`+('\n'+'DIE '+i)*abs(d[i])            

d['THAT']=0;                                         
d['THESE']=d['THOSE']=d['THAT'];del d['THAT'];                         
0+1+1;d['THESE']+=0+1+1+00+1+1+1;d['THOSE']+=0+1+1+1+0;    
for i in 'THESE,THOSE'.split(','):print i+'='+`d[i]`+('\n'+'DIE '+i)*abs(d[i])                                 

Dobrze, że 00 == 0: P

Oczywiście można zaoszczędzić kilka bajtów, wykorzystując niejednoznaczność reguł. Na przykład nie jest powiedziane, co powinno się zdarzyć, gdy ktoś spróbuje DIE()użyć zmiennej, która nie została importedytowana lub już została bifurcated. Domyślam się na podstawie opisu, że powinien wystąpić błąd. Jeśli błąd nie jest wymagany, mógłbym usunąć delinstrukcję.

EDYCJA: Naprawiono błąd, którego nie testowały dostarczone przypadki testowe. Mianowicie, tak jak było, każdy ~ATHblok resetuje zmienną do zera przed jej zwiększeniem. Naprawienie tego kosztowało mnie 28 bajtów. Jeśli ktoś widzi lepszy sposób na zastąpienie ~ATHbloków, chciałbym to wiedzieć.

EDYCJA 2: Zaoszczędzono 12 bajtów, rozwijając pętlę wyrażeń regularnych, czyniąc je wszystkimi subnsami i pozwalając kompresji zająć się powtórzeniem.

EDYCJA 3: Zaoszczędź 20 bajtów, zastępując wewnętrzną forpętlę mnożeniem łańcucha.

kwintopia
źródło
Hej, w końcu regexy różdżki magii! Nie będę w stanie tego pokonać, ale dobrze zrobione!
kot
Moja implementacja całkowicie ignoruje rzeczy, które nie są wyraźnie objęte regułami, co oznacza, że ​​możesz nie zgłaszać błędów i po prostu ignorować te przypadki.
kot
możesz zaoszczędzić trochę bajtów, wykonując import sys,rezamiastimport sys;import re
cat
1
podświetlanie składni znacznie ułatwia czytanie
cat
1
@cat przepraszam, dawno temu zapomniałem Ci odpowiedzieć. uruchamiasz go z wiersza poleceń i python ~ath.py < program.~ath
wpakowujesz