Libgreattao i jego tekstowa powłoka
09.06.2015 | aktual.: 10.06.2015 09:54
W tym rozdziale opiszę sposób poruszania po powłoce wbudowanej w bibliotekę do generowania interfejsów(można ją nazywać biblioteką interakcji), zwanej libgreattao. To moja biblioteka może być użyta do komunikacji z użytkownikiem. Może ona pracować w trzech trybach: normalnym, powłoki, sieciowym. W każdym trybie sposób działania libgreattao jest inny. W tym artykule zajmę się trybem powłoki, ale po kolei.
Libgreattao pozwala aplikacjom na tworzenie okien, dodawanie do nich abstraktów(dla potrzeb libgreattao nazywam je ścieżkami zdarzeń), ustawianie atrybutów abstraktu(skojarzeń). Przykładami atrybutu abstraktu są: opis, nazwa, ikona. W powłoce nie jest istotna klasa okien - jest ona potrzebna jedynie do odwoływania się do okien, a robi się to przez ciąg nazwa_klasy_okna[numer_egzemplarzu]. Jest to również prawdą w trybie sieciowym, jednak nie jest to prawdą w trybie normalnym(w trybie normalnym jest tworzone GUI - plik klasy okna to zbiór elementów interfejsu i elementów odpowiedzialnych za przetwarzanie abstraktów i ich atrybutów, więc w tym trybie klasa okien ma znaczenie). Dla zilustrowania wszystkiego w tej kwestii:
[code=C] /* utworzenie okna odpowiedniej klasy */ void *window = tao_new_window("/desktop/dialogs/question_dialog"); /* dodawanie abstraktów */ tao_add_handler(window, "/message", NULL, NULL); tao_add_handler(window, "/actions/exit", &exit_callback wartość_przekazana_do_procedury); tao_add_handler(window, "/actions/retry", &retry_callback wartość_przekazana_do_procedury2); /* przypisywanie atrybutów do abstraktów */ tao_set_hint(window, "/message", HINT_NAME, "Błąd krytyczny"); tao_set_hint(window, "/message", HINT_DESCRIPTION "Nie można otworzyć pliku wymaganego przez aplikację"); tao_set_hint(window, "/action/exit", HINT_DESCRIPTION "Wyłącza aplikację"); tao_set_hint(window, "/action/retry", HINT_DESCRIPTION "Ponawia próbę załadowania pliku"); [/code]
W powyższym przykładzie tworzymy trzy abstrakty. Pierwszy z nich odpowiada komunikatowi, a dwa pozostałe akcjom, jakie może podjąć użytkownik. Z tego powodu w przypadku pierwszego wywołania tao_add_handler trzeci i czwarty parametr nie mają znaczenia. Dlaczego przyciskom(ehhhmmm... akcją) przypisujemy tylko opis? Bo nazwa w tym przypadku jest niepotrzebna. W powłoce trzeba będzie odwoływać się do abstraktu po oknie i ścieżce/nazwie abstraktu(drugi argument), a plik klasy okna będzie pewnie zawierać reguły do automatycznego ustawiania np. etykiet na przyciskach, np. z części nazwy/ścieżki abstraktu po ostatnim slash-u. Powłoka oczywiście nie obsługuje ikon.
Uruchamianie aplikacji w trybie powłoki tao
Libgreattao obsługuje dwa przełączniki do uruchomienia powłoki libgreattao. Są to tao‑shell-execute-file i tao‑na-shell-interactive. W drugim przełączniku użyto ciągu -na- Co on znaczy? To, że po przełączniku nie występuje argument. Postanowiłem dodać możlliwość określenia, że po przełączniku nie występuje argument na wypadek, gdybym chciał dodać możliwość podania wartości do argumentu.
Oba te przełączniki powodują, że nie zostanie aplikacja uruchomiona w trybie normalnym, a w trybie powłoki. Czym się różnią te dwa przełączniki? Pierwszy powoduje wykonanie pliku ze ścieżką podaną w parametrze występującym po tym przełączniku. Drugi powoduje uruchomienie powłoki w trybie interaktywnym. Oba te przełączniki można łączyć, co powoduje, że po wykonaniu podanego pliku, powłoka przejdzie w tryb interaktywny.
Jak wyjść z trybu interaktywnego? Wystarczy wpisać exit kod_powrotu. Jeżeli aplikacja nie zdefiniowała funkcji dla zakończenia aplikacji, to aplikacji zakończy się z podanym kodem powrotu. W przeciwnym wypadku aplikacja będzie mogła sama zdecydować, co z kodem powrotu zrobić.
Jest coś, co powoduje, że ta powłoka przypomina assemblera(analogia choć dziwna jest związane z tym, że instrukcje dostępne w assemblerze są zależne od wielu czynników, m.in od architektury procesora), bo w assemblerze na x86 trzeba było włączyć FPU, przełączyć linię A21 do zwiększenia ilości pamięci, itd. W mojej powłoce po uruchomieniu należy zrobić jedną rzecz - włączyć czasomierze, zupełnie jak na ARM‑ach.
O języku
Tworząc język chciałem pójść po najmniejszej linii oporu, dodając możliwie najwięcej funkcjonalności. Stąd np. liczby wewnętrznie są przechowywane w postaci ciągu znaków - nie można utworzyć zmiennej liczbowej, ale można za to utworzyć zmienną tekstową. Innymi skutkami takiej decyzji jest to, że język przypomina assemblera - nie ma znaków działań - by dodać dwie liczby, to należy wprowadzić:
=add nazwa_zmiennej_wynikowej wartość1 wartość2
No dobra - można utworzyć zmienną liczbową o nazwie "i" i wartości 2 w taki sposób:
=add i 2 0
;‑D . Jednak dodałem fajne mechanizmy, jakie są w językach interpretowanych, jak zakleszczenia, słowniki czy rozwijanie zmiennych. Jeżeli ktoś chce mieć tablice, to może dołączyć plik znajdujący się w katalogu ze źródłami libgreattao, w podkatalogu shell-utils. Plik ma nazwę types, a obecnie dostarcza tylko obsługę tablic. Tablica jest rodzajem słownika, więc mogłem zaimplementować tablice wykorzystując słownik.
Jeszcze nie mam nazwy języka wykorzystywanego przez libgreattao. Wymyśliłem dwie: "goto language" i "scope switcher" - mam nadzieję, że po przeczytaniu całego artykułu zaproponujesz własną lub oddasz głos na już zaproponowaną nazwę dla mojego języka. Interpreter poleceń ma blisko 6500 linijek, jednak w pliku z kodem trzymam deklaracje funkcji, które należałoby odliczyć.
Ograniczenia
- Nie można podstawiać zmiennych pod nazwy funkcji/bloków kodu
- Nazwy modułów nie mogą się zaczynać od znaku @ lub .
- Nazwy zmiennych nie mogą się zaczynać od kropki - jedynie zmienne wbudowane mogą się zaczynać od kropki
- Nazwy etykiet nie mogą zaczynać się od kropki - jedynie specjalne nazwy etykiet mogą zaczynać się od kropki
- Maksymalna długość polecenia to 1024 znaki
Wprowadzanie komend i o komendach ogólnie
Komendy w trybie interaktywnym wprowadza się, zatwierdzając każdorazowo wprowadzaną komendę klawiszem enter/return. Są jednak przypadki, gdy wciśnięcie jednego z tych przycisków nie spowoduje uruchomienie komendy, a oczekiwanie na dalszy ciąg komendy. Dzieje się tak, gdy przed naciśnięciem jednego ze wspomnianych klawiszy wprowadziliśmy backslash, otwarty jest cudzysłów lub apostrof, albo ilość nawiasów domykających nie ujętych w cudzysłowia nie jest równa ilości nawiasów otwierających nie ujętych w cudzysłowia. Uwaga: przeprzedzenie znaku znakiem baskslash powoduje, że znak po nim traci swoje znaczenie. Tak więc ciąg \' tak jakby nie zawierał apostrofu, analogicznie należy podjeść do ciągów \", \( i \). Jeżeli naciśniemy przycisk enter w momencie, gdy powłoka oczekuje czegoś innego, np. zamknięcia cudzysłowia, to w miejsce entera/returna zostanie wstawione przejście do nowej linii.
Parametry
Przed każdym parametrem, poza pierwszym, musi wystąpić domyślny separator dla bieżącego polecenia. Nawias/apostrof nie pełni swojej roli, jeśli przed nim lub po nim występuje jakiś inny znak niż domyślny separator dla bieżącego polecenia. Dla przykładu:
echo a"
Wyświetli:
a"
(w bloku powyżej powinno być a", ale blog ma jakiś błąd i tego nie wyświetla) Natomiast
echo "a"
Wyświetli:
a
Nawiasy rządzą się oddzielnymi prawami i każdy nawias musi być zamknięty.
Podstawianie zmiennych
By podstawić zmienną z bieżącego zakresu, należy poprzedzić jej nazwę znakiem dolara. Ujęcie nazwy zmiennej z poprzedzającym znakiem dolara w cudzysłów/apostrof/nawias lub poprzedzenie znaku dolara backslashem spowoduje, że w miejsce parametru nie zostanie podstawiona nazwa zmiennej.
Podanie wielu znaków dolara przed nazwą zmiennej ma specjalne znaczenie. W kodzie powłoki jest pewna funkcja, która wykonuje polecenia, i to ona podstawia zmienne w miejsca wystąpienia ich nazw, wywołując inną funkcję. Ta "inna funkcja" sprawdza czy po znaku dolara nie występuje następny znak dolara. Jeśli tak, to nie podstawia zmienną pod nazwę, a tylko odejmuje jeden znak dolara. Ponieważ komendy, jak scopelevel, execute, jak i inne, wywołują ponownie funkcję wykonującą polecenia, to w połączeniu z odpowiednimi komendami można to wykorzystać to do niecnych celów, jak np. dokonanie operacji na zmiennej leżącą kontekst niżej. Jeśli więc w wywołaniu funkcji a występuje wywołuje funkcję b, to dzięki scopelevel mamy dostęp do zmiennych funkcji a, np.:
function a =text a 1 function b =text a 3 scopelevel -1 =add a $$a $a endblock b b echo $a endblock a a
Spowoduje wyświetlenie cyfry 4! Pozwoliło mi to na pozbycie się konieczności implementacji tysiąca drobiazgów, jak referencje czy instrukcji retrun wartość. Funkcje mogą zwracać dowolną ilość rezultatów do dowolnie niskiego poziomu.
Wprowadzanie bloków kodu
Po napotkaniu słowa kluczowego function lub block z następującą po nim nazwą nowo tworzonej funkcji/tworzonego bloku kodu, nastąpi oczekiwanie na wprowadzenie instrukcji. Wprowadzanie instrukcji należy zakończyć słowem kluczowym endblock z opcjonalną nazwą kończonego bloku. Podanie nazwy kończonego bloku pozwala na wykrycie, przez interpreter, błędu nieprawidłowego zamknięcia bloku, a także zwiększa czytelność.
Nawiasy
Nawias jest normalnym ciągiem znaków. Jeżeli jednak jako jedyny parametr(w miejscu nazwy polecenia) występuje nawias, to zawartość nawiasu zostanie użyta, jako polecenie. W takim wypadku, tuż po otwarciu nawiasu musi wystąpić jakiś znak, który będzie domyślnym separatorem parametrów dla bieżącego polecenia, np. można wprowadzić:
(\n echo To jest tekst)
I spowoduje to wyświetlenie "To jest tekst". Inne przykłady poleceń, które zrobią to samo:
echo "To jest tekst"
(; echo;To jest tekst)
Pusty parametr
By uzyskać pusty parametr, można wykonać jedno z:
- Wstawić pusty cudzysłów lub pusty apostrof
- Wstawić dwa domyślne separatory bieżącego polecenia obok siebie, ale w przypadku, gdy domyślnym separatorem bieżącego polecenia nie jest spacja
Dla przykładu
echo ""
(\n echo )
(W powyższym nawiasie występują dwa znaki nowej linii)
(; echo;;)
Wybrane polecenia
Powłoka libgreattao powstała do sterowania aplikacją, więc podstawowym poleceniem jest na pewno polecenie uruchamiające akcję zdefiniowaną dla danego abstraktu. Jest nią run. Jako pierwszy parametr należy podać okno, a jako drugi nazwę abstraktu(ścieżkę zdarzenia). Oto przykład dla programu główny.o uruchamiający program edytor.o, a następnie:kończący program główny.o. Źródła obu programów są zamieszczone w katalogu Demos źródeł libgreattao,.
run /desktop/dialogs/question_dialog[1] /actions/Button3 run /desktop/dialogs/question_dialog[1] /actions/exit
Ponieważ wspomnieliśmy już o zmiennych, to można to uprościć:
=window main_window /desktop/dialogs/question_dialog[1] run $main_window /actions/Button3 run $main_window /actions/exit
Podanie ciągu znaków jako drugi argument do polecenia run spowoduje, że odpowiednie okno zostanie automatycznie wyszukane - jeżeli nie zostanie znalezione, to wystąpi błąd, ale o błędach nie teraz. Błędy wystąpią za każdym razem, gdy do polecenia podajemy wartość o typie, który nie może zostać przekonwertowany do oczekiwanego typu, np. =add i $a "Tutaj wystąpi błąd". Polecenie =window:
=window nazwa_zmiennej ścieżka_do_okna[numer_okna_o_tej_ścieżce]
Spowoduje wyszukanie zmiennej o podanej nazwie w bieżącym bloku kodu. Jeżeli zmienna o podanej nazwie nie istnieje w bieżącym bloku kodu, to zostanie utworzona. Jeżeli w bieżącym bloku kodu istnieje zmienna o podanej nazwie, lecz innym typie niż okno, to wystąpi błąd.
Polecenia rozpoczynające się od znaku równości dokonują przypisania wartości do zmiennej o nazwie podanej jako drugi argument(pierwszym jest nazwa polecenia). Polecenia rozpoczynające się wykrzyknikiem powodują iterowanie po jakiś wartościach, np. !event /desktop/dialogs/question_dialog[1] a "echo $a" spowoduje wyświetlenie nazw wszystkich abstraktów wskazanego okna.
Zmienne
Powłoka libgreattao ma wbudowane następujące typy zmiennych:
- Ciąg znaków(string)
- Okno(window)
- Słownik(dictionary)
Dodatkowe dwa typu są tak naprawdę ciągiem znaków, lecz jeśli polecenie wymaga danego type, to powłoka spraedzi czy wartość danego typu da się przekonwertować do wymaganego typu. Tym dwami typami są:
- Liczba naturalna(unsigned)
- Liczba całkowita(signed
Do usuwania zmiennych z bieżącego bloku kodu służy polecenie unset .
=text tekst "To jest tekst" unset teskt echo $tekst
Powyższy kod, naturalnie, spowoduje błąd.
Rozwijanie/rozszerzanie zmiennych
Przypuśćmy teraz, że mamy zmienną zawierającą dwa parametry, jak ścieżkę okna i nazwę abstraktu. Chcemy teraz wykonać polecenie run z tymi dwoma parametrami. Można to osiągnąć stosując rozszerzenie zmiennej. Robi się to wstawiając .:~ za ostatni znak dolara, poprzedzający nazwę zmiennej. Oto przykład:
=text parametry "/desktop/dialogs/question_dialog[1] /actions/Button3" run $.:~parametry
Przypisywanie i usuwanie zmiennych
Jeżeli zmieniasz wartość istniejącej zmiennej, to tak, jakby poprzednia zmienna była usuwana. Tutaj masz listę tego, co się dzieje podczas tworzenia i usuwania zmiennych określonego typu:
- String usuwanie - wartość jest zwalniana z pamięci; String tworzenie - wartość jest kopiowana
- Window usuwanie - zmniejsza licznik referencji danego okna - jeżeli licznik osiągnął 0, a okno jest przeznaczone do usunięcia przez program, to je usuwa; Window tworzenie - zwiększa licznik referencji danego okna.
- Dictionary usuwanie - wartość jest zwalniana z pamięci; Dictionary tworzenie - wszystkie zmienne ze słownika źródłowego są kopiowane
Słowniki
Wspomnieliśmy o tym, jaki ciąg znaków może zostać przekonwertowany na referencję do okna. Teraz wspomnę o tym, jaki ciąg znaków może zostać przekonwertowany na słownik. Taki ciąg powinien wyglądać tak:
[klucz1 = wartość1 [klucz2 = wartość2 ... [kluczN = wartośćN]]]
Czyli np:
okno = /desktop/dialogs/question_dialog[1] action = /actions/exit
Tam, gdzie są spacje, to należy postawić spacje. Nie można używać podstawień zmiennych podczas tworzenia słownika. Jeżeli chcesz dodać element z użyciem zmiennej/zmiennych jako klucz/wartość, to utwórz pusty słownik i posłuż się appendtodict. Poniżej prezentuję przykład:
=dictionary nazwa_słownika "" appendtodict nazwa_słownika klucz wartość
By pobrać wartość ze słownika, należy posłużyć się =fromdict, np.
=fromdict a $słownik nazwa_klucza
Znak dolara przed słownikiem jest celowy, gdyż komenda ta, w przeciwieństwie do większości komend operujących na słownikach, nie przyjmuję nazwy zmiennej przechowującej słownik, lecz sam słownik. Możemy w miejsce $słownik podstawić ciąg znaków reprezentujący słownik i z niego pobrać wartość.
By usunąć coś ze słownika, to należy posłużyć się komendą removefromdict:
removefromdict nazwa_słownika nazwa_klucza
Słowniki można rozwijać w ten sam sposób , co ciągi znaków. Kolejność podawania elementów na wyjście jest związana z kolejnością ich dodawania. Implementacja tablic jest taka, że przy usuwaniu elementu kolejność jest zachowana.
Bloki kodu i funkcje
Są dwa typy bloków kodu(można utworzyć jeszcze niby jeden, korzystając z nawiasów i polecenia execute-all, ale o tym później), jednak te dwa typy bloków kodu mogą realizować wszystko to, co języki strukturalne, gdyż obsługują polecenia: continue, break, return. Te polecenia opiszę przy okazji instrukcji warunkowych. Dzięki scopelevel lub scopebyname możemy uzyskać coś podobnego do czegoś, co obsługuje Java, czyli break lub continue względem bloku kodu. W przeciwieństwie do Javy, obsługujemy goto.
Jakie obsługujemy typy bloków kodu? Zwykły blok kodu i funkcja. Jak wyglądają oba bloki przedstawiłem już wcześniej. Czym się one jednak różnią? Zwykły blok kodu jest wykonywany, gdy po miejscu zakończenia poprzednio wykonanej instrukcji znajdzie się blok kodu, a my nie użyliśmy instrukcji call, callonce, goto, throwerror, return, break, continue lub instrukcji warunkowej. Funkcja jest wykonywana w momencie wywołania i możliwa jest w niej rekursja. Poza tym, to podczas tworzenia funkcji tworzony jest słownik $.unnamed_params i dodawane do niego są przekazane parametry pozycyjne. Parametry pozycyjne to te, które zostały podane po nazwie funkcji. Dodatkowa różnica polega na tym, że tylko w bloku funkcji lub dziecka tego bloku możemy używać instrukcji functionuse i usemodule. O tych dwóch instrukcjach napiszę później.
Każda funkcja i blok kodu muszą posiadać nazwę. Nazwy mogą się powtarzać.
Etykiety i goto
Etykiety i goto służą do zmiany przebiegu programu. Etykiety możemy tworzyć jedynie w obrębie jakiejś funkcji lub bloku kodu. Jest to spowodowane chęcią uproszczenia implementacji i redukcji użycia pamięci. Wyobraź sobie, że etykietę możesz wstawić gdziekolwiek - wtedy bym musiał przewidywać, jaką etykietę możesz wywołać i jak może wyglądać wykonanie programu po wywołaniu etykiety, lub pamiętać dosłownie każdą wprowadzoną instrukcję. Tryb zapamiętywania instrukcji jest włączany dopiero po napotkaniu słowa kluczowego block lub function, zwiększając odpowiedni licznik po napotkaniu jednego z tych słów kluczowych, a zmniejszając po napotkaniu endblock. Jeżeli licznik ten osiągnie 0, to tryb zapamiętywania jest wyłączany. W takim wypadku nie ma sensu dawać możliwości tworzenia etykiet gdziekolwiek.
Teraz przedstawię dwa przykłady pętli nieskończonej.
block nieskończona_pętla echo Wystartowała =text komunikat "Iteracja " =text i 1 : iteracja =cat ile_razy $komunikat $i echo $ile_razy goto iteracja endblock nieskończona_pętla
block nieskończona_pętla function następna scopelevel -1 goto iteracja endblock następna echo Wystartowała =text komunikat "Iteracja " =text i 1 : iteracja =cat ile_razy $komunikat $i echo $ile_razy następna endblock nieskończona_pętla
Jeszcze jeden przykład:
block nieskończona_pęta echo Wystartowała =text komunikat "Iteracja " =text i 1 block iteracja =cat ile_razy $komunikat $i echo $ile_razy continue endblock iteracja endblock nieskończona_pętla
Return i break
Return i break są różne. Ich znaczenie jest mało intuicyjne, ale wzięło się to z przyzwyczajeń programistów języków strukturalnych. Otóż break wcale nie powoduje opuszczenia bieżącego bloku. Można za pomocą instrukcji call i callonce wywołać etykietę w bieżącym bloku. Różnica jest taka, że break wyjdzie z biezącego wywołania, a return opuści blok. Jest to wykorzystywane przy wywoływaniu etykiety .initialize, jeżeli interpreter napotka instrukcje endconditionlist, ale o tym w poleceniach warunkowych.
W czym są one podobne? Jeżeli użyto return, lub nie wykonaliśmy żadnych skoków wewnątrz bieżącego bloku, to obie instrukcje powodują skok przed instrukcję endblock bieżącego bloku, czyli jego opuszczenie. Oczywiście - bieżący blok możemy zmienić za pomocą scopelevel lub scopebyname, dając podobne efekty do tych z Javy.
Parametry funkcji
Funkcja ma dwa typy parametrów - nazwane i pozycyjne. Parametry pozycyjne podajemy w ten sposób:
Nazwa_Funkcji [parametr1 [parametr2 ... [parametrN]]]
W jaki sposób podajemy parametry nazwane? W oto taki sposób:
appendtodict .named_params nazwa_parametru1 wartość1 appendtodict .named_params nazwa_parametru2 wartość2 Nazwa_Funkcji
Zadziała to tylko wtedy, gdy zmienna $.named_params istnieje. Wcześniej trzeba ją utworzyć:
=dictionary .named_params ""
Zmienne o tej nazwie to jedyny przypadek, kiedy interpreter nie zablokuje nam możliwości jej utworzenia lub zmiany wartości. Parametry nazwane mogą posłużyć do symulacji this z języków obiektowych, gdyż słownik parametrów nazwanych będzie wykorzystywany przez kolejne wywołania funkcji w tym samym kontekście, lecz również możemy opróżnić słownik, tworząc nowy na jego miejsce.,
Możemy jednocześnie przekazać funkcji parametry nazwane i nienazwane.
Jak jednak obsłużyć parametry wewnątrz funkcji? Sposoby są przeróżne. Pierwszym z nich jest wykorzystanie zmiennej $.@. Zawiera ona to, co wpisaliśmy w wierszu polecenia przy wywołaniu funkcji, poza nazwą funkcji. Ma to jednak sporo wad. Otóż podstawienie zmiennej $.@ pod wywołanie funkcji spowoduje, że zmienna wewnątrz wywołanej funkcji będzie mieć ciąg $.@ jako parametr. Jest to zła technika. Jeżeli chcesz z niej korzystać, to przed wywołaniem funkcji z wykorzystaniem zmiennej $.@, należy posłużyć się wywołaniem funkcji expandvariablesandcallfunction z pliku shell-utils/functions, który leży w katalogu ze źródłami libgreattao. Tak to powinno wyglądać:
expandvariablesandcallfunction nazwa_funckji $.@
Możemy wykorzystać =shellparsestring i =shellgettokencount. W poniższy sposób wyświetlimy każdy element przekazany w wierszu polecenia:
function echo_all =shellgettokencount i $.@ =text j 0 block process ifintless $j $i =add j $j 1 =shellparsestring wynik $j $.@ echo $wynik continue : .false endblock process endblock echo_all
Jednak o warunkach napiszę potem. Jeżeli jednak chcemy otrzymywać argumenty nazwane i pozycyjne po przetworzeniu? Mamy dwa słowniki - łatwo się domyśleć jednego. Pierwszym słownikiem jest $.named_params, dostępny jednak kontekst niżej od kontekstu wywołania. Drugim słownikiem jest $.unnamed_params, który jest z kolei dostępny w bieżącym kontekście. Drugi słownik zawiera numerowane parametry pozycyjne, zaczynając od liczby 1. Są jeszcze dwie zmienne tworzone podczas wywołania funkcji: $.unnamed_param_count i $.named_param_count. Zawierają one ilość parametrów w słownikach kolejno: pozycyjnych i nazwanych. Można się tym bawić, ale jest prostsza i bardziej czytelna metoda - funkcja paramrequires. Jest ona zdefiniowana w pliku shell-utils/functions w katalogu ze źródłami libgreattao. Tak wygląda definicja dwóch funkcji run. Druga z nich przyjmuje dwa parametry: nazwę abstraktu i okno. Następnie wywołuje ona wbudowaną funkcję run, podając swoje parametry w odwrotnej kolejności. Pierwsza funkcja po prostu wywołuje run. Oto przykład:
function run paramrequires positioned_window window positioned_string path buildin run $window $path endblock function run errordisplaymode void paramrequires any_string path any_window window errordisplaymode all buildin run $window $path break : .error errordisplaymode all next endblock
Użyto tutaj obsługi błędów i instrukcji next. O obsłudze błędów napiszę później, a teraz zajmijmy się instrukcją next. Wywołuje ona kolejną, dostępną w bieżącym zasięgu funkcję o tej samej nazwie. Możemy w takim wypadku wywołać run podając w dowolnej kolejności okno i nazwę abstraktu, bo w przypadku podania najpierw okna, paramrequires zwróci błąd(zaraz paramrequires omówimy), więc zostanie wywołana pierwsza funkcja z tymi samymi parametrami. Next może się okazać bardzo pomocne do tworzenia szalonych rzeczy, jak choćby funkcji-owijek, np. do debugowania(wyświetla wszystkie podane parametry, wiersz poleceń, itd.).
Teraz omówimy paramrequires. Pisząc o parametrach nie będę teraz wliczać nazwy funkcji. Do paramrequires należy przekazać parzystą liczbę parametrów. Każdy nieparzysty parametr zawiera źródło parametru, zakończone znakiem podkreślenia i typ parametru po nim. Każdy parzysty parametr to nazwa nowo tworzonej zmiennej. Jeżeli nie dopełnimy tych warunków, to paramrequires rzuci wyjątek. Obsługiwane źródła to: any(parametry pozycyjne i nazwane), positioned(parametry pozycyjne), named(parametry nazwane). Obsługiwane typy to: string, window, unsigned, signed. Dla źródła any, paramrequires w pierwszej kolejności szuka elementu o nazwie zmiennej do utworzenia w słowniku parametrów nazwanych, a jeżeli nie znajdzie, to pobiera kolejny parametr z parametrów pozycyjnych, zaczynając od 1. Oczywiście, że przed tym procesem, dodaje ilość parametrów nazwanych i pozycyjnych przekazanych do kontekstu, z którego została wywołana, i sprawdza czy jest równa ilości parametrów przekazanych do siebie podzielić przez 2.
Jeżeli chcemy wywołać paramrquires z bloku kodu wewnątrz funkcji, to możemy to zrobić tak:
scopetype readvariable scopebyname nazwa_funkcji paramrequires źródło1_typ1 nazwa1 ...
Dla przykładu:
function błąd =text kod_błędu "" block jeśli_dwa_parametry ifinteq $.unnamed_param_count 2 varscopelevel -1 kod_błędu varscopelevel -1 komunikat scopetype readvariable scopevbyname błąd paramrequires positioned_unsigned kod_błędu positioned_string komunikat break : .false varscopelevel -1 komunikat scopetype readvariable scopevbyname błąd paramrequires positioned_string komunikat endblock jeśli_dwa_parametry =cat kod_błędu $kod_błędu " " =cat komuniakat $kod_błędu $komunikat echo $komunikat endblock
varscopelevel działa podobnie, jak scopelevel, tylko powoduje, że wszystkie odwołania do zmiennej o wskazanej nazwie będą rozpatrywane pod kątem wskazanego poziomu.
Block swich
Z pomocą libgreattao możemy utworzyć prosty block switch. Będzie on działać, jak blok powłoki libgreattao, lecz również jak blok switch. Oto przykład:
block nasz_switch ghostmode goto $i : .1 block 1 echo "wybrano 1" endblock break : .2 block 2 echo "wybrano 2" endblock break : .3 block 3 echo "wybrano 3" endblock break : .error echo Default endblock
W przypadku braku etykiety nastąpi skok do do etykiety .error. Instrukcja ghostmode powoduje, że blok nasz_switch będzie pomijany przy zasięganiu zasięg wyżej, jak choćby skok z bloku 3 do etykiety .error bloku wyżej, blok nasz_switch będzie pomijany. Eliminuje to skok do .error bloku nasz_switch w przypadku błędu w którymś z dzieci.
Warunki i pętle
W powłoce libgreattao instrukcje warunkowe są normalnymi instrukcjami, lecz zaczynającymi się od prefixu if. Muszą one zachowywać się odpowiedni sposób - to znaczy, że w przypadku napotkania fałszu, skaczą one poziom wyżej do etykiety false. To wszystko! Możesz samemu pisać własne typy instrukcji warunkowych. Jeżeli chcemy utworzyć etykietę .true i .initialize, a dodatkowo, by etykieta .initialize była wywoływana tylko przy pierwszym sprawdzeniu warunków, to możemy umieścić za listą warunków instrukcję endconditionlist. Etykiety .true jednak w większości przypadków nie będzie potrzebna, gdyż warunki są po prostu normalnymi instrukcjami - jeżeli warunek jest prawdziwy, to zostanie wykonana kolejna instrukcja. To sprawia, że:
- Warunki połączone są koniunkcją logiczną
- Warunki działają, jak w C(przynajmniej w nowszych kompilatorach) i Pythonie, czyli są wykonywane dopóki któryś z warunków nie będzie fałszywy
Dzięki takiemu podejściu łatwo można zaimplementować negację. Oto implementacja negacji:
function not execute $.@ scopelevel -1 goto .false : .false endblock not
Negacja jest zaimplementowana w pliku shell-utils/logic w katalogu ze źródłami libgreattao. W tym samym pliku jest też zaimplementowane OR i AND.
Assercje
W pliku shell-utils/logic w katalogu ze źródłami libgreattao są również zaimplementowane assercje. Assercja nic innego nie robi, tylko powraca po prawidłowym wywołaniu przekazanej jej instrukcji, a w przypadku fałszu, skacze do etykiety .error poziom wyżej. Wcześniej zapomniałem dodać, że brak jakieś etykiety skutkuje rzuceniem wyjątku. Jest też assert_verbose, która w przypadku fałszu dodatkowo wypisuje, co jej podaliśmy jako polecenie do wykonania.
Przykłady pętli
Pętla while:
block pętla_while warunek1 warunek2 ... warunekN instrukcje continue : .false endblock
Pętla do-while:
block pętla_do_while instrukcje warunek1 warunek2 ... warunekN continue : .false endblock
Pętla repeat unitl:
block repeat_until : .false instrukcje warunek_zakończenia endblock
Pętla for:
block pętla_for inicjalizacja goto pierwsza_iteracja : iteracja np.zwiększenie_licznika : pierwsza_iteracja warunek1 warunek2 ... warunekN instrukcje goto iteracja endblock
Dla zwiększenia czytelności pętli zastanawiam się nad wprowadzaniem możliwości stosowania etykiet .continue, do której skakałaby instrukcja continue, lecz w przypadku jej braku, skakałaby do do początku bloku(tak, jak obecnie).
Ładne formatowanie warunków
Lista warunków w języku libgreattao wygląda ładnie. Jednak co z funkcjami OR lub AND? Można im przekazać listę warunków w następujący sposób:
OR "ifstreq a b" "ifstreq a c"
Jednak to nie wygląda pięknie. Innym sposobem jest skorzystanie z nawiasów, o tak:
(\n OR ifstreq a b ifstreq a c)
Przed OR‑em można wstawić znak przeniesienia do nowej linii, jako iż znak przeniesienia do nowej linii jest domyślnym separatorem. To samo możemy zrobić w przypadku nawiasu zamykającego. Pamiętać jednak należy, by nie wstawiać znaków przeniesienia do nowej linii obok siebie, gdyż takie coś będzie oznaczać parametr pusty i być może spowoduje błąd.
Domknięcia
Domknięcia umożliwiają zwrócenie funkcji z innej funkcji, jednak zwracana funkcja będzie móc korzystać z parametrów przekazanych do funkcji zwracającą funkcję. Taki parametr musi wcześniej zostać zapamiętany w specjalnej strukturze zwracanej funkcji. Dla przykładu możemy napisać funkcję, która pozwala na podnoszenie liczby przekazanej jako parametr do zapamiętanej potęgi. Oto przykład:
function create_power =fromdict power $.unnamed_params 1 scopelevel -1 function power functionuse power =fromdict number $.unnamed_params 1 scopelevel -1 =text power 1 block wykonaj ifintless 1 $power =sub power $power 1 scopelevel -2 =mul power $$power $number continue : .false endblock endblock power endblock create_power
Ważna jest instrukcja functionuse. Powoduje ona zapamiętanie zmiennej power z funkcji create_power do struktury reprezentującej zwracaną funkcję(o nazwie power). To się dzieje podczas zapamiętywania/tworzenia funkcji. Natomiast podczas wykonania powoduje to przeniesienie zmiennej o nazwie power ze struktury związanej z funkcją power do kontekstu/zasięgu obecnego bloku. Wykonanie poniższego kodu wywoła wyjątek:
function a =text a s echo $a functionuse a endblock a
Będzie to spowodowane tym, że zmienna a nie będzie dostępna w momencie napotkania instrukcji echo.
Moduły
Pliki w libgreattao możemy includować tak:
script ścieżka_do_pliku
Jednak można także ładować moduły. O tak:
loadmodule ścieżka_do_pliku wewętrzna_nazwa_modułu
Każdy plik zawierający poprawne instrukcje libgreattao można includować pierwszą lub drugą metoda. Druga metoda powoduje, że zmienne i funkcje, które podczas przetwarzania pliku nie zostały utworzone/zmodyfikowane z użyciem instrukcji zmiany kontekstu/poziomu, będą prywatne. Dla zrozumienia tego mechanizmu warto pobrać ze strony domowej libgreattao menadżer plików tao‑manager/tao-file-manager w wersji drugiej i przyjrzeć się modułowi libgreattao tego programu. Możliwość ładowania modułów i dodatkowych skryptów powstała nie tylko po to, by użytkownik mógł sobie ułatwić użytkowanie wielu aplikacji, ale powstało również po to, by aplikacje mogły dostarczać przyjazne nazwy funkcji, m.in ułatwiając pracę użytkownikowi, jak również dostarczając stabilniejsze API. Dla przykładu moduł powłoki libgreattao dostarcza takie funkcje, jak ls, copy, rm, cd,. pwd, więc jest parę poleceń przypominających te z uniksowych powłok.
Instrukcja usemodule
Instrukcja usemodule jest przeznaczona specjalnie dla bloków kodu. Powoduje ona, że poziom niżej jest wklejany moduł o nazwie podanej, jako drugi parametr. Jeżeli funkcja wcześniej nie wywołala usemodule, to ten wybrany moduł jest dopinany do łańcucha kontekstów. Jeżeli funkcja wcześniej wywołała usemodule, to moduł jest zamieniany. Wraz z scopelevel, pozwala to w prosty sposób osiągnąć coś na wzór zmiennych prywatnych i publicznych funkcji, które odwołują się do zmiennych prywatnych. By utworzyć funkcję publiczną dla modułu, to należy się posłużyć domknięcie, korzystając ze zmiennej $.module_name, wskazującą na nazwę podaną jako trzeci parametr dla funkcji loadmodule.
Instrukcja hascurrentfiledescriptorflag
Instrukcja służy do sprawdzania czy struktura reprezentująca obecnie aktywnego deskrypora pliku ma określoną flagę. Po załodowaniu pliku lub wystartowaniu w trybie interaktywnym, tworzona jest struktura reprezentująca odpowiedni deskryptor pliku. Może ona posiadać kilka flag, które mówią np., że obecnie powłoka powinna pracować w trybie interaktywnym, debugowania lub oznacza, że obecnie wczytywany jest moduł. Pozwala to na warunkowe przygotowanie modułu do pracy jako moduł - można to wykorzystać by ustawić zmienną podstawianą do polecenia scopelevel, występującego przed definicjami rzeczy publicznych. Jeżeli chcemy, by dana rzecz była publiczna, to scopelevel występujący przed jej definicją powinien mieć parametr -1, jeśli plik został załadowany jako moduł. W przeciwny wypadku do scopelevel należy przekazać parameter 0.
Po co moduły
Na przykładzie modułu powłoki libgreattao drugiej wersji menadżera plików, podam przykład zmiennej currentview. Jeżeli plik zostanie załadowany za pomocą script, to zmienna będzie publiczna, a jeżeli za pomocą loadmodule, to zmienna będzie prywatna. Moduły trochę przypominają obiekty, które nie dopuszczają do swoich zabawek.
Wyjątki
W powłoce libgreattao istnieją cztery typy zachowań po napotkaniu wyjątku: throw, continue, exit, debug. Pierwszy powoduje skok do etykiety .error w bieżącym bloku, ale tylko w przypadku, gdy bieżący kontekst/poziom nie ma ustawionej flagi błędu(domyślnie flaga jest wyzerowana). Poza tym ustawia tą flagę. Gdy jednak flaga jest ustawiona, to skacze do etykiety .error w bloku niżej, jeżeli zachowanie dla obsługi błędów w tym bloku to również throw. Jeżeli etykieta .error nie istnieje w bloku branym pod uwagę, to powoduje to kolejny wyjątek, więc automatycznie(z powodu ustawieina wspomnianej flagi), throw szuka etykiety .error wyżej. Jeżeli throw przeszukiwał któryś blok w poszukiwaniu .error, a zachowanie obslugi błędów dla tego bloku jest inne niż throw, to throw po prostu wychodzi z obecnie obsługiwanego bloku i wyzwala sposób obsługi wyjątków przypisany temu blokowi, do którego throw wyszedł. Continue nić nie robi w przypadku napotkania błędu - zwyczajnie kontynuuje skrypt. Exit powoduje wyjście z programu. Debug natomiast powoduje przejście w interaktywny tryb debugowania.
Obecny tryb obsługi błędów definiuje się poleceniem:
errorbehaviour nazwa
Nie można natomiast ustawić zachowania debug, gdy program nie został uruchomiony z przełącznikiem -‑tao-na-shell-enable-debug. W takim przypadku zostanie zachowany stary sposób obsługi błędów. Domyślnym sposobem obsługi błędów jest throw.
Zachowanie obsługi błędów dla bieżącego bloku jest przechowywane w zmiennej $.error_handling_behaviour. Jednak przed wykonaniem bloku do jego kontekstu dopinana jest nowa zmienna $.error_handling_behaviour z wartością zdefiniowaną w zmiennej $.error_handling_behaviour_for_new_context, poza wyjątkiem, gdy $.error_handling_behaviour_for_new_context posiada wartość inherit. W przypadku wartości inherit do nowotworzonej zmiennej $.error_handling_behaviour jest kopiowana wartość z obecnej zmiennej $.error_handling_behaviour. Dodanie obsługi zmiennej $.error_handling_behaviour_for_new_context było podyktowane tym, że być może chcielibyśmy dla naszego bloku kodu ustawić sposób obsługi błędów na continue, lecz wywołujemy w nim funkcję. Ponieważ ta funkcja to dla nas czarna skrzynka, to lepszym zachowaniem dla niej jest throw.
Sposób obsługi błędów określa też flaga mówiąca o tym, czy należy wyświetlać komunikat o błędzie, wraz ze śladem. By ją aktywować, to należy wykonać poniższy kod:
errordisplaymode all
By ją zdezaktywować, to należy wykonać poniższy kod:
errordisplaymode void
Kilka słów na koniec
Mam już niewiele do zrobienia przed wydaniem pierwszej wersji alfa. Chciałbym napisać kilka prostych programów w libgreattao, jak:
- Tao-multishell: Graficzna/tekstowa(dzięki libgreattao) powłoka do uruchamiania innych programów
- tao-makefile-ui: Program analizujący plik makefile, pozwalający na wybór celów wykonania make, a także pozwalający na podanie parametrów do makefile, zapisanie ich do późniejszego użycia
Zamierzam napisać kolejny wpis blogowy o tworzeniu własnego, tekstowego instalatora, jednak nie korzystający z libgreattao(<<<co za szkoda!>>>), bo ma być to instalator libgreattao(<<<co za szczęście!>>>) ;‑) . Instalator ma być skryptem powłoki, z dodaną licencją i archiwum tar.gz na końcu.
Co do sugestii na temat powłoki/języka, to czekam na sugestie. Moim zdaniem, to stworzyłem bardzo zaawansowanego assemblera(prostota i moc!), ale wynika to z mojego lenistwa.