Написать интерпретатор ATH

12

Популярный веб-комикс Homestuck использует язык программирования, предназначенный ~ATHдля уничтожения вселенных. В то время как задача кода в гольф не состоит в том, чтобы написать программу, которая уничтожит наше существование, мы будем уничтожать некоторые более ручные (хотя и менее интересные) объекты: переменные

~ATH(произносится как «до смерти», обратите внимание, как ~athработает «тильда спорт»), создавая переменную с именем THIS, выполняя команду с помощью EXECUTEи заканчивая программу с помощью THIS.DIE(). Вики-страницу для использования языка в Homestuck можно найти здесь . Целью этой задачи будет создание ~ATHпереводчика.

Ради задачи я собираюсь создать некоторые детали ~ATH, которых на самом деле не существует, но сделаю это (несколько) полезным.

  • Язык будет работать только с целыми числами, которые объявлены с import <variable name>;. Переменная будет автоматически установлена ​​на значение 0. Одновременно может быть импортирована только одна переменная.
  • Переменную xможно скопировать записью bifurcate x[y,z];, которая удалит переменную xи заменит ее идентичными переменными yи z. Обратите внимание, что он не может создать переменную с тем же именем, что и удаленная. По сути, переменная переименовывается, а затем создается копия переменной с другим именем. Это кажется глупой чертой, но глупость очень глубоко укоренилась в Homestuck.
  • Синтаксис для написания программы, выполняющей код, x- ~ATH(x){EXECUTE(<code>)}. Если вы хотите , чтобы выполнить код от двух переменных одновременно, код становится вложенным, как это: ~ATH(x){~ATH(y){EXECUTE(<code>)}}. Все команды <code>будут выполнены как на, так xи на y.
  • Теперь давайте перейдем к командам. +увеличивает соответствующую переменную (переменные) на 1 и -уменьшает их на 1. И ... вот и все.
  • Последняя особенность в ~ATHтом, что он убивает все, с чем работает. Переменные печатаются в формате <name>=<value>(за которым следует новая строка) в команде [<name>].DIE();. После этого программа печатает слово DIE <name>и символ новой строки количество раз, равное абсолютному значению значения переменной. Когда переменные уничтожаются одновременно с [<name1>,<name2>].DIE();(вы можете уничтожить столько переменных, сколько хотите, до тех пор, пока они существуют), DIE()команда выполняется для переменных последовательно.

Примеры программ

Программа 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

Выход:

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

Программа 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

Выход:

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

Это код гольф, поэтому применяются стандартные правила. Самый короткий код в байтах побеждает.

Арктур
источник
2
До смерти. Я вижу что ты тут делал.
Цифровая травма
3
@DigitalTrauma Я должен передать кредит Эндрю Хасси (парень, который пишет Homestuck) за придумывание имени.
Арктур
1
@sysreq ~ATHиспользует точку с запятой в качестве линейных окончаний для import, bifurcateи DIEкоманд. И REPL и файлы в порядке. Чувствительность к регистру требуется как на входе, так и на выходе (я стараюсь ~ATHмаксимально соответствовать фактическому ).
Арктур
1
@sysreq Мне пришлось изменить несколько вещей, чтобы язык действительно что- то делал в реальной жизни, описанные мной pecs - это нормально.
Арктур
2
Я искренне удивлен, что этот вопрос не получил больше ответов, и еще более удивлен, что нет никакой орды волшебников Perl, вооруженных волшебными палочками регулярных выражений
кошка

Ответы:

3

Python 2.7.6, 1244 1308 1265 1253 1073 1072 1071 1065 1064 1063 байта

Хорошо, я не беру здесь никаких записей, но речь идет о самом маленьком Python, поскольку он считывает входные данные сразу из файла, а не последовательно с течением времени. Я постараюсь сделать это позже на другом языке (и интерпретаторе, а не просто парсере). До тех пор наслаждайтесь отвратительно отвратительным чудовищем.

Примечание : открывает файл с именем tв рабочем каталоге. Чтобы открыть аргумент командной строки, добавьте import sysв начало файла и измените 't'наsys.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
Кот
источник
2

Python 2, 447 475 463 443 байта

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

Оказывается, архивирование и шифрование программы base64 все еще экономит байты по сравнению с обычной версией. Для сравнения вот нормальный:

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])

В основном желаемое решение "regexy wands of magic". Читает во всей программе из stdin как одну строку, заменяет выражения ~ ATH выражениями Python, которые выполняют описанную семантику, и выполняет exec () полученную строку.

Чтобы увидеть, что он делает, посмотрите на программу на python, в которую переведена вторая предоставленная тестовая программа:

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])                                 

Это хорошо, что 00 == 0: P

Очевидно, что несколько байт можно было бы сэкономить, используя двусмысленность в правилах. Например, не сказано, что должно произойти, если кто-то попытается DIE()использовать переменную, которая не была importотредактирована или уже была bifurcated. Мое предположение, основанное на описании, заключалось в том, что должна быть ошибка. Если ошибка не требуется, я мог бы удалить delутверждение.

РЕДАКТИРОВАТЬ: Исправлена ​​ошибка, которую не тестировали тестовые примеры. А именно, как это было, каждый ~ATHблок сбрасывал переменную в ноль, прежде чем увеличивать ее. Это стоило мне 28 байт, чтобы это исправить. Если кто-нибудь увидит лучший способ заменить ~ATHблоки, я бы хотел узнать это.

РЕДАКТИРОВАТЬ 2: Сохраните 12 байтов, развернув цикл регулярных выражений, превратив их в субныри и позволив сжатию позаботиться о повторении.

РЕДАКТИРОВАТЬ 3: Сохранено еще 20 байтов путем замены внутреннего forцикла умножением строки.

quintopia
источник
Эй, наконец, волшебное волшебство! Я не смогу победить это, но молодец!
кот
Моя реализация полностью игнорирует вещи, явно не указанные в правилах, что означает, что вы можете не выдавать ошибку и просто игнорировать эти случаи.
кот
Вы можете сэкономить несколько байтов, выполнив import sys,reвместоimport sys;import re
cat
1
Подсветка синтаксиса делает это намного легче для чтения
кошка
1
@ Кэт, прости, я забыл тебе ответить очень давно. вы запускаете его из командной строки и python ~ath.py < program.~ath
передаете