Izgradite tumač u Javi - implementirajte izvršni mehanizam

Prethodna 1 2 3 Stranica 2 Sljedeća Stranica 2 od 3

Ostali aspekti: žice i nizovi

Dva druga dijela jezika BASIC implementirana je interpretatorom COCOA: nizovi i nizovi. Pogledajmo prvo implementaciju nizova.

Da bi se nizovi implementirali kao varijable, Expressionklasa je izmijenjena tako da uključuje pojam izraza "string". Ova je izmjena imala oblik dva dodatka: isStringi stringValue. Izvor za ove dvije nove metode prikazan je u nastavku.

String stringValue (Program pgm) baca BASICRuntimeError {baciti novi BASICRuntimeError ("Nema predstavljanja niza za ovo."); } boolean isString () {return false; }

Jasno je da BASIC programu nije previše korisno dobivanje vrijednosti niza osnovnog izraza (koji je uvijek numerički ili logički izraz). Iz nedostatka korisnosti mogli biste zaključiti da te metode tada nisu pripadale Expressioni pripadale su podklasi Expression. Međutim, stavljanjem ove dvije metode u osnovnu klasu, svi se Expressionobjekti mogu testirati kako bi se utvrdilo jesu li zapravo nizovi.

Drugi pristup dizajnu je vraćanje numeričkih vrijednosti kao nizova pomoću StringBufferobjekta za generiranje vrijednosti. Tako bi se, na primjer, isti kôd mogao prepisati kao:

String stringValue (Program pgm) baca BASICRuntimeError {StringBuffer sb = new StringBuffer (); sb.append (this.value (pgm)); vratiti sb.toString (); }

A ako se koristi gornji kod, možete eliminirati upotrebu isStringjer svaki izraz može vratiti vrijednost niza. Nadalje, možete izmijeniti valuemetodu da biste pokušali vratiti broj ako se izraz ocjenjuje u niz tako što ćete ga pokrenuti kroz valueOfmetodu java.lang.Double. U mnogim se jezicima poput Perla, TCL-a i REXX-a ova vrsta amorfnog tipkanja koristi s velikom prednošću. Oba su pristupačna rješenja, a vi biste trebali odabrati svoj odabir na temelju dizajna vašeg prevoditelja. U BASIC-u tumač mora vratiti pogrešku kada je niz dodijeljen numeričkoj varijabli, pa sam odabrao prvi pristup (vraćanje pogreške).

Što se tiče nizova, postoje različiti načini na koje možete dizajnirati svoj jezik za njihovo tumačenje. C koristi kvadratne zagrade oko elemenata niza za razlikovanje referenci na indeks niza od referenci na funkcije koje imaju zagrade oko svojih argumenata. Međutim, dizajneri jezika za BASIC odlučili su koristiti zagrade i za funkcije i za nizove, pa kad NAME(V1, V2)parser vidi tekst , to može biti ili poziv funkcije ili referenca niza.

Leksički analizator razlikuje žetone iza kojih slijede zagrade pretpostavljajući da su funkcije i testirajući to. Zatim se nastavlja jesu li ključne riječi ili varijable. Ova odluka sprečava vaš program da definira varijablu pod nazivom "SIN". Bilo koja varijabla čije se ime podudara s imenom funkcije, leksički će analizator umjesto toga vratiti kao funkcijski token. Drugi trik koji koristi leksički analizator je provjera da li iza imena varijable odmah slijedi `('. Ako jest, analizator pretpostavlja da je referenca niza. Analiziranjem ovog u leksičkom analizatoru uklanjamo niz` MYARRAY ( 2 )'iz tumačenja kao valjanog polja (imajte na umu razmak između imena varijable i otvorene zagrade).

Posljednji trik za implementaciju nizova je u Variableklasi. Ova se klasa koristi za primjerak varijable, a kao što sam spomenuo u prošlomjesečnom stupcu, ona je podrazred Token. Međutim, on također ima neke uređaje za potporu nizova i to je ono što ću pokazati u nastavku:

class Variable extends Token {// Legal type sub type final static int NUMBER = 0; konačni statički int STRING = 1; konačni statički int NUMBER_ARRAY = 2; konačni statički int STRING_ARRAY = 4; Naziv niza; int podtip; / * * Ako je varijabla u tablici simbola, te su vrijednosti * inicijalizirane. * / int ndx []; // indeksi nizova. int mult []; // množitelji niza dvostruki nArrayValues ​​[]; Niz sArrayValues ​​[];

Gornji kod prikazuje varijable instance povezane s varijablom, kao u ConstantExpressionklasi. Treba napraviti izbor o broju klasa koje će se koristiti u odnosu na složenost klase. Jedan od dizajnerskih izbora mogao bi biti izgradnja Variableklase koja sadrži samo skalarne varijable, a zatim dodavanje ArrayVariablepodklase za rješavanje zamršenosti nizova. Odlučio sam ih kombinirati, pretvarajući skalarne varijable u osnovi u nizove duljine 1.

Ako pročitate gornji kod, vidjet ćete indekse niza i množitelje. To su ovdje jer su višedimenzionalni nizovi u BASIC-u implementirani pomoću jednog linearnog Java niza. Linearni indeks u Java nizu izračunava se ručno pomoću elemenata niza množitelja. Indeksi koji se koriste u programu BASIC provjeravaju se valjanošću uspoređujući ih s maksimalnim pravnim indeksom u ndx nizu indeksa .

Na primjer, BASIC niz s tri dimenzije 10, 10 i 8 imao bi vrijednosti 10, 10 i 8 pohranjene u ndx. To omogućuje evaluatoru izraza da testira uvjet "indeksa izvan granica" uspoređujući broj koji se koristi u programu BASIC s maksimalnim pravnim brojem koji je sada pohranjen u ndx. Niz množitelja u našem primjeru sadržavao bi vrijednosti 1, 10 i 100. Te konstante predstavljaju brojeve kojima se preslikava iz specifikacije indeksa višedimenzionalnog niza u specifikaciju indeksa linearne matrice. Stvarna jednadžba je:

Java Index = Index1 + Index2 * Maksimalna veličina Index1 + Index3 * (MaxSize of Index1 * MaxSizeIndex 2)

Sljedeći Java niz u Variableklasi prikazan je u nastavku.

 Izraz expns []; 

Niz expns koristi se za obradu nizova koji su napisani kao " A(10*B, i)." U tom su slučaju indeksi zapravo izrazi, a ne konstante, pa referenca mora sadržavati pokazivače na one izraze koji se procjenjuju u vrijeme izvođenja. Napokon, tu je i prilično ružno izgledajući kôd koji izračunava indeks ovisno o tome što je proslijeđeno u programu. Ova privatna metoda prikazana je u nastavku.

private int computeIndex (int ii []) baca BASICRuntimeError {int offset = 0; if ((ndx == null) || (ii.length! = ndx.length)) baciti novu BASICRuntimeError ("Pogrešan broj indeksa."); for (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) izbaci novu BASICRuntimeError ("Index out of range."); pomak = pomak + (ii [i] -1) * mult [i]; } povratni pomak; }

Gledajući gornji kod, primijetit ćete da kod prvo provjerava je li korišten točan broj indeksa prilikom referenciranja niza, a zatim da li je svaki indeks unutar zakonskog raspona za taj indeks. Ako se otkrije pogreška, tumaču se dobacuje izuzetak. Metode numValuei stringValuevraćaju vrijednost iz varijable kao broj, odnosno niz. Ove dvije metode prikazane su u nastavku.

double numValue (int ii []) baca BASICRuntimeError {return nArrayValues ​​[computeIndex (ii)]; } Niz stringValue (int ii []) baca BASICRuntimeError {if (subType == NUMBER_ARRAY) return "" + nArrayValues ​​[computeIndex (ii)]; vratiti sArrayValues ​​[computeIndex (ii)]; }

Postoje dodatne metode za postavljanje vrijednosti varijable koje ovdje nisu prikazane.

Skrivajući veći dio složenosti načina na koji je svaki komad implementiran, kada napokon dođe vrijeme za izvršavanje programa BASIC, Java kôd je sasvim jednostavan.

Pokretanje koda

Kôd za tumačenje BASIC izraza i njihovo izvršavanje sadržan je u

run

metoda

Program

razred. Kôd ove metode prikazan je u nastavku, a ja ću ga koračati da bih ukazao na zanimljive dijelove.

1 javni void vožnja (InputStream in, OutputStream out) baca BASICRuntimeError {2 PrintStream pout; 3 nabrajanje e = stmts.elements (); 4 stmtStack = novi stog (); // pretpostavimo da nema naslaganih izjava ... 5 dataStore = new Vector (); // ... i nema podataka za čitanje. 6 podatakaPtr = 0; 7 Izjava s; 8 9 vars = novo RedBlackTree (); 10 11 // ako program još nije valjan. 12 if (! E.hasMoreElements ()) 13 return; 14 15 if (out instanceof PrintStream) {16 pout = (PrintStream) out; 17} else {18 pout = novi PrintStream (out); 19}

Gornji kod pokazuje da runmetoda uzima " InputStreami" OutputStreamza upotrebu kao "konzolu" za izvršni program. U retku 3, objekt popisivanja e postavljen je na skup iskaza iz zbirke nazvane stmts . Za ovu sam kolekciju koristio varijaciju na binarnom stablu pretraživanja nazvanu "crveno-crno" stablo. (Za daljnje informacije o binarnim stablima pretraživanja, pogledajte moj prethodni stupac o izradi generičkih zbirki.) Nakon toga kreiraju se dvije dodatne zbirke - jedna koja koristi a Stacki druga pomoćuVector. Stog se koristi kao stog na bilo kojem računalu, ali vektor se izričito koristi za izraze DATA u programu BASIC. Konačna zbirka je još jedno crveno-crno stablo koje sadrži reference za varijable definirane programom BASIC. Ovo je stablo tablica simbola koju program koristi dok se izvršava.

Nakon inicijalizacije postavljaju se ulazni i izlazni tokovi, a ako e nije nula, započinjemo sakupljanjem svih deklariranih podataka. To se radi kako je prikazano u sljedećem kodu.

/ * Prvo učitavamo sve izjave podataka * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); if (s.keyword == Statement.DATA) {s.execute (this, in, pout); }}

Gornja petlja jednostavno pregledava sve izraze i svi pronađeni DATA izrazi se tada izvršavaju. Izvršenje svakog DATA izraza ubacuje vrijednosti deklarirane tim izrazom u vektor dataStore . Dalje izvršavamo vlastiti program, što se radi pomoću sljedećeg dijela koda:

e = stmts.elements (); s = (Izjava) e.nextElement (); učiniti {int yyy; / * Tijekom izvođenja preskačemo izjave s podacima. * / probajte {yyy = in.available (); } ulov (IOException ez) {yyy = 0; } if (yyy! = 0) {pout.println ("Zaustavljeno na:" + s); guranje (i); pauza; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (this, (traceFile! = null)? traceFile: pout); } s = s.izvršiti (ovo, u, durenje); } else s = nextStatement (s); } while (s! = null); }

Kao što možete vidjeti u kodu iznad, prvi korak je ponovna inicijalizacija e-a . Sljedeći je korak dohvat prve naredbe u varijablu s, a zatim ulazak u petlju izvršenja. Postoji neki kod za provjeru unosa na čekanju u ulaznom toku kako bi se napredovanje programa moglo prekinuti tipkanjem na programu, a zatim petlja provjerava bi li izjava za izvršavanje bila DATA izjava. Ako jest, petlja preskače izjavu jer je već izvršena. Potrebna je prilično zamršena tehnika izvođenja svih naredbi podataka jer BASIC omogućuje da se izrazi DATA koji zadovoljavaju READ izraz pojave bilo gdje u izvornom kodu. Napokon, ako je omogućeno praćenje, ispisuje se zapis traga i vrlo neinpresivna izjavas = s.execute(this, in, pout);se poziva. Ljepota je u tome što sav trud oko kapsuliranja osnovnih pojmova u lako razumljive klase čini konačni kod trivijalnim. Ako to nije trivijalno, možda imate pojma da možda postoji još jedan način da podijelite svoj dizajn.

Umotavanje i daljnja razmišljanja

Tumač je dizajniran tako da može raditi kao nit, pa istovremeno može postojati nekoliko niti interpretatora COCOA istovremeno. Nadalje, upotrebom proširenja funkcije možemo pružiti način na koji te niti mogu međusobno komunicirati. Postojao je program za Apple II, a kasnije za PC i Unix pod nazivom C-roboti, koji je bio sustav interakcije "robotskih" entiteta koji su programirani pomoću jednostavnog izvedenog jezika BASIC. Igra je meni i drugima pružila mnogo sati zabave, ali je ujedno bila i izvrstan način da osnovna načela računanja predstavim mlađim učenicima (koji su pogrešno vjerovali da se samo igraju, a ne uče).Podsistemi interpretatora temeljeni na Javi mnogo su snažniji od njihovih prethodnih Java kolega jer su trenutno dostupni na bilo kojoj Java platformi. COCOA se pokrenuo na Unix sustavima i Macintoshesu isti dan kad sam počeo raditi na računalu sa sustavom Windows 95. Iako Java prebijaju nekompatibilnosti u implementacijama niti niti ili prozora, ono što se često zanemaruje je sljedeće: Puno koda "jednostavno radi".