Clem to minimalny język programowania oparty na stosach, oferujący funkcje najwyższej klasy. Twoim celem jest napisanie tłumacza języka Clem. Powinien poprawnie wykonać wszystkie przykłady zawarte w implementacji referencyjnej, która jest dostępna tutaj .
- Jak zwykle obowiązują standardowe luki .
- Najmniejszy wpis według liczby bajtów wygrywa.
Język Clem
Clem to język programowania oparty na stosie z pierwszorzędnymi funkcjami. Najlepszym sposobem na naukę Clem jest uruchomienie clem
interpretera bez argumentów. Rozpocznie się w trybie interaktywnym, umożliwiając grę z dostępnymi poleceniami. Aby uruchomić przykładowe programy, wpisz clem example.clm
gdzie example to nazwa programu. Ten krótki samouczek powinien wystarczyć, aby zacząć.
Istnieją dwie główne klasy funkcji. Funkcje atomowe i funkcje złożone. Funkcje złożone to listy złożone z innych funkcji złożonych i funkcji atomowych. Zauważ, że funkcja złożona nie może się zawierać.
Funkcje atomowe
Pierwszym typem funkcji atomowej jest stała . Stała jest po prostu liczbą całkowitą. Na przykład -10. Gdy interpreter napotyka stałą , wypycha ją na stos. Uruchom clem
teraz. Wpisz -10
polecenie. Powinieneś zobaczyć
> -10
001: (-10)
>
Wartość 001
opisuje pozycję funkcji na stosie i (-10)
jest stałą, którą właśnie wprowadziłeś. Teraz wpisz +11
po znaku zachęty. Powinieneś zobaczyć
> +11
002: (-10)
001: (11)
>
Zauważ, że (-10)
przesunął się na drugą pozycję na stosie i (11)
teraz zajmuje pierwszą. Taka jest natura stosu! Zauważysz, że -
jest to również polecenie zmniejszenia. Za każdym razem -
lub +
poprzedzając liczbę, oznaczają one znak tej liczby, a nie odpowiadające jej polecenie. Wszystkie pozostałe funkcje atomowe są komendami . Łącznie jest ich 14:
@ Rotate the top three functions on the stack
# Pop the function on top of the stack and push it twice
$ Swap the top two functions on top of the stack
% Pop the function on top of the stack and throw it away
/ Pop a compound function. Split off the first function, push what's left,
then push the first function.
. Pop two functions, concatenate them and push the result
+ Pop a function. If its a constant then increment it. Push it
- Pop a function. If its a constant then decrement it. Push it
< Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
> Pop a function and print its ASCII character if its a constant
c Pop a function and print its value if its a constant
w Pop a function from the stack. Peek at the top of the stack. While it is
a non-zero constant, execute the function.
Wpisanie polecenia w wierszu spowoduje wykonanie polecenia. Wpisz #
znak zachęty (polecenie duplikatu). Powinieneś zobaczyć
> #
003: (-10)
002: (11)
001: (11)
>
Zauważ, że (11) zostało zduplikowane. Teraz wpisz %
w wierszu polecenia (polecenie upuszczenia). Powinieneś zobaczyć
> %
002: (-10)
001: (11)
>
Aby wypchnąć polecenie na stos, po prostu umieść je w nawiasie. Wpisz (-)
polecenie. Spowoduje to przesunięcie operatora zmniejszania na stos. Powinieneś zobaczyć
> (-)
003: (-10)
002: (11)
001: (-)
>
Funkcje złożone
Możesz również zawrzeć wiele funkcji atomowych w nawiasach, aby utworzyć funkcję złożoną. Po wprowadzeniu funkcji złożonej w wierszu polecenia jest ona wypychana na stos. Wpisz ($+$)
polecenie. Powinieneś zobaczyć
> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>
Technicznie wszystko na stosie jest funkcją złożoną. Jednak niektóre funkcje złożone na stosie składają się z jednej funkcji atomowej (w takim przypadku uznamy je za funkcje atomowe ze względu na wygodę). Podczas manipulowania funkcjami złożonymi na stosie .
często przydatne jest polecenie (konkatenacja). Wpisz .
teraz. Powinieneś zobaczyć
> .
003: (-10)
002: (11)
001: (- $ + $)
>
Zauważ, że pierwsza i druga funkcja na stosie zostały połączone, a druga funkcja na stosie jest pierwsza na liście wynikowej. Aby wykonać funkcję znajdującą się na stosie (atomową lub złożoną), musimy wydać w
polecenie (while). w
Komenda pokaże pierwszą funkcję na stosie i wykonać go wielokrotnie tak długo, jak druga funkcja na stosie jest niezerowa stała. Spróbuj przewidzieć, co się stanie, jeśli piszemy w
. Teraz wpisz w
. Powinieneś zobaczyć
> w
002: (1)
001: (0)
>
Czy tego się spodziewałeś? Dwie liczby znajdujące się na szczycie stosu zostały dodane i ich suma pozostaje. Spróbujmy jeszcze raz. Najpierw upuszczamy zero i wciskamy 10, wpisując %10
. Powinieneś zobaczyć
> %10
002: (1)
001: (10)
>
Teraz napiszemy całą funkcję w jednym ujęciu, ale dodamy dodatkową %
na końcu, aby pozbyć się zera. Wpisz (-$+$)w%
polecenie. Powinieneś zobaczyć
> (-$+$)w%
001: (11)
>
(Uwaga: ten algorytm działa tylko wtedy, gdy pierwsza stała na stosie jest dodatnia).
Smyczki
Ciągi są również obecne. Są to głównie cukier składniowy, ale mogą być bardzo przydatne. Gdy interpreter napotka ciąg, wypycha każdy znak od ostatniego do pierwszego na stos. Wpisz, %
aby usunąć 11 z poprzedniego przykładu. Teraz wpisz 0 10 "Hi!"
polecenie. 0
Wstawi terminator NULL i 10
będzie wstawić znak nowej linii. Powinieneś zobaczyć
> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
>
Wpisz, (>)w
aby wydrukować znaki ze stosu, aż napotkamy terminator NULL. Powinieneś zobaczyć
> (>)w
Hi!
001: (0)
>
Wnioski
Mam nadzieję, że powinno to wystarczyć do rozpoczęcia pracy z tłumaczem. Projekt języka powinien być stosunkowo prosty. Daj mi znać, jeśli coś jest strasznie niejasne :) Kilka rzeczy pozostało celowo niejasnych: wartości muszą być podpisane i co najmniej 16 bitów, stos musi być wystarczająco duży, aby uruchomić wszystkie programy referencyjne itp. Wiele szczegółów nie zostało wyrzeźbionych tutaj, ponieważ pełna specyfikacja języka byłaby zbyt duża do opublikowania (a jeszcze jej nie napisałem: P). W razie wątpliwości naśladuj implementację referencyjną.
źródło
Odpowiedzi:
Haskell,
931921875nie jest jeszcze w pełni golfa, ale prawdopodobnie nigdy nie będzie. Mimo to jest już krótszy niż wszystkie inne rozwiązania.
Niedługo będę grać w golfa.Nie mam już ochoty grać w golfa.prawdopodobnie ma kilka subtelnych błędów, ponieważ nie grałem z referencyjną implementacją C.
to rozwiązanie używa tego typu
StateT [String] IO ()
do przechowywania „uruchomialnego” programu clem. większość programu jest parserem, który analizuje „program uruchamialny”.w celu uruchomienia tego użycia
r "<insert clem program here>"
.źródło
Python,
16841281 znakówWykonałem wszystkie podstawowe rzeczy związane z golfem. Uruchamia wszystkie przykładowe programy i dopasowuje wyjściowy znak po znaku.
Testowanie :
Zbierz clemint.py , clemtest_data.py , clemtest.py i skompilowany
clem
plik binarny do katalogu i uruchomclemtest.py
.Wyjaśnienie :
Najbardziej niestosowana wersja to ta . Śledź wraz z tym.
S
to główny stos. Każda pozycja na stosie ma 3 listy, jedną z:Dla stałych
f
jest funkcją, która wypycha stałą na stos. Dla atmoics,f
to funkcja wykonuje jedną z operacji (na przykład-
,+
). Dla związkówfs
jest lista pozycji.xec
wykonuje element. Jeśli jest stała lub atomowa, po prostu wykonuje funkcję. Jeśli jest złożony, jeśli nie było jeszcze rekurencji, wykonuje każdą funkcję. Więc wykonanie(10 20 - 30)
będzie wykonanie każdej z funkcji10
,20
,-
, i30
, pozostawiając10 19 30
na stosie. Jeśli nastąpiła rekurencja, to po prostu wypycha funkcję złożoną na stos. Na przykład podczas wykonywania(10 20 (3 4) 30)
wynik powinien być następujący10 20 (3 4) 30
: nie10 20 3 4 30
.Zagnieżdżanie było trochę trudne. Co robisz czytając
(1 (2 (3 4)))
? Rozwiązaniem jest mieć stosy. Na każdym poziomie zagnieżdżania na stosie umieszczany jest nowy stos, a wszystkie operacje wypychania trafiają na ten stos. Ponadto, jeśli zostało zagnieżdżone, funkcje atomowe są wypychane zamiast wykonywane. Więc jeśli widzisz10 20 (- 30) 40
,10
jest wciśnięty, a następnie20
, po czym nowy stos jest tworzony,-
a30
są wypychane na nowy stos i)
Zdejmuje nowego komina, zamienia ją w danej pozycji i odkłada ją na stosie o jeden poziom niżej.endnest()
uchwyty)
. To było trochę trudne, ponieważ istnieje szczególny przypadek, w którym tylko jeden przedmiot został popchnięty, a my wracamy na główny stos. Oznacza to, że(10)
powinien popchnąć stałą10
, a nie kompozyt z jedną stałą, ponieważ wtedy-
i+
nie działają. Nie jestem pewien, czy jest to zgodne z zasadami, ale tak to działa ...Mój interpreter jest procesorem znak po znaku - nie tworzy tokenów - więc liczby, łańcuchy i komentarze były nieco denerwujące. Istnieje osobny stos,
N
dla aktualnie przetwarzanego numeru i za każdym razem, gdy przetwarzana jest postać, która nie jest liczbą, muszę zadzwonić,endnum()
aby sprawdzić, czy najpierw powinienem uzupełnić ten numer i umieścić go na stosie. To, czy jesteśmy w ciągu, czy w komentarzu, jest śledzone przez zmienne boolowskie; gdy sznurek jest zamknięty, popycha wszystkie wewnętrzne części stosu. Liczby ujemne również wymagały specjalnego traktowania.To tyle na temat przeglądu. Resztę realizacji wszystkich Zabudowy i upewniając się zrobić głębokie kopie
+
,-
i#
.źródło
C 837
Dzięki @ceilingcat za znalezienie znacznie lepszej (i krótszej) wersji
Traktuje to wszystko jako proste ciągi - wszystkie elementy stosu są ciągami, nawet stałe są ciągami.
Wypróbuj online!
Mniej oryginalna wersja mojego oryginału (w przeciwieństwie do wersji golfowej, ta drukuje stos, gdy kończy się, jeśli nie jest pusty, i przyjmuje parametr -e, abyś mógł podać skrypt w wierszu poleceń zamiast odczytywać z pliku):
źródło