РЕДАКТИРОВАТЬ: Как некоторые из вас подозревали, была ошибка в официальном переводчике: порядок композиции в .
был обратный. У меня было две версии переводчика, и я использовал неправильную. Примеры были также написаны для этой неправильной версии. Я исправил переводчик в репозитории, и примеры ниже. Описание >
также было немного двусмысленным, поэтому я исправил это. Кроме того, извинения за то, что это заняло так много времени, я застрял в некоторых реальных вещах.
РЕДАКТИРОВАТЬ 2: У моего интерпретатора была ошибка в реализации, .
которая была отражена в примерах (они полагались на неопределенное поведение). Теперь проблема исправлена.
Введение
Shift - это эзотерический функциональный язык программирования, который я создал пару лет назад, но опубликовал сегодня. Он основан на стеке, но также имеет автоматическое каррирование, как Haskell.
Спецификация
В Shift есть два типа данных:
- Функции, которые имеют произвольную положительную арность (количество входов) и возвращают список выходов. Например, функция, которая дублирует свой единственный вход, имеет арность 1, а функция, которая меняет два своих входа, имеет арность 2.
- Бланки, которые все идентичны и не имеют никакой другой цели, кроме как не быть функциями.
Программа Shift состоит из нуля или более команд , каждая из которых представляет собой один символ ASCII. Всего 8 команд:
!
( применить ) извлекает функциюf
и значениеx
из стека и применяетсяf
кx
. Еслиf
имеет арность 1, списокf(x)
добавляется в начало стека. Если он имеет арностьn > 1
, новая(n-1)
-арная функция помещаетсяg
в стек. Он принимает входные данные и возвращает .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( пусто ) толкает пробел в стек.+
( клон ) помещает в стек унарную функцию, которая дублирует входные данные: любое значениеx
отображается на[x,x]
.>
( shift ) помещает в стек унарную функцию, которая принимаетn
-ary-функциюf
, и возвращает(n+1)
-ary-функцию,g
которая игнорирует свой первый аргументx
, вызываетf
оставшиеся и привязываетx
перед результатом. Например,shift(clone)
это двоичная функция, которая принимает входные данныеa,b
и возвращает[a,b,b]
./
( fork ) помещает в стек троичную функцию, которая принимает три входаa,b,c
и возвращает,[b]
еслиa
пусто, и в[c]
противном случае.$
( Вызов ) толкает в стек двоичную функцию , которая появляется функциейf
и значениеx
, и применяетf
кx
точности так , как!
делает..
( цепочка ) помещает в стек двоичную функцию, которая выдает две функцииf
иg
и возвращает их композицию: функция,h
которая имеет ту же самую арностьf
, что и которая обычно принимает свои входные данные, применяетсяf
к ним, а затем полностью применяетсяg
к результату (вызывает это столько раз, сколько диктует его арность), с неиспользованными предметами из результата,f
оставшимися в результатеh
. Например, предположим, чтоf
это двоичная функция, которая клонирует свой второй аргумент иg
называется call . Если стек содержит[f,g,a,b,c]
и мы.!!
, то он содержит[chain(f,g),a,b,c]
; если мы делаем!!
дальше, тоf
сначала применяетсяa,b
, производя[a,b,b]
, затемg
применяется к первым двум элементам этого, поскольку его арность равна 2, производя[a(b),b]
, и стек, наконец, будет[a(b),b,c]
.@
( скажем ) выдвигает унарную функцию, которая просто возвращает свой ввод и печатает,0
если она была пустой, и1
если это была функция.
Обратите внимание, что все команды, кроме !
простого переноса значения в стек, не позволяют выполнять ввод, и единственный способ вывести что-либо - использовать @
. Программа интерпретируется путем оценки команд одна за другой, вывода 0
s или 1
s при каждом вызове «say» и выхода. Любое поведение, не описанное здесь (применение пробела, применение стека длиной 0 или 1, вызов «цепочки» для пробела и т. Д.) Не определено: интерпретатор может аварийно завершить работу, потерять молчание, запросить ввод или что-то еще.
Задание
Ваша задача - написать переводчика для Shift. Он должен принимать из STDIN, командной строки или аргумента функции интерпретируемую программу Shift и печатать в STDOUT или возвращать результирующий (возможно, бесконечный) вывод 0
s и 1
s. Если вы пишете функцию, вы должны каким-то образом получить доступ к выходам бесконечной длины (генератор в Python, ленивый список в Haskell и т. Д.). В качестве альтернативы вы можете взять другой ввод, число n
и вернуть хотя бы n
символы вывода, если он длиннее n
.
Побеждает меньшее количество байтов, и стандартные лазейки запрещены.
Тестовые случаи
Эта программа Shift печатает 01
:
?@!@@!
Начиная слева: нажмите пробел, нажмите сказать , затем примените слово к пробелу. Это выводы 0
. Затем дважды нажмите « сказать» и примените второе слово к первому. Это выводы 1
.
Эта программа зацикливается навсегда, не выводя ничего:
$+.!!+!!
Нажмите call и clone , затем примените к ним цепочку (нам нужно два !
s, так как цепочка является двоичной функцией). Теперь в стеке есть функция, которая принимает один аргумент, дублирует его и вызывает первую копию второго. С +!!
, мы дублируем эту функцию и вызываем ее на себя.
Эта программа печатает 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Нажмите пробел и скажите . Затем создайте двоичную функцию, которая копирует свой второй аргумент b
, затем копирует первый a
и составляет его с собой, затем применяет композицию к копии b
, возвращая [a(a(b)),b]
. Примените его, чтобы сказать и пусто, затем примените слово к двум элементам, остающимся в стеке.
Эта программа печатает 0
. Для каждого, !!!
что вы добавляете к нему, печатается дополнительный 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Нажмите пробел и скажите . Затем составьте троичную функцию, которая принимает в f,g,x
качестве входных данных и возвращает [f,f,g,g(x)]
. Клонируйте эту функцию и примените ее к себе, скажем , и к пробелу. Это приложение не меняет стек, поэтому мы можем применить функцию снова столько раз, сколько захотим.
Эта программа печатает бесконечную последовательность 001011011101111...
, где число 1
s всегда увеличивается на единицу:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Репозиторий содержит аннотированную версию.
f(x1, x2, ..., xn)
и g(y1, y2, ..., ym)
. Вызов .
вызывает оба из них и толкает функцию h(z1, z2, ..., zn)
. Теперь вы можете съесть все эти аргументы, постепенно вычеркивая их !
. После n
таких приложений оставшаяся функция имела только один аргумент, и в этот момент она вычисляет f(z1, z2, ..., zn)
(то есть f
применяется ко всем аргументам, в которых вы каррировали), который выдвигает некоторые новые значения, а затем сразу же получает m
значения из стека и вызывает g
их.
.
работает точно так же, как описал Мартин, за исключением того, что если он f
возвращает список m
значений, меньших значений, результат не определен (композиция имеет арность n
, поэтому она не может использовать больше аргументов из стека). По сути, выходные данные f
используются в качестве временного стека, на который g
проталкиваются и применяются m
времена !
, а результат этого добавляется в основной стек.