Programování

Sestavte tlumočníka v Javě - implementujte spouštěcí modul

Předchozí 1 2 3 Strana 2 Další Strana 2 ze 3

Další aspekty: Řetězce a pole

Tlumočník COCOA implementuje dvě další části jazyka BASIC: řetězce a pole. Nejprve se podívejme na implementaci řetězců.

Chcete-li implementovat řetězce jako proměnné, Výraz třída byla upravena tak, aby zahrnovala pojem výrazů „řetězec“. Tato úprava měla podobu dvou dodatků: isString a stringValue. Zdroj těchto dvou nových metod je uveden níže.

 String stringValue (Program pgm) vyvolá BASICRuntimeError {vyvolá nový BASICRuntimeError ("Žádné řetězcové vyjádření pro toto."); } boolean isString () {return false; } 

Je zřejmé, že pro program BASIC není příliš užitečné získat hodnotu řetězce základního výrazu (což je vždy číselný nebo booleovský výraz). Z nedostatku užitečnosti můžete usoudit, že tyto metody tehdy nepatřily Výraz a patřil do podtřídy Výraz namísto. Uvedením těchto dvou metod do základní třídy však všechny Výraz objekty lze testovat, aby se zjistilo, zda ve skutečnosti jde o řetězce.

Dalším přístupem k návrhu je vrátit číselné hodnoty jako řetězce pomocí a StringBuffer objekt pro vygenerování hodnoty. Například by mohl být přepsán stejný kód jako:

 String stringValue (Program pgm) vyvolá BASICRuntimeError {StringBuffer sb = new StringBuffer (); sb.append (this.value (pgm)); vrátit sb.toString (); } 

A pokud se použije výše uvedený kód, můžete jeho použití vyloučit isString protože každý výraz může vrátit hodnotu řetězce. Dále můžete upravit hodnota metoda, která se pokusí vrátit číslo, pokud se výraz vyhodnotí na řetězec spuštěním přes hodnota metoda java.lang.Double. V mnoha jazycích, jako je Perl, TCL a REXX, se tento druh amorfního psaní používá s velkou výhodou. Oba přístupy jsou platné a měli byste se rozhodnout na základě návrhu vašeho tlumočníka. V BASICu musí tlumočník vrátit chybu, když je řetězec přiřazen k číselné proměnné, takže jsem zvolil první přístup (vrácení chyby).

Pokud jde o pole, existují různé způsoby, jak můžete navrhnout svůj jazyk tak, aby je interpretoval. C používá hranaté závorky kolem prvků pole k rozlišení odkazů indexu pole od odkazů na funkce, které mají kolem argumentů závorky. Návrháři jazyků pro BASIC se však rozhodli použít závorky pro funkce i pole, takže při textu JMÉNO (V1, V2) je viděn analyzátorem, může to být buď volání funkce, nebo odkaz na pole.

Lexikální analyzátor rozlišuje mezi tokeny, za kterými následují závorky, a to nejprve za předpokladu, že jsou funkcemi a testováním. Poté se pokračuje, aby se zjistilo, zda jde o klíčová slova nebo proměnné. Právě toto rozhodnutí brání vašemu programu v definování proměnné s názvem „SIN“. Libovolná proměnná, jejíž název se shodoval s názvem funkce, by místo toho vrátil lexikální analyzátor jako token funkce. Druhým trikem, který lexikální analyzátor používá, je zkontrolovat, zda za názvem proměnné bezprostředně následuje `('. Pokud ano, analyzátor předpokládá, že jde o odkaz na pole. Jeho analýzou v lexikálním analyzátoru odstraníme řetězec`MYARRAY (2)'z interpretace jako platné pole (všimněte si mezery mezi názvem proměnné a otevřenou závorkou).

Poslední trik k implementaci polí je v Variabilní třída. Tato třída se používá pro instanci proměnné a jak jsem diskutoval ve sloupci z minulého měsíce, je to podtřída Žeton. Má však také nějaké mechanismy pro podporu polí, a to ukážu níže:

