EDYCJA: Jak niektórzy z was podejrzewali, w oficjalnym tłumaczu wystąpił błąd: kolejność kompozycji .
została odwrócona. Miałem dwie wersje tłumacza i użyłem tutaj niewłaściwej. Przykłady zostały również napisane dla tej niepoprawnej wersji. Naprawiłem interpreter w repozytorium i poniższe przykłady. Opis >
był również trochę niejednoznaczny, więc to naprawiłem. Przepraszam za to, że trwało to tak długo, że pochłonęły mnie pewne rzeczy z życia.
EDYCJA 2: Mój tłumacz miał błąd w implementacji, .
który został odzwierciedlony w przykładach (polegali na nieokreślonym zachowaniu). Problem został już rozwiązany.
Wprowadzenie
Shift to ezoteryczny funkcjonalny język programowania, który stworzyłem kilka lat temu, ale opublikowałem go dzisiaj. Jest oparty na stosie, ale ma również automatyczne curry, takie jak Haskell.
Specyfikacja
Shift ma dwa typy danych:
- Funkcje, które mają dowolną dodatnią arity (liczba wejść) i które zwróci listę wyjść. Na przykład funkcja, która duplikuje jedyne dane wejściowe, ma arity 1, a funkcja zamiany dwóch danych wejściowych ma arity 2.
- Puste miejsca, które są identyczne i nie mają innego celu niż bycie funkcjami.
Program Shift zawiera zero lub więcej poleceń , z których każde jest pojedynczym znakiem ASCII. Łącznie jest 8 poleceń:
!
( Apply ) wyświetla funkcjęf
i wartośćx
ze stosu i stosuje sięf
dox
. Jeślif
ma arity 1, listaf(x)
jest dodawana na początku stosu. Jeśli ma arityn > 1
, nowa(n-1)
funkcjag
jest wypychana na stos. Wymaga danych wejściowych i zwrotów .x1,x2,...,xn-1
f(x,x1,x2,...,xn-1)
?
( puste ) popycha puste do stosu.+
( klon ) wypycha na stos jednoargumentową funkcję, która powiela dane wejściowe: dowolna wartośćx
jest odwzorowywana[x,x]
.>
( shift ) wypycha na stos jednoargumentową funkcję, która przyjmuje funkcjęn
-aryf
, i zwraca funkcję(n+1)
-ary,g
która ignoruje swój pierwszy argumentx
, wywołujef
pozostałe i haczyx
przed wynikiem. Na przykładshift(clone)
jest funkcją binarną, która pobiera dane wejściowea,b
i zwraca[a,b,b]
./
( fork ) wypycha na stos funkcję potrójną, która pobiera trzy dane wejściowea,b,c
i zwraca,[b]
jeślia
jest pusta, i w[c]
przeciwnym razie.$
( wywołanie ) wypycha na stos funkcję binarną, która wyskakuje z funkcjif
i wartościx
, i stosujef
sięx
dokładnie tak, jak to!
robi..
( łańcuch ) wypycha na stos funkcję binarną, która wyskakuje z dwóch funkcjif
ig
zwraca ich skład: funkcja,h
która ma tę samą aranżacjęf
i która przyjmuje swoje dane wejściowe normalnie, stosuje sięf
do nich, a następnie w pełni stosujeg
się do wyniku (wywołania tyle razy, ile dyktuje to jego arsenał), przy czym niewykorzystane przedmioty z produkcjif
pozostają w wynikuh
. Załóżmy na przykład, żef
jest to funkcja binarna, która klonuje swój drugi argument ig
jest wywołaniem . Jeśli stos zawiera[f,g,a,b,c]
i my to robimy.!!
, to zawiera[chain(f,g),a,b,c]
; jeśli zrobimy to!!
dalej,f
to najpierw zastosujemy doa,b
produkcji[a,b,b]
, następnieg
jest stosowany do pierwszych dwóch elementów tego, ponieważ jego arsenał wynosi 2, co powoduje[a(b),b]
, że stos będzie w końcu[a(b),b,c]
.@
( powiedzmy ) wypycha jednoargumentową funkcję, która po prostu zwraca dane wejściowe i wypisuje,0
czy była pusta, a1
jeśli była funkcją.
Zauważ, że wszystkie polecenia oprócz !
po prostu wypychają wartość na stos, nie ma sposobu na wprowadzenie danych, a jedynym sposobem na wyprowadzenie czegokolwiek jest użycie @
. Program jest interpretowany przez ocenianie poleceń jeden po drugim, wypisywanie 0
s lub 1
s za każdym razem, gdy wywoływane jest „powiedz”, i wychodzenie. Każde zachowanie nieopisane tutaj (zastosowanie pustego miejsca, zastosowanie stosu o długości 0 lub 1, wywołanie „łańcucha” na pustym miejscu itp.) Jest niezdefiniowane: interpreter może ulec awarii, po cichu zawieść, poprosić o dane wejściowe lub cokolwiek innego.
Zadanie
Twoim zadaniem jest napisanie tłumacza dla Shift. Powinien wziąć z argumentu STDIN, wiersza poleceń lub funkcji program Shift do interpretacji i wypisać go do STDOUT lub zwrócić wynikowe (być może nieskończone) wyjście 0
s i 1
s. Jeśli piszesz funkcję, musisz mieć możliwość uzyskania dostępu do danych wyjściowych o nieskończonej długości (generator w Pythonie, lista leniwa w Haskell itp.). Alternatywnie możesz wziąć inne dane wejściowe, liczbę n
i zwrócić przynajmniej n
znaki wyjścia, jeśli jest ono dłuższe niż n
.
Wygrywa najniższa liczba bajtów, a standardowe luki są niedozwolone.
Przypadki testowe
Ten program Shift drukuje 01
:
?@!@@!
Zaczynając od lewej: wciśnij puste miejsce, wciśnij say , a następnie zastosuj say do pustego miejsca. To wychodzi 0
. Następnie Push powiedzieć dwa razy i zastosować drugi głos do pierwszego. To wychodzi 1
.
Ten program zapętla się na zawsze, nie dając żadnych wyników:
$+.!!+!!
Wciśnij call i klon , a następnie zastosuj do nich łańcuch (potrzebujemy dwóch !
s, ponieważ łańcuch jest funkcją binarną). Teraz stos zawiera funkcję, która pobiera jeden argument, duplikuje go i wywołuje pierwszą kopię na drugiej. Za pomocą +!!
kopiujemy tę funkcję i wywołujemy ją samą.
Ten program drukuje 0010
:
?@$.++>!.!!.!!.!!!!+?/!!!@!@>!!!
Naciśnij puste miejsce i powiedz . Następnie utwórz funkcję binarną, która kopiuje swój drugi argument b
, a następnie kopiuje pierwszy a
i komponuje go ze sobą, a następnie stosuje kompozycję do kopii b
, zwracając [a(a(b)),b]
. Zastosuj to do powiedzenia i puste, a następnie zastosuj powiedz do dwóch elementów pozostałych na stosie.
Ten program drukuje 0
. Dla każdego !!!
dołączonego do niego drukuje dodatkowy 0
.
?@+$>!>!+>!///!!>!>!.!!.!!.!!+!!!!
Naciśnij puste miejsce i powiedz . Następnie utwórz funkcję potrójną, która przyjmuje f,g,x
dane wejściowe i zwraca [f,f,g,g(x)]
. Sklonuj tę funkcję i zastosuj ją do siebie, powiedzmy , i do spacji. Ta aplikacja nie zmienia stosu, więc możemy zastosować tę funkcję ponownie tyle razy, ile chcemy.
Ten program wypisuje nieskończoną sekwencję 001011011101111...
, w której liczba 1
s zawsze wzrasta o jeden:
@?/!@>!??/!!>!+.!!.!!.!!.+>!.!!$$$$+$>!>!$>!>!+>!$>!>!>!+>!>!///!!>!>!>!.!!.!!.!!.!!.!!.!!.!!.!!.!!.!!+!!!!!
Repozytorium zawiera wersję z adnotacjami.
f(x1, x2, ..., xn)
i g(y1, y2, ..., ym)
. Wywołanie .
wyskakuje z nich obu i powoduje uruchomienie funkcji h(z1, z2, ..., zn)
. Teraz możesz pochłonąć wszystkie te argumenty, stopniowo je curry !
. Po n
takich aplikacjach pozostała funkcja miała tylko jeden argument i w tym momencie oblicza f(z1, z2, ..., zn)
(tj. f
Stosuje się do wszystkich argumentów, w których się przeglądasz), co wypycha niektóre nowe wartości, a następnie natychmiast zużywa m
wartości ze stosu i wywołuje g
je.
.
działa dokładnie tak, jak opisał Martin, z tym wyjątkiem, że jeśli f
zwraca listę mniejszą niż m
wartości, wynik jest niezdefiniowany (skład ma arity n
, więc nie może jeść więcej argumentów ze stosu). Zasadniczo dane wyjściowe f
są używane jako tymczasowy stos, na który g
jest wypychany i nakładany m
za pomocą !
, a wynik jest dodawany do głównego stosu.