-----------Uwaga! Fragmenty w >...< pogrubiź-------------------- E6 Rafaī Wiosna Za oknem -47 stopni, ōwiėta sā jutro, a ja tu mam pisaź o E... No wiecie co! Ale co ja na to poradzė, jak trzeba, to trzeba. Przypominam wiėc o wrzuceniu jakiejō muzyczki w tīo. Tym razem polecam najnowszā pīytė zespoīu, którego dawny leader jest autorem Magazynu AMIGA!!! (Nie piszė, co to za zespóī, ūeby nikt mnie nie posādziī o kryptoreklamė). Muzyka naprawdė odjazdowa!!! Bīėdów cionk dalszy W poprzednim odcinku o E przedstawiīem zastosowanie handlera bīėdów w gīównej procedurze main(). Czy jednak moūliwe jest, aby zrobiź to w podprocedurach? Oczywiōcie! Handlery sā zorganizowane w taki sposób, ūe Raise() skacze do najbliūszego handlera w okolicy -- zwykle handlera procedury, w której zostaī wykonany. Jeūeli zdefiniujemy procedurė main() z popdprocedurā drukuj(), która ma swój wīasny handler, to kaūda komenda Raise() skoczy do handlera bīėdów funkcji drukuj(), a nie main(). Jeūeli jednak nie zdefiniujemy w drukuj() handlera bīėdów, Raise()-y zawėdrujā do handlera w main(). Najlepiej wyjaōni to przykīad: ENUM BEZBLEDU, BLAD1, BLAD2 PROC main() HANDLE WriteF('main\n') pierwsza() WriteF('main -- po pierwszej\n') druga() WriteF('main -- po drugiej\n') Raise(BEZBLEDU) WriteF('main -- koniec\n') EXCEPT IF exception WriteF('main: BLAD\d\n',exception) ENDIF ENDPROC PROC pierwsza() HANDLE WriteF('pierwsza\n') Raise(BLAD1) WriteF('pierwsza -- koniec\n') EXCEPT IF exception WriteF('pierwsza: BLAD\d\n',exception) ENDIF ENDPROC PROC druga() WriteF('druga\n') Raise(BLAD2) WriteF('druga -- koniec\n') ENDPROC Wynikiem dziaīania programu bedā napisy: main pierwsza pierwsza: BLAD1 main -- po pierwszej druga main: BLAD2 Jak widaź na przykīadzie, jeūeli nie ma handlera o jeden stopieļ wyūej, to program jest zatrzymywany -- w powyūszym listingu napis "main -- po drugiej" nie zostanie wypisany, gdyū zostaī wykonany handler funkcji main(), nad którā nie ma juū innej procedury. Bardzo miīā funkcjā E jest Trhrow. Wystėpuje ona jednak tylko w wersji >Amiga 3.0< i nowszych. Ma ona postaź: Throw(x,y) gdzie x to numer bīėdu, taki sam jak w wypadku uūywania Raise(), a y to liczba, którā moūna przkazaź handlerowi. Daje to moūliwoōź stworzenia klas bīėdów obsīugiwanych przez osobne procedury: Throw(ERROR_DOS,IoErr()) Throw(ERROR_INTERNAL,warosc) Oto przykīad uūycia Throw(): MODULE 'dos/dos' CONST MAXFNLEN=256, ESCAPE=27 ENUM OK,BLAD_NOTADIR,BLAD_CANTEX,BLAD_LOCK,BLAD_OPEN,BLAD_READ,IFF,HALT DEF spacje PROC main() spacje:=0 katalog(arg) ENDPROC PROC katalog(uarg) HANDLE DEF dirlock, fib:PTR TO fileinfoblock,ifffilename[256]:STRING,s DEF dirfilename[256]:STRING fib:=New(SIZEOF fileinfoblock) IF (dirlock:=Lock(uarg,ACCESS_READ))=NIL THEN Throw(BLAD_LOCK,uarg) IF Examine(dirlock,fib)=NIL THEN Throw(BLAD_CANTEX,uarg) IF fib.direntrytype<0 THEN Throw(BLAD_NOTADIR,uarg) IF spacje THEN FOR s:=1 TO spacje DO WriteF(' ') WriteF('\c[31m\s\c[0m\n',ESCAPE,uarg,ESCAPE) IF CtrlC() THEN Raise(OK) WHILE ExNext(dirlock,fib) IF CtrlC() THEN Raise(HALT) IF fib.direntrytype>0 INC spacje StrCopy(dirfilename,'',ALL) StrCopy(dirfilename,uarg,ALL) AddPart(dirfilename,fib.filename,MAXFNLEN) katalog(dirfilename) DEC spacje ELSE IF spacje THEN FOR s:=1 TO spacje DO WriteF(' ') WriteF(' \s [\d] ',fib.filename,fib.size) StrCopy(ifffilename,'',ALL) StrCopy(ifffilename,uarg,ALL) AddPart(ifffilename,fib.filename,MAXFNLEN) IF fib.size>12 THEN czyiff(ifffilename) WriteF('\n') ENDIF ENDWHILE Raise(OK) EXCEPT IF dirlock THEN UnLock(dirlock) IF exception SELECT exception CASE BLAD_LOCK; WriteF('Nie mogė wejōź do katalogu \s',exceptioninfo) CASE BLAD_CANTEX; WriteF('Nie mogė przeanalizowaź \s!\n',exceptioninfo) CASE BLAD_NOTADIR; WriteF('\s nie jest katalogiem!\n',exceptioninfo) CASE HALT; ReThrow() ENDSELECT ENDIF ENDPROC PROC czyiff(ufname) HANDLE DEF ufh,iffis[4]:STRING,iffid[4]:STRING IF (ufh:=Open(ufname,MODE_OLDFILE))=NIL THEN Throw(BLAD_OPEN,IoErr()) IF Read(ufh,iffis,4)=FALSE THEN Throw(BLAD_READ,IoErr()) Seek(ufh,4,OFFSET_CURRENT) IF Read(ufh,iffid,4)=FALSE THEN Throw(BLAD_READ,IoErr()) IF StrCmp(iffis,'FORM')=TRUE THEN Throw(IFF,iffid) EXCEPT DO IF ufh THEN Close(ufh) IF exception SELECT exception CASE BLAD_OPEN; PrintFault(exceptioninfo,'!! Nie mogė otworzyź pliku. Powód: ') CASE BLAD_READ; PrintFault(exceptioninfo,'!! Nie mogė odczytaź pliku. Powód: ') CASE IFF; WriteF('\c[1mIFF \s\c[0m',ESCAPE,exceptioninfo,ESCAPE) ENDSELECT ENDIF ENDPROC Program jest wersjā komendy dir. Po skompilowaniu argumentem programu jest nazwa katalogu, który ma zostaź "przeōwietlony". Polega to na sprawdzeniu wszystkich plików w katalogu, czy nie sā przypadkiem IFF-ami, a jeūeli sā, to jakiego typu. Jeūeli zostanie napotkany katalog, program wejdzie do niego i sprawdzi w nim wszystkie pliki. W ostatniej linii widzisz liniė z >EXCEPT DO<. Otóū jest to pewien trik. Jeūeli napiszesz tak w swoim śródle, to bieg programu po napotkaniu EXCEPT nie zostanie przerwany (lub nie wróci do miejsca wywoīania procedury), ale wykona handler ze zmiennā exception ustawionā na 0. Dziėki DO konstrukcjė ... /* OK to ENUM-owana staīa o wartoōci 0 */ Raise(OK) EXCEPT ... moūna zastāpiź przez krótsze ... EXCEPT DO ... Maīe, a cieszy. Czy zauwaūyīeō uūycie nie znanej jeszcze funkcji >ReThrow()< w handlerze procedury katalog()? Ūeby wyjaōniź jej dziaīanie, muszė siė posīuūyź przykīadem. Przyjmijmy, ūe Twój program rysuje dywan Sierpiļskiego -- fraktal, którego algorytm tworzenia najlepiej zapisaź rekurencyjnie (jeūeli nie wiesz, co to jest rekurencja, to proponujė przeczytaź ksiāūkė [1], gdzie jest to bardzo īadnie objaōnione). Oczywiōcie otworzyīeō ekran, okienka i inne bajery. W lewym górnym rogu ekranu, jak na dobry soft amigowski przystaīo, znajduje siė gadūet zamykajācy, którego wciōniėcie ma spowodowaź przerwanie rysowania fraktala i zakoļczenie wykonywania programu. Zastanów siė, jak byō to zrealizowaī? Po otrzymaniu sygnaīu od Intuition, ūe gadūet ten jest wciōniėty, naleūy wróciź do gīównej procedury i posprzātaź po sobie (zamknāź okna, ekran, ewentualnie zwolniź inne zasoby systemu). Proste, tak? No dobrze, ale jak to zrobiź, jeūeli przy kaūdym wywoīaniu procedury, dajmy na to, fraktal(), która jest zbudowana rekurencyjnie (wywoīuje samā siebie), rezerwujesz maīy kawaīek pamiėci na jakieō podrėczne dane? Po zauwaūeniu wciōniėcia gadūetu naleūy przecieū te kilka (kilkadziesiāt..., kilkaset..., kilka tysiėcy..., zaleūy od gīėbokoōci rekurencji) alokacji zwróciź systemowi!!! I tu zaczynajā siė schody, bo wskaśniki do tych alokacji sā w niedostėpnych z wnėtrza procedury fraktal() zmiennych, naleūācych do "rodziców" (procedur wywoīujācych) i grzecznie odkīadanych na stos przy kaūdym wywoīaniu rekurencyjnym... Radā na to jest wīaōnie ReThrow(). Funkcja ta powoduje wywoīanie handlera bīėdów procedury, gdzie, zgodnie z tym, co pisaīem w poprzednim odcinku, zwalniamy zajėtā na poczātku biegu procedury pamiėź. Potem ReThrow() wywoīuje handler procedury "rodzica" itp. Tak wiėc wszystkie alokacje zostanā zwrócone! I o to chodzi. ReThrow() jest unikatem i wraz z handlerami bīėdów stanowi bardzo mocnā stronė E. W poprzednim odcinku zadeklarowaīem siė, ūe opiszė dziaīanie konstrukcji >SELECT val OF var<. Zaīóūmy, ūe z niewiadomych powodów, chcesz wypisywaź znaki z pliku na ekran w bardzo dziwny sposób. Wszystkie o kodach poniūej 32 majā byź zastāpione przez kropki, oprócz znaku (kod 10) oznaczajācego poczātek nowej linii, po napotkaniu którego wypiszesz na ekran ciāg znaków "*NEW LINE*", natomiast znak (kod 13) ma nie byź wyōwietlany w ogóle, nawet w postaci kropki. Znaki o kodach powyūej 127 teū majā byź zamienione na kropki. Oprócz tego cyfry majā byź pogrubiane, a maīe litery pochylone. Dziėki SELECT val OF var moūna zrobiź to bardzo zgrabnie: SELECT 127 OF znak:=Fgetc(input) CASE "\n" /* \n to znak czyli kod 10 */ WriteF('*NEW LINE*\n') CASE "\b" /* \b to znak czyli kod 13 */ NOP /* 8^) wstawka assemblerowa */ CASE "0" TO "9" WriteF('\e[1m\c\e[0m',znak) CASE "a" TO "z" WriteF('\e[2m\c\e[0m',znak) DEFAULT WriteF('.') ENDSELECT Pierwszym argumentem konstrukcji SELECT val OF var jest zakres -- przyjmowane jest od 0 do val. Drugim argumentem jest zmienna, której wartoōź jest porównywana w CASE-ach w ōrodku SELECT..ENDSELECT. Jeūeli zmienna ta nie mieōci siė w zakresie [0..val], to wywoīywane jest dziaīanie zdefiniowane w DEFAULT. Przy stosowaniu SELECT val OF var naleūy pamiėtaź, ūe jest to bardzo szybki, ale pamiėcioūerny sposób. Amiga E tworzy tablicė skoków (dziėki czemu nie trzeba porównywaź wartoōci, wystarczy na ich podstawie pobraź adres z odpowiedniej komórki tablicy skoków) o wielkoōci 2*val bajtów (tutaj: 256). Nie naleūy wiėc przesadzaź i nie uūywaź wartoōci wiėkszych niū 256... * Tym razem odstāpiė od tradycji i nie zamieszczė przykīadowego programu do wklepania. Wybaczcie mi, ale mówiāc szczerze trudno siė zmieōciź w 4 KB tekstu linstingu, która to objėtoōź wchodzi akurat na stronė w naszym piōmie, tak, aby program byī i ciekawy, i poūyteczny. Obiecujė jednak, ūe w nastėpnym numerze znajdziecie taki "przykīadowy" listing, byź moūe o dwa razy wiėkszej objėtoōci. Przy okazji zachėcam do przysyīania listów z opisami Waszych problemów dotyczācych jėzyka E. Postaram siė na najwaūniejsze odpowiedzieź na īamach pisma, co, jak sādzė, wyjdzie na korzyōź nie tylko pytajācemu, ale i Czytelnikom. Ūegnam na miesiāc. CUL8R (czyt: si-ju-l-eit-er). Literatura: [1] Anna Struūyļska-Walczak, Krzysztof Walczak "Nauka programowania dla... juū nie caīkiem poczātkujācych; Turbo Pascal", Wydawnictwo W&W (taka zielona ksiāūka z ūóītym tytuīem).