třída Proměnná rozšiřuje Token {// Legální podtypy proměnných final static int ČÍSLO = 0; konečný statický int STRING = 1; konečný statický int NUMBER_ARRAY = 2; final static int STRING_ARRAY = 4; Název řetězce; int subType; / * * Pokud je proměnná v tabulce symbolů, jsou tyto hodnoty * inicializovány. * / int ndx []; // indexy polí. int mult []; // multiplikátory pole zdvojnásobit nArrayValues ​​[]; Řetězec sArrayValues ​​[]; 

Výše uvedený kód ukazuje proměnné instance spojené s proměnnou, jako v Konstantní výraz třída. Je třeba si vybrat z počtu tříd, které se mají použít, ve srovnání se složitostí třídy. Jednou z návrhových možností by mohlo být vybudování Variabilní třída, která obsahuje pouze skalární proměnné a poté přidá ArrayVariable podtřída pro řešení složitosti polí. Rozhodl jsem se je kombinovat a přeměnit skalární proměnné v podstatě na pole délky 1.

Pokud si přečtete výše uvedený kód, uvidíte indexy polí a multiplikátory. Jsou zde proto, že vícerozměrná pole v BASIC jsou implementována pomocí jediného lineárního pole Java. Lineární index do pole Java je vypočítán ručně pomocí prvků multiplikačního pole. Platnost indexů použitých v programu BASIC se kontroluje jejich porovnáním s maximálním legálním indexem v indexech ' ndx pole.

Například pole BASIC se třemi rozměry 10, 10 a 8 bude mít hodnoty 10, 10 a 8 uložené v ndx. To umožňuje vyhodnocovači výrazů testovat podmínku "index mimo hranice" porovnáním čísla použitého v programu BASIC s maximálním legálním číslem, které je nyní uloženo v ndx. Pole multiplikátoru v našem příkladu obsahuje hodnoty 1, 10 a 100. Tyto konstanty představují čísla, která se používají k mapování ze specifikace indexu vícerozměrného pole do specifikace indexu lineárního pole. Skutečná rovnice je:

Index Java = Index1 + Index2 * Maximální velikost Index1 + Index3 * (MaxSize of Index1 * MaxSizeIndex 2)

Další pole Java v souboru Variabilní třída je uvedena níže.

 Vyjádření výrazů []; 

The expns pole se používá k řešení polí, která jsou zapsána jako „A (10 * B, i). "V takovém případě jsou indexy ve skutečnosti spíše výrazy než konstanty, takže odkaz musí obsahovat ukazatele na ty výrazy, které jsou vyhodnocovány za běhu. Konečně je tu docela ošklivě vypadající část kódu, která vypočítá index v závislosti na tom, co byl předán v programu. Tato soukromá metoda je uvedena níže.

 private int computeIndex (int ii []) hodí BASICRuntimeError {int offset = 0; if ((ndx == null) || (ii.length! = ndx.length)) házet nový BASICRuntimeError ("Chybný počet indexů."); for (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) throw new BASICRuntimeError ("Index out of range."); offset = offset + (ii [i] -1) * mult [i]; } návratový offset; } 

Při pohledu na výše uvedený kód si všimnete, že kód nejprve zkontroluje, zda byl při odkazování na pole použit správný počet indexů, a poté, že každý index byl v zákonném rozsahu pro tento index. Pokud je zjištěna chyba, je interpretovi vyvolána výjimka. Metody numValue a stringValue vrací hodnotu z proměnné jako číslo nebo řetězec. Tyto dvě metody jsou uvedeny níže.

 double numValue (int ii []) vyvolá BASICRuntimeError {návrat nArrayValues ​​[computeIndex (ii)]; } String stringValue (int ii []) vyvolá BASICRuntimeError {if (subType == NUMBER_ARRAY) return "" + nArrayValues ​​[computeIndex (ii)]; vrátit sArrayValues ​​[computeIndex (ii)]; } 

Existují další metody pro nastavení hodnoty proměnné, které zde nejsou uvedeny.

Skrytím velké části složitosti toho, jak je každá část implementována, když konečně přijde čas na provedení programu BASIC, je kód Java docela přímočarý.

Spuštění kódu

Kód pro interpretaci příkazů BASIC a jejich provádění je obsažen v

běh

metoda

Program

třída. Kód této metody je zobrazen níže a já si ji projdu, abych poukázal na zajímavé části.

 1 běh veřejného prázdna (vstup InputStream, výstup OutStream) vyvolá BASICRuntimeError {2 PrintStream pout; 3 Výčet e = stmts.elements (); 4 stmtStack = nový Stack (); // nepředpokládejme žádné skládané příkazy ... 5 dataStore = new Vector (); // ... a žádná data ke čtení. 6 dataPtr = 0; 7 prohlášení s; 8 9 vars = nový RedBlackTree (); 10 11 // pokud program ještě není platný. 12 if (! E.hasMoreElements ()) 13 návrat; 14 15 if (out instanceof PrintStream) {16 pout = (PrintStream) out; 17} else {18 pout = nový PrintStream (out); 19} 

Výše uvedený kód ukazuje, že běh metoda trvá InputStream a Výstupní proud pro použití jako „konzole“ provádějícího programu. V řádku 3 objekt výčtu E je nastavena na sadu příkazů z pojmenované kolekce známky. Pro tuto kolekci jsem použil variantu na binárním vyhledávacím stromu nazvanou „červeno-černý“ strom. (Další informace o binárních vyhledávacích stromech najdete v mém předchozím sloupci o vytváření obecných sbírek.) Poté se vytvoří dvě další kolekce - jedna pomocí Zásobník a jeden pomocí a Vektor. Zásobník se používá jako zásobník v libovolném počítači, ale vektor se používá výslovně pro příkazy DATA v programu BASIC. Konečná kolekce je další červeno-černý strom, který obsahuje odkazy na proměnné definované programem BASIC. Tento strom je tabulka symbolů, kterou program používá při provádění.

Po inicializaci jsou nastaveny vstupní a výstupní toky a poté, pokud E není null, začneme shromažďováním jakýchkoli údajů, které byly deklarovány. To se provádí, jak je znázorněno v následujícím kódu.

 / * Nejprve načteme všechny datové příkazy * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); if (s.keyword == Statement.DATA) {s.execute (this, in, pout); }} 

Výše uvedená smyčka jednoduše prohlédne všechny příkazy a všechny nalezené příkazy DATA se poté provedou. Provedení každého příkazu DATA vloží hodnoty deklarované tímto příkazem do souboru úložiště dat vektor. Dále provedeme vlastní program, který se provádí pomocí této další části kódu:

 e = stmts.elements (); s = (Statement) e.nextElement (); dělat {int yyy; / * Při běhu přeskakujeme datové příkazy. * / zkuste {yyy = in.available (); } catch (IOException ez) {yyy = 0; } if (yyy! = 0) {pout.println ("Zastaveno na:" + s); tlačit; přestávka; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (this, (traceFile! = null)? traceFile: pout); } s = s.execute (this, in, pout); } else s = nextStatement (s); } while (s! = null); } 

Jak vidíte v kódu výše, prvním krokem je reinicializace E. Dalším krokem je načtení prvního příkazu do proměnné s a poté vstoupit do prováděcí smyčky. Existuje nějaký kód ke kontrole nevyřízeného vstupu ve vstupním proudu, aby bylo možné přerušit postup programu zadáním do programu, a poté smyčka zkontroluje, zda by příkaz, který má být proveden, byl příkazem DATA. Pokud ano, smyčka přeskočí příkaz tak, jak byl již proveden. Je nutná spíše spletitá technika provádění všech datových příkazů jako první, protože BASIC umožňuje, aby se kdekoli ve zdrojovém kódu objevily příkazy DATA, které splňují příkaz READ. Nakonec, pokud je povoleno trasování, je vytištěn záznam trasování a velmi neinpresivní prohlášení s = s.execute (this, in, pout); je vyvolána. Krása je v tom, že veškerá snaha o zapouzdření základních konceptů do snadno srozumitelných tříd činí finální kód triviální. Pokud to není triviální, pak možná máte ponětí, že by mohl existovat jiný způsob, jak rozdělit váš design.

Balení a další myšlenky

Tlumočník byl navržen tak, aby mohl běžet jako podproces, takže ve vašem programovém prostoru může současně běžet několik podprocesů tlumočníka COCOA. Dále s využitím rozšíření funkcí můžeme poskytnout prostředky, pomocí kterých mohou tato vlákna vzájemně interagovat. Existoval program pro Apple II a později pro PC a Unix s názvem C-roboti, což byl systém interakce „robotických“ entit, které byly programovány pomocí jednoduchého jazyka odvozených od jazyka BASIC. Tato hra poskytla mně i ostatním mnoho hodin zábavy, ale byla také vynikajícím způsobem, jak představit základní principy výpočtu mladším studentům (kteří se mylně domnívali, že jen hrají a neučí se). Subsystémy tlumočníků založené na jazyce Java jsou mnohem výkonnější než jejich protějšky z doby před Java, protože jsou okamžitě dostupné na jakékoli platformě Java. COCOA běžel na systémech Unix a Macintoshes ve stejný den, kdy jsem začal pracovat na počítači se systémem Windows 95. Zatímco Java je zbita nekompatibilitou v implementacích podprocesů nebo okenních nástrojů, často se přehlíží toto: Mnoho kódu „prostě funguje“.

$config[zx-auto] not found$config[zx-overlay] not found