Крихітний Лисп, крихітний перекладач


33

Програмісти Lisp вихваляються тим, що Lisp - це потужна мова, яку можна створити з дуже невеликого набору примітивних операцій . Давайте зберемо цю ідею на практиці гольф інтерпретатор діалекті називається tinylisp.

Специфікація мови

У цій специфікації будь-яка умова, результат якої описується як "невизначений", може зробити що завгодно у вашому інтерпретаторі: вийти з ладу, вийти з ладу в мовчазному режимі, створити випадковий gobbldegook або працювати як слід. Довідкова реалізація в Python 3 доступна тут .

Синтаксис

Лексеми в tinylisp є (, )або будь-який рядок з одного або декількох друкованих ASCII символів , крім дужок або простору. (Тобто наступний регулярний вираз:) [()]|[^() ]+. Будь-який маркер, який повністю складається з цифр, є цілим літералом. (Нулі в порядку.) Будь-який маркер , який містить нецифровий символ, навіть числові виглядають прикладів подобаються 123abc, 3.14і -10. Усі пробіли (включаючи, як мінімум, символи 32 і 10 ASCII) ігноруються, за винятком випадків, коли він розділяє лексеми.

Програма тинілісп складається з серії виразів. Кожен вираз є або цілим числом, символом, або s-виразом (список). Списки складаються з нуля або більше виразів, загорнутих у дужки. Між елементами не використовується розділювач. Ось приклади виразів:

4
tinylisp!!
()
(c b a)
(q ((1 2)(3 4)))

Висловлення, які не є добре сформованими (зокрема, що мають невідповідні дужки), дають не визначену поведінку. (Реалізація посилань автоматично закриває відкриті паролі і зупиняє аналіз на неперевершених закритих паролях.)

Типи даних

Типи даних тинілісп - цілі числа, символи та списки. Вбудовані функції та макроси також можна вважати типом, хоча їх вихідний формат не визначений. Список може містити будь-яку кількість значень будь-якого типу і може бути вкладений довільно глибоко. Цілі особи повинні підтримуватися принаймні від -2 ^ 31 до 2 ^ 31-1.

Порожній список ()- також званий нулем - і ціле число 0- єдині значення, які вважаються логічно помилковими; всі інші цілі числа, непорожні списки, вбудовані та всі символи логічно вірні.

Оцінка

Вирази в програмі оцінюються в порядку і результати кожного надсилаються в stdout (докладніше про форматування виводу пізніше).

  • Ціле число буквально оцінює себе.
  • Порожній список ()оцінює сам.
  • Список одного або декількох елементів оцінює його перший елемент і розглядає його як функцію або макрос, називаючи його з рештою елементами як аргументи. Якщо елемент не є функцією / макросом, поведінка не визначена.
  • Символ оцінюється як ім'я, надаючи значення, пов'язане з цим ім'ям у поточній функції. Якщо ім'я не визначено в поточній функції, воно оцінюється до значення, пов'язаного з ним у глобальному масштабі. Якщо ім’я не визначено в поточній чи глобальній області, результат не визначений (реалізація посилання дає повідомлення про помилку та повертає нуль).

Вбудовані функції та макроси

У тиніліспі є сім вбудованих функцій. Функція оцінює кожен її аргумент, перш ніж застосувати до них якусь операцію і повернути результат.

  • c- мінуси [список довідок]. Бере два аргументи, значення і список, і повертає новий список, отриманий додаванням значення в передній частині списку.
  • h- голова ( машина , в термінології Лісп). Бере список і повертає перший елемент у ньому, або нуль, якщо дано нуль.
  • t- хвіст ( cdr , в термінології Лісп). Бере список і повертає новий список, що містить усі, крім першого пункту, або нуль, якщо дано нуль.
  • s- відняти. Бере два цілих числа і повертає перший мінус другий.
  • l- менше, ніж. Бере два цілих числа; повертає 1, якщо перший менше другого, 0 в іншому випадку.
  • e- рівний. Приймає два значення одного типу (обидва цілі числа, обидва списки або обидва символи); повертає 1, якщо два рівні (або однакові в кожному елементі), 0 в іншому випадку. Тестування вбудованих рівнів є невизначеним (посилання на реалізацію працює як очікується).
  • v- eval. Бере один список, ціле число або символ, що представляє вираз, і оцінює його. Наприклад (v (q (c a b))), те саме, що робити (c a b); (v 1)дає 1.

"Значення" тут включає будь-який список, ціле число, символ або вбудований, якщо інше не вказано. Якщо функція вказана як прийняття конкретних типів, передача її різним типам є невизначеною поведінкою, як і передача неправильної кількості аргументів (реалізація посилань зазвичай виходить з ладу).

У тиніліспі є три вбудовані макроси. Макрос, на відміну від функції, не оцінює свої аргументи перед тим, як застосувати до них операції.

  • q- цитата. Бере один вираз і повертає його неоціненим. Наприклад, оцінка (1 2 3)дає помилку, оскільки вона намагається викликати 1як функцію чи макрос, але (q (1 2 3))повертає список (1 2 3). Оцінювання aдає значення, пов'язане з іменем a, але (q a)дає саме ім'я.
  • i- якщо. Бере три вирази: умова, вираз iftrue та iffalse. Спочатку оцінює стан. Якщо результат є хибним ( 0або нульовим), оцінює і повертає вираз iffalse. В іншому випадку оцінює і повертає вираз iftrue. Зауважте, що вираз, який не повертається, ніколи не оцінюється.
  • d- деф. Бере символ і вираз. Оцінює вираз і прив'язує його до заданого символу, який розглядається як ім'я в глобальному масштабі , а потім повертає символ. Спроба переосмислити ім’я має бути невдалою (мовчки, з повідомленням або збоєм; у виконанні посилання відображається повідомлення про помилку). Примітка: немає необхідності цитувати ім'я , перш ніж передати його d, хоча необхідно процитувати вислів , якщо це список або символ ви не хочете оцінили: наприклад, (d x (q (1 2 3))).

Передача неправильної кількості аргументів макросу - це невизначена поведінка (збої в застосуванні посилань). Передача чогось, що не є символом, оскільки перший аргумент dне визначеного поведінки (реалізація посилання не дає помилки, але значення не може бути посилається згодом).

Функції та макроси, визначені користувачем

Починаючи з цих десяти вбудованих, мову можна розширити, побудувавши нові функції та макроси. Вони не мають виділеного типу даних; вони просто списки з певною структурою:

  • Функція - це список двох елементів. Перший - це або список одного або декількох імен параметрів, або одне ім'я, яке отримає перелік будь-яких аргументів, переданих функції (таким чином, дозволяючи виконувати функції змінної арності). Другий - це вираз, який є тілом функції.
  • Макрос - це те саме, що і функція, за винятком того, що він містить нуль перед назвою (-ми) параметрів, таким чином, перетворюючи його на три-елементний список. (Спроба викликати трипозиційні списки, які не починаються з нуля, - це невизначена поведінка; реалізація посилань ігнорує перший аргумент і трактує їх також як макроси.)

Наприклад, наступне вираження - це функція, яка додає два цілих числа:

(q               List must be quoted to prevent evaluation
 (
  (x y)          Parameter names
  (s x (s 0 y))  Expression (in infix, x - (0 - y))
 )   
)

І макрос, який бере будь-яку кількість аргументів і оцінює та повертає перший:

(q
 (
  ()
  args
  (v (h args))
 )
)

Функції та макроси можна викликати безпосередньо, прив’язати до імен за допомогою dта передати іншим функціям чи макросам.

Оскільки тіла функцій не виконуються в час визначення, рекурсивні функції легко визначити:

(d len
 (q (
  (list)
  (i list                      If list is nonempty
   (s 1 (s 0 (len (t list))))  1 - (0 - len(tail(list)))
   0                           else 0
  )
 ))
)

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

Рекурсія хвоста

Рекурсія на виклик хвоста - важлива концепція в Ліспі. Він реалізує певні види рекурсії у вигляді циклів, тим самим зберігаючи стек виклику малим. Ваш перекладач тинілісп повинен реалізовувати належну рекурсивну хворобу !

  • Якщо вираз повернення визначеної користувачем функції або макросу є викликом до іншої визначеної користувачем функції або макросу, ваш інтерпретатор не повинен використовувати рекурсію для оцінки цього виклику. Натомість він повинен замінити поточну функцію та аргументи на нову функцію та аргументи та циклічно, поки ланцюг викликів не буде вирішено.
  • Якщо вираз повернення визначеної користувачем функції або макросу є викликом до i, не одразу оцінюйте обрану гілку. Натомість перевірте, чи це виклик до іншої визначеної користувачем функції або макрос. Якщо так, замініть функцію та аргументи, як описано вище. Це стосується довільно глибоко вкладених подій i.

Хвостова рекурсія повинна працювати як для прямої рекурсії (функція викликає саму себе), так і непрямої рекурсії (функція aвиклику функція, bяка викликає [тощо], яка викликає функцію a).

Хвостово-рекурсивна функція довжини (з допоміжною функцією len*):

(d len*
 (q (
  (list accum)
  (i list
   (len*
    (t list)
    (s 1 (s 0 accum))
   )
   accum
  )
 ))
)
(d len
 (q (
  (list)
  (len* list 0)
 ))
)

Ця реалізація працює для довільно великих списків, обмежених лише максимальним цілим розміром.

Область застосування

Параметри функції - це локальні змінні (фактично константи, оскільки їх неможливо змінити). Вони знаходяться в межах, поки тіло цього виклику цієї функції виконується, і виходить за межі під час будь-яких більш глибоких викликів та після повернення функції. Вони можуть "затінювати" глобально визначені імена, тим самим роблячи глобальне ім'я тимчасово недоступним. Наприклад, наступний код повертає 5, а не 41:

(d x 42)
(d f
 (q (
  (x)
  (s x 1)
 ))
)
(f 6)

Однак наступний код повертає 41, оскільки xна рівні виклику 1 недоступний з рівня 2 виклику:

(d x 42)
(d f
 (q (
  (x)
  (g 15)
 ))
)
(d g
 (q (
  (y)
  (s x 1)
 ))
)
(f 6)

Єдиними іменами в області застосування в будь-який момент часу є 1) локальні імена поточно виконуваної функції, якщо такі є, і 2) глобальні імена.

Вимоги до подання

Вхід і вихід

Ваш перекладач може прочитати програму зі stdin або з файлу, вказаного через аргумент stdin або аргумент командного рядка. Оцінивши кожен вираз, він повинен вивести результат цього виразу в stdout із зворотним новим рядком.

  • Цілі лічильники повинні виводитись у найбільш природне уявлення мови вашої реалізації. Негативні цілі числа можуть виводитись із провідними знаками мінус.
  • Символи повинні виводитись у вигляді рядків, без навколишніх лапок чи пробілів.
  • Списки повинні бути виведені з усіма елементами, розділеними пробілом, і загорнуті в круглі дужки. Простір усередині дужок є необов'язковим: (1 2 3)і ( 1 2 3 )обидва прийнятні формати.
  • Виведення вбудованих функцій та макросів - це невизначена поведінка. (Довідкове тлумачення відображає їх як <built-in function>.)

Інший

Довідковий інтерпретатор включає середовище REPL та можливість завантажувати модулі тинілісп з інших файлів; вони надаються для зручності і не потрібні для цього завдання.

Тестові справи

Тестові приклади розділені на кілька груп, так що ви можете протестувати більш прості, перш ніж працювати над більш складними. Однак вони також працюватимуть чудово, якщо ви скинете їх у один файл разом. Просто не забудьте видалити заголовки та очікуваний результат перед його запуском.

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


"Будь-який маркер, що складається повністю з цифр, є цілим літералом. (Провідні нулі в порядку.) Будь-який маркер, який містить нецифрові цифри, є символом, навіть приклади з числовим виглядом, такі як 123abc, 3.14 і -10." здається, суперечить "Цілі особи повинні підтримуватися принаймні від -2 ^ 31 до 2 ^ 31-1."
msh210

3
@ msh210 Не дуже, тому що перший говорить про лексеми, тоді як другий говорить про значення . Хоча немає прямого способу введення -1, я все одно можу генерувати значення -1, роблячи це (s 0 1).
DLosc

1
@coredump Прочитавши відповідну статтю у Вікіпедії , я дійшов висновку, що реалізація насправді наближається до динамічної, але не вкладається. Змінні у функції Fнедоступні у функції, Gякщо Fдзвінки G(як при динамічному масштабуванні), але вони також недоступні у функції, Hякщо Hце вкладена функція, визначена всередині F(як при лексичному обміні) - див. Тестовий випадок 5. Отже, називаючи це "лексичним "може ввести в оману.
DLosc

1
Інакше кажучи: через відсутність вкладеного обсягу реалізація може використовувати або динамічну, або лексичну стратегію обстеження та приводити до тих же результатів. Єдиними іменами, які мають область застосування в будь-який момент часу, є 1) локальні назви функцій, що виконуються в даний час, якщо такі є, і 2) глобальні імена. Закриття не підтримується. (Реалізація посилань зберігає стек прив'язки імен, що відповідає стеку викликів - підхід у динамічному стилі, який, на мою думку, буде найпростішим для реалізації.)
DLosc

1
Обов’язковий xkcd .
mınxomaτ

Відповіді:


11

Пітон 2, 685 675 660 657 646 642 640 байт

import sys,re
E=[]
G=zip("chtsle",[eval("lambda x,y=0:"+f)for f
in"[x]+y (x+[E])[0] x[1:] x-y +(x<y) +(x==y)".split()])
def V(e,L=E):
 while 1:
    try:return e and int("0%s"%e)
    except:A=e[1:]
    if""<e:return dict(G+L).get(e,e)
    f=V(e[0],L)
    if""<f:
     if f in"iv":t=V(A[0],L);e=(e[~bool(t)],t)[f>"u"];continue
     if"e">f:G[:]+=(A[0],V(A[1],L)),
     return A[0]
    if[]>f or f[0]:A=[V(a,L)for a in A]
    if[]>f:return f(*A)
    P,e=f[-2:];L=([(P,A)],zip(P,A))[P<""]
F=lambda x:V<x<""and"(%s)"%" ".join(map(F,x))or"%s"%x
for t in re.sub("([()])"," \\1 ",sys.stdin.read()).split():
 if")"==t:t=E.pop()
 if"("==t:E+=[],
 elif E:E[-1]+=t,
 else:print F(V(t))

Читає вхід зі STDIN і записує вихід у STDOUT.

Хоча це не суворо потрібно, інтерпретатор підтримує нульові функції та макроси та оптимізує хвостові дзвінки, що виконуються наскрізь v.

Пояснення

Розбір

Для того, щоб розібрати вхід, ми спочатку оточуємо кожне входження (і )з пробілами, і розділити отриману рядок на слова; це дає нам список жетонів. Ми підтримуємо стек виразів E, який спочатку порожній. Ми скануємо маркери, щоб:

  • якщо ми стикаємося з a (, ми висуваємо порожній список у верхній частині стеження виразів;
  • якщо ми стикаємося з a ), ми виставляємо значення у верхній частині стека виразів і додаємо його до списку, який раніше був під ним у стеці;
  • в іншому випадку ми додаємо поточний маркер як рядок до списку у верхній частині стека виразів (ми зберігаємо цілі числа як рядки на цьому етапі та аналізуємо їх під час оцінки.)

Якщо під час обробки звичайного маркера або після вискакування виразу з стека через стек )виразів порожній, ми знаходимося на вислові верхнього рівня, і ми оцінюємо значення, яке б в іншому випадку ми додали б, використовуючи V()та надрукувати його результат, відформатований відповідним чином F().

Оцінка

Ми підтримуємо глобальну область застосування, Gяк список пар ключів / значень. Спочатку він містить лише вбудовані функції (але не макроси, і не v, які ми трактуємо як макрос), які реалізуються як лямбда.

Оцінка відбувається всередині V(), який приймає вираз для оцінки, eі локальна область, L, який теж, список пар ключ / значення (при оцінці виразу верхнього рівня, локальна область порожній.) Нутрощі V()живий всередині нескінченного циклу, як ми виконуємо оптимізацію хвостових викликів (TCO), як пояснено далі.

Ми обробляємо eвідповідно до його типу:

  • якщо це порожній список або рядок, конвертований в int, ми повертаємо його негайно (можливо, після перетворення в int); інакше,

  • якщо це рядок, ми шукаємо це у словнику, побудованому з конкатенації глобальних та локальних областей. Якщо ми знайдемо пов’язане значення, повертаємо його; в іншому випадку, eмає бути ім'я вбудовано макрос (тобто q, i, dабо v), і ми повертаємо його без змін. В іншому випадку, якщо eце не рядок,

  • eце (непустий) список, тобто виклик функції. Ми оцінюємо перший елемент списку, тобто вираз функції, викликаючи V()рекурсивно (використовуючи поточну локальну область); ми називаємо результат f. Решта списку A, - це список аргументів. fможе бути лише рядок, в цьому випадку це вбудований макрос (або функція v), лямбда, в цьому випадку це вбудована функція або список, в цьому випадку це визначена користувачем функція або макрос.

    Якщо fрядок aa, тобто вбудований макрос, ми обробляємо його на місці. Якщо це макрос, iабо vми оцінюємо його перший операнд, або або вибираємо другий або третій операнд відповідно у випадку i, або використовуємо результат першого операнда у випадку v; замість того, щоб рекурсивно оцінювати вибраний вираз, який би переміг TCO, ми просто замінюємо eзазначеним виразом і переходимо до початку циклу. Якщо fмакрос d, ми додаємо пару, першим елементом якого є перший операнд, а другий елемент є результатом оцінки другого операнда до глобальної області G, і повертаємо перший операнд. В іншому випадку fце макрос q, і в цьому випадку ми просто повертаємо його операнд безпосередньо.

    По-друге, якщо fце лямбда або список, першим елементом якого не є (), то це ненульова функція, а не макрос. У цьому випадку ми оцінюємо її аргументи, тобто елементи A, і замінюємо Aрезультатом.

    Якщо fце лямбда, ми називаємо це, передаючи йому розпаковані аргументи A, і повертаємо результат.

    В іншому випадку f- це список, тобто визначена користувачем функція або макрос; його список параметрів є другим за останнім елементом, а його тіло - останнім елементом. Як і у випадку з макросами, iі vдля того, щоб виконати ТСО, ми не оцінюємо тіло рекурсивно, а замість цього замінюємо eтіло і продовжуємо наступну ітерацію. В відміну від iі v, тим НЕ менш, ми також замінити локальну область видимості, Lз нової локальної області видимості функції. Якщо список параметрів, Pнасправді, є списком, нова локальна область побудована за допомогою блискавки списку параметрів зі списком Pаргументів A; інакше ми маємо справу з варіативною функцією, і в цьому випадку нова локальна область має лише один елемент - пару (P, A).

ВІДПОВІДЬ

Якщо ви хочете пограти з ним, ось версія REPL для перекладача. Він підтримує перевизначення символів та імпорт файлів через аргументи командного рядка або (import <filename>)макрос. Щоб вийти з інтерпретатора, закінчіть введення (зазвичай, Ctrl + D або Ctrl + Z).

Ось приклад сесії, реалізація сортування злиття:


Ви можете отримати що-небудь коротше за допомогою zlib :) Стисніть код, перетворений у байтах, і замініть його на:import zlib;exec(zlib.decompress(your_code_compressed_in_bytes))
Labo

Ви можете зберегти два байти, додавши A[0]до якоїсь одночасної змінної відразу після блоку, окрім блоку
Ханнес Карппіла

@HannesKarppila Це так, але це порушило б нульові функції (оскільки Aв цьому випадку порожнє), і я не хочу "регресувати".
Ell

4

C (GNU), 1095 байт

Значна частина дій відбувається у функції гіганта v. Замість реалізації хвостовій рекурсії в явному вигляді, vструктуровано таким чином , що багато хто з викликів від vдо vбуде оброблятися шляхом оптимізації хвостовій рекурсії ССЗА. Збору сміття немає.

Це дуже сильно використовує розширення GCC, тому його можна компілювати лише за допомогою gcc (використовуйте команду gcc -w -Os tl.c). Він також використовує деякі scanfрозширення, які були недоступні для Windows, якими я зазвичай користуюся. Перспектива написання аналізатора зі стандартом scanfбула такою жахливою, що я використовував Linux VM для тестування програми. Парсинг без scanfкласів символів, ймовірно, додав би 100+ байт.

#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})
#define P printf
#define F(I,B)({for(I;x->c;x=x->l)B;})
#define Z return
typedef struct o{struct o*n,*l,*c;int i,t;}o;E(o a,o b){Z
a.n?!strcmp(a.n,b.n):a.c?b.c&&E(*a.c,*b.c)&E(*a.l,*b.l):!b.c&a.i==b.i;}p(o*x){x->t?P("%d ",x->i):x->n?P("%s ",x->n):F(P("("),p(x->c);P(")"));}o*x,G,N;*C(o*h,o*t){Z
O(c:h,l:t);}o*v(o*x,o*e){o*W(o*l,o*e){Z
l->c?C(v(l->c,e),W(l->l,e)):&N;}o*y,*a,*f;int t;Z
x->c?y=v(x->c,e),x=x->l,t=y->i,t?9/t?a=v(x->c,e),t>7?(t>8?a->c:a->l)?:a:t>6?v(a,e):t<6?x=v(x->l->c,e),t>4?C(a,x):O(t:1,i:t>3?E(*a,*x):t>2?a->i<x->i:a->i-x->i):v((a-&N&&!a->t|a->i?x:x->l)->l->c,e):(t&1&&d(x->c->n,v(x->l->c,e)),x->c):(y->l->l->l?y=y->l:(x=W(x,e)),a=y->c,v(y->l->c,a->n?O(n:a->n,c:x,l:&G):F(f=&G,(f=O(n:a->c->n,c:x->c,l:f),a=a->l);f))):x->n?e->n?strcmp(x->n,e->n)?v(x,e->l):e->c:e:x;}d(o*n,o*x){*v(O(n:""),&G)=(o){n:n,c:x,l:O()};}*R(h){char*z,*q;Z
scanf(" %m[^ \n()]",&q)>0?h=strtoul(q,&z,10),C(*z?O(n:q):O(t:1,i:h),R()):~getchar()&1?q=R(),C(q,R()):&N;}main(i){for(;++i<12;)d(strndup("slecivthqd"+i-2,1),O(i:i));F(x=R(),p(v(x->c,&G)));}

Напівгольф

typedef struct o o;
struct o {
    char* n;
    o* l, //next in this list
     * c; 
    int i,
        t;
} ;



#define O(...)({o*_=malloc(32);*_=(o){__VA_ARGS__};_;})

E(o a, o b) { //tests equality 
    return
        a.n ? !strcmp(a.n,b.n) :
        a.t ? a.i==b.i :
        a.c ? b.c && E(*a.c,*b.c)&E(*a.l,*b.l) :
        !b.c
    ;
}

#define P printf


p(o*x){
    x->t?P("%d ",x->i):x->n?P("%s ",x->n):({for(P("(");x->c;x=x->l)p(x->c);P(")");});
}


o*_,G,N; //N = nil



o*C(o*h,o*t){return O(c:h,l:t);}


/*
        2 3 4 5 6 7 8 9 10 11
        s l e c i v t h d  q
    */


o* v(o* x, o* e) { //takes list, int, or name
    o*W(o* l, o* e) { //eval each item in list
        return l->c ? C(v(l->c ,e), W(l->l, e)) : &N;
    }

    o*y,*a,*f;int t;
    return x->c ? //nonempty list = function/macro call
        y = v(x->c,e), //evals to function/macro
        x = x->l,   //list position of first arg (if it exists)
        (t=y->t)?   //builtin no., if any
             t>9 ?
              t&1 ? x->c // 11 = q
                : //10 = d
                (d(x->c,v(x->l->c,e)),x->c)
           : (a = v(x->c,e), //eval'd first arg
             t)>7 ? // t/h
                (t > 8 ? a->c : a->l) ?: a
           : t>6 ? //v
                v(a,e)
           : (x = x->l, //position of 2nd arg in list
             t)>5 ? //i
                v( (a->n||a->l||a->i|a->t>1 ? x : x->l)->c, e)
           : (x = v(x->c,e), //evaluated 2nd arg
             t)>4 ? // c
                C(a,x)
           : O(t:1,i:
                t>3 ? E(*a,*x) :  //e
                t>2 ? a->i<x->i : //l
                      a->i-x->i   //s
              )
        :
        (
            y->l->l->l ? //whether this is macro
                y = y->l :
                (x = W(x,e)),  //eval args
            a = y->c,  //a = arg list
            //a = a->n ? x=C(x, &N), C(a, &N) : a, //transform variadic style to normal
            v(y->l->c,
               a->n ? //variadic
                O(n:a->n,c:x,l:&G)
              : ({
                   for(f=&G; a->c; a=a->l,x=x->l)
                      f=O(n:a->c->n, c: x->c, l:f);
                   f;
                })
            )
        )
    :
    x->n ? // name
        e->n ?
            strcmp(x->n,e->n) ?
                v(x,e->l)
            : e->c
        : e
     : x; //int or nil
}

d(o*n,o*x){
    * v(O(n:""),&G) =
        (o){n:n->n,c:x,l:O()};
}


;
o*R(){
    char*z,*q;int h;
return scanf(" %m[^ \n()]",&q)>0?
    h=strtoul(q,&z,10),
    C(*z ? O(n:q) : O(t:1,i:h), R())
: getchar()&1?&N:(q=R(),C(q,R()));
}
main(i) {

    for(;++i<12;) d(O(n:strndup("slecivthdq"+i-2,1)),O(t:i));

    o *q;
    for(q=R(); q->c; q=q->l) p(v(q->c,&G));

}

Яке використання компільованого виконуваного файлу? Це ВІДПОВІДЬ? Чи приймає це ім'я файлу як вхід?
ckjbgames

@ckjbgames Він читає програму зі stdin.
feersum

Гаразд. Я думаю, ви повинні відредагувати свою відповідь і зазначити це.
ckjbgames

1

Цейлон, 2422 байти

(Я думаю, це моя найдовша програма для гри в гольф досі.)

import ceylon.language{sh=shared,va=variable,fo=formal,O=Object}import ceylon.language.meta.model{F=Function}interface X{sh fo V v(S t);sh fo G g;}class G(va Map<S,V>m)satisfies X{v(S t)=>m[t]else nV;g=>this;sh S d(S s,V v){assert(!s in m);m=map{s->v,*m};return s;}}V nV=>nothing;class LC(G c,Map<S,V>m)satisfies X{g=>c;v(S t)=>m[t]else g.v(t);}alias C=>V|Co;interface Co{sh fo C st();}interface V{sh fo C l(X c,V[]a);sh default Boolean b=>0<1;sh fo C vO(X c);sh default V vF(X c){va C v=vO(c);while(is Co n=v){v=n.st();}assert(is V r=v);return r;}}class L(sh V*i)satisfies V{vO(X c)=>if(nonempty i)then i[0].vF(c).l(c,i.rest)else this;equals(O o)=>if(is L o)then i==o.i else 1<0;b=>!i.empty;string=>"(``" ".join(i)``)";hash=>i.hash;sh actual C l(X c,V[]p){value[h,ns,x]=i.size<3then[f,i[0],i[1]]else[m,i[1],i[2]];value n=if(is L ns)then[*ns.i.narrow<S>()]else ns;assert(is S|S[]n,is V x);V[]a=h(c,p);LC lC=if(is S n)then LC(c.g,map{n->L(*a)})else LC(c.g,map(zipEntries(n,a)));return object satisfies Co{st()=>x.vO(lC);};}}class S(String n)satisfies V{vO(X c)=>c.v(this);l(X c,V[]a)=>nV;equals(O o)=>if(is S o)then n==o.n else 1<0;hash=>n.hash;string=>n;}class I(sh Integer i)satisfies V{vO(X c)=>this;l(X c,V[]a)=>nV;equals(O o)=>if(is I o)then i==o.i else 1<0;hash=>i;b=>!i.zero;string=>i.string;}V[]f(X c,V[]a)=>[for(v in a)v.vF(c)];V[]m(X c,V[]a)=>a;L c(X c,V h,L t)=>L(h,*t.i);V h(X c,L l)=>l.i[0]else L();V t(X c,L l)=>L(*l.i.rest);I s(X c,I f,I s)=>I(f.i-s.i);I l(X c,I f,I s)=>I(f.i<s.i then 1else 0);I e(X c,V v1,V v2)=>I(v1==v2then 1else 0);C v(X c,V v)=>v.vO(c);V q(X c,V a)=>a;C i(X c,V d,V t,V f)=>d.vF(c).b then t.vO(c)else f.vO(c);S d(X c,S s,V x)=>c.g.d(s,x.vF(c));class B<A>(F<C,A>nat,V[](X,V[])h=f)satisfies V given A satisfies[X,V+]{vO(X c)=>nV;string=>nat.declaration.name;l(X c,V[]a)=>nat.apply(c,*h(c,a));}{<S->V>*}b=>{S("c")->B(`c`),S("h")->B(`h`),S("t")->B(`t`),S("s")->B(`s`),S("l")->B(`l`),S("e")->B(`e`),S("v")->B(`v`),S("q")->B(`q`,m),S("i")->B(`i`,m),S("d")->B(`d`,m)};[V*]p(String inp){value ts=inp.split(" \n()".contains,1<0,1<0);va[[V*]*]s=[];va[V*]l=[];for(t in ts){if(t in" \n"){}else if(t=="("){s=[l,*s];l=[];}else if(t==")"){l=[L(*l.reversed),*(s[0]else[])];s=s.rest;}else if(exists i=parseInteger(t),i>=0){l=[I(i),*l];}else{l=[S(t),*l];}}return l.reversed;}sh void run(){va value u="";while(exists l=process.readLine()){u=u+l+"\n";}V[]e=p(u);X c=G(map(b));for(v in e){print(v.vF(c));}}

Я міг би пограти ще на кілька байтів, оскільки в деяких місцях я використовував двобуквенні ідентифікатори, але у мене вичерпалися дещо значущі букви для них. Хоча навіть таким чином це не дуже схоже на Цейлон ...

Це об'єктно-орієнтована реалізація.

У нас є інтерфейс значень Vіз класами реалізації L(список - лише обгортка навколо цілонової послідовності V), S(символ - обгортка навколо рядка), I(ціле число - обгортка навколо цілового цілового цілого) та B(вбудована функція або макрос, обгортка навколо Цейлонна функція).

Я використовую стандартне позначення рівності на Цейлоні, реалізуючи equalsметод (а також hashатрибут, який дійсно потрібен лише для символів), а також стандартний stringатрибут для виведення.

У нас є атрибут Boolean b(який за замовчуванням є істинним, переосмислений Iі Lповертається хибним для порожніх списків), і два способи l(виклик, тобто використання цього об'єкта як функції) та vO(оцінка одного кроку). Обидва повертають або значення, або об'єкт «Продовження», що дозволяє потім оцінювати ще один крок, і vF(оцінювати повністю) циклі, поки результат більше не буде продовженням.

Контекстний інтерфейс дозволяє отримати доступ до змінних. Існує дві реалізації: Gдля глобального контексту (який дозволяє додавати змінні за допомогою dвбудованого) та LCдля локального контексту, який активний при оцінці вираження функції користувача (він повертається до глобального контексту).

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

Для вбудованих я використав трюк, аналогічний тому, що я використовував у своєму Shift Interpreter , який дозволяє мені визначати їх із типом аргументів, які їм потрібні, але називати їх узагальненою послідовністю за допомогою відображення (типи перевірятимуться під час виклику). Це дозволяє уникнути клопотання про перетворення / затвердження типів усередині функцій / макросів, але потребує функцій верхнього рівня, щоб я міг отримати їх мета-моделі Function.

Функція p(розбір) розбиває рядок на пробіли, нові рядки та круглі дужки, потім петлі над лексемами та створює списки за допомогою стека та запущеного списку.

Інтерпретатор (у runметоді, який є точкою введення) потім приймає цей перелік виразів (які є лише значеннями), оцінює кожен з них і друкує результат.


Нижче представлена ​​версія з коментарями та запущена через формат.

Більш рання версія, перш ніж я почав займатися гольфом (і все ще з деякими непорозуміннями щодо оцінювання списку), знайдена в моєму сховищі Github , я незабаром поставлю її (тому переконайтеся, що ви шукаєте першу версію, якщо ви хочете оригінал).

//  Tiny Lisp, tiny interpreter
//
// An interpreter for a tiny subset of Lisp, from which most of the
// rest of the language can be bootstrapped.
//
// Question:   https://codegolf.stackexchange.com/q/62886/2338
// My answer:  https://codegolf.stackexchange.com/a/63352/2338
//
import ceylon.language {
    sh=shared,
    va=variable,
    fo=formal,
    O=Object
}
import ceylon.language.meta.model {
    F=Function
}

// context

interface X {
    sh fo V v(S t);
    sh fo G g;
}
// global (mutable) context, with the buildins 
class G(va Map<S,V> m) satisfies X {
    // get entry throws error on undefined variables. 
    v(S t) => m[t] else nV;
    g => this;
    sh S d(S s, V v) {
        // error when already defined
        assert (!s in m);
        // building a new map is cheaper (code-golf wise) than having a mutable one.
        m = map { s->v, *m };
        return s;
    }
}

// This is simply a shorter way of writing "this is not an allowed operation".
// It will throw an exception when trying to access it.
// nV stands for "no value".
V nV => nothing;

// local context
class LC(G c, Map<S,V> m) satisfies X {
    g => c;
    v(S t) => m[t] else g.v(t);
    // sh actual String string => "[local: ``m``, global: ``g``]";
}

// continuation or value
alias C => V|Co;

// continuation
interface Co {
    sh fo C st();
}

// value
interface V {
    // use this as a function and call with arguments.
    // will not work for all types of stuff.
    sh fo C l(X c, V[] a);
    // check the truthiness. Defaults to true, as
    // only lists and integers can be falsy.
    sh default Boolean b => 0 < 1;
    // evaluate once (return either a value or a continuation).
    // will not work for all kinds of expression.
    sh fo C vO(X c);
    /// evaluate fully
    sh default V vF(X c) {
        va C v = vO(c);
        while (is Co n = v) {
            v = n.st();
        }
        assert (is V r = v);
        return r;
    }
}
class L(sh V* i) satisfies V {

    vO(X c) => if (nonempty i) then i[0].vF(c).l(c, i.rest) else this;
    equals(O o) => if (is L o) then i == o.i else 1 < 0;
    b => !i.empty;
    string => "(``" ".join(i)``)";
    hash => i.hash;

    sh actual C l(X c, V[] p) {
        value [h, ns, x] =
                i.size < 3
                then [f, i[0], i[1]]
                else [m, i[1], i[2]];
        // parameter names – either a single symbol, or a list of symbols.
        // If it is a list, we filter it to throw out any non-symbols.
        // Should throw an error if there are any, but this is harder.
        value n = if (is L ns) then [*ns.i.narrow<S>()] else ns;
        assert (is S|S[] n, is V x);
        V[] a = h(c, p);

        // local context
        LC lC = if (is S n) then
            LC(c.g, map { n -> L(*a) })
        else
            LC(c.g, map(zipEntries(n, a)));
        // return a continuation instead of actually
        // calling it here, to allow stack unwinding.
        return object satisfies Co {
            st() => x.vO(lC);
        };
    }
}

// symbol
class S(String n) satisfies V {
    // evaluate: resolve
    vO(X c) => c.v(this);
    // call is not allowed
    l(X c, V[] a) => nV;
    // equal if name is equal
    equals(O o) => if (is S o) then n == o.n else 1 < 0;
    hash => n.hash;
    string => n;
}

// integer
class I(sh Integer i) satisfies V {

    vO(X c) => this;
    l(X c, V[] a) => nV;
    equals(O o) => if (is I o) then i == o.i else 1 < 0;
    hash => i;
    b => !i.zero;
    string => i.string;
}

// argument handlers for functions or macros
V[] f(X c, V[] a) => [for (v in a) v.vF(c)];
V[] m(X c, V[] a) => a;

// build-in functions
// construct
L c(X c, V h, L t) => L(h, *t.i);
// head
V h(X c, L l) => l.i[0] else L();
// tail
V t(X c, L l) => L(*l.i.rest);
// subtract
I s(X c, I f, I s) => I(f.i - s.i);
// lessThan
I l(X c, I f, I s) => I(f.i < s.i then 1 else 0);
// equal
I e(X c, V v1, V v2) => I(v1 == v2 then 1 else 0);
// eval (returns potentially a continuation)
C v(X c, V v) => v.vO(c);

// build-in macros
// quote
V q(X c, V a) => a;
// if (also returns potentially a continuation)
C i(X c, V d, V t, V f) => d.vF(c).b then t.vO(c) else f.vO(c);
// define symbol in global context
S d(X c, S s, V x) => c.g.d(s, x.vF(c));

// buildin function or macro, made from a native function and an argument handler
class B<A>(F<C,A> nat, V[](X, V[]) h = f)
        satisfies V
        given A satisfies [X, V+] {
    vO(X c) => nV;
    string => nat.declaration.name;
    // this "apply" is a hack which breaks type safety ...
    // but it will be checked at runtime.
    l(X c, V[] a) => nat.apply(c, *h(c, a));
}

// define buildins
{<S->V>*} b => {
    S("c") -> B(`c`),
    S("h") -> B(`h`),
    S("t") -> B(`t`),
    S("s") -> B(`s`),
    S("l") -> B(`l`),
    S("e") -> B(`e`),
    S("v") -> B(`v`),
    S("q") -> B(`q`, m),
    S("i") -> B(`i`, m),
    S("d") -> B(`d`, m)
};

// parses a string into a list of expressions.
[V*] p(String inp) {
    // split string into tokens (retain separators, don't group them –
    // whitespace and empty strings will be sorted out later in the loop)
    value ts = inp.split(" \n()".contains, 1 < 0, 1 < 0);
    // stack of not yet finished nested lists, outer most at bottom
    va [[V*]*] s = [];
    // current list, in reverse order (because appending at the start is shorter)
    va [V*] l = [];
    for (t in ts) {
        if (t in " \n") {
            // do nothing for empty tokens
        } else if (t == "(") {
            // push the current list onto the stack, open a new list.
            s = [l, *s];
            l = [];
        } else if (t == ")") {
            // build a lisp list from the current list,
            // pop the latest list from the stack, append the created lisp list. 
            l = [L(*l.reversed), *(s[0] else [])];
            s = s.rest;
        } else if (exists i = parseInteger(t), i >= 0) {
            // append an integer to the current list.
            l = [I(i), *l];
        } else {
            // append a symbol to the current list.
            l = [S(t), *l];
        }
    }
    return l.reversed;
}

// Runs the interpreter.
// This handles input and output, calls the parser and evaluates the expressions.
sh void run() {
    va value u = "";
    while (exists l = process.readLine()) {
        u = u + l + "\n";
    }
    V[] e = p(u);
    // create global context
    X c = G(map(b));
    // iterate over the expressions, ...
    for (v in e) {
        // print("  '``v``' → ...");
        // ... evaluate each (fully) and print the result.
        print(v.vF(c));
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.