Zbuduj interpreter w Javie — zaimplementuj silnik wykonawczy

dla tych z Was, którzy właśnie do nas dołączyli, w mojej kolumnie” Java In Depth ” w ciągu ostatnich kilku miesięcy omawiałem, jak można zbudować interpreter w Javie. W pierwszej kolumnie interpretera omówiliśmy niektóre z pożądanych atrybutów interpretera; w drugiej kolumnie omówiliśmy zarówno parsowanie, jak i układ pakietu klasowego do implementacji interpretera. W tej kolumnie przyjrzymy się uruchomieniu interpretera i klasom wsparcia niezbędnym do osiągnięcia tego celu. Na koniec zakończę serię dyskusją o tym, jak interpreter można podłączyć do innych klas Java, zwiększając w ten sposób ich możliwości.

przeglądając odpowiednie bity

pozwólcie, że zacznę od szkicu tego, co omówiliśmy do tej pory i zwrócę uwagę na te części projektu, które staną się ważniejsze w miarę omawiania trybu wykonania. Aby uzyskać bardziej szczegółowy opis tych klas, zapoznaj się z moimi poprzednimi kolumnami lub linkami do kodu źródłowego, które znajdują się w sekcji

zasoby

poniżej.

w implementacji interpretera są trzy klasy Fundacji, Program, Statementi Expression. Poniżej przedstawiono, jak te trzy elementy są ze sobą powiązane:

Program

ta klasa Program skleja ze sobą komponenty parsowania i wykonywania parsera. Ta klasa definiuje dwie główne metody, load i run. Metoda load odczytuje instrukcje ze strumienia wejściowego i parsuje je do zbioru instrukcji, metoda run iteracje nad kolekcją i wykonuje każde z instrukcji. Klasa Program zapewnia również zbiór zmiennych do wykorzystania przez program, a także stos do przechowywania danych.

polecenie

Klasa Statement zawiera pojedynczą przetworzoną instrukcję. Ta klasa jest faktycznie podklasowana do określonego typu instrukcji (PRINT, GOTO, IF itd.), ale wszystkie instrukcje zawierają metodę execute, która jest wywoływana do wykonania instrukcji w kontekście instancji klasy Program.

wyrażenie

Klasa Expression zawiera drzewo parsowania wyrażenia. Podczas wykonywania, metoda value służy do obliczenia wyrażenia i zwrócenia jego wartości. Podobnie jak Statement, Klasa Expression jest przeznaczona przede wszystkim do podklasowania przez określone typy wyrażeń.

wszystkie te klasy współpracują ze sobą, tworząc podstawę tłumacza. Klasa Program jednocześnie hermetyzuje operację parsowania i operację wykonania, podczas gdy klasy Statement i Expression hermetyzują rzeczywiste koncepcje obliczeniowe zaimplementowanego języka. Dla tych trzech artykułów na temat budowania tłumaczy przykładowy język był podstawowy.

obiekty do obliczeń

w interpreterze znajdują się dwie klasy wykonywalne,

Statement

oraz

Expression

. Najpierw rzućmy okiem na

Expression

instancje Expression są tworzone przy pomocy metody expressionw klasie ParseExpression. Klasa ParseExpression implementuje Parser wyrażeń w tym interpreterze. Ta klasa jest peerem klasy ParseStatement, która używa metody statement do parsowania podstawowych instrukcji. Instancje Expression mają wewnętrzną wartość type, która określa, jaki operator reprezentuje instancja, oraz dwie metody, value i stringValue, które zwracają obliczoną wartość wyrażenia. Dodatkowo, gdy instancja wyrażenia jest tworzona, otrzymuje ona nominalnie dwa parametry reprezentujące lewą i prawą stronę operacji wyrażenia. W postaci źródłowej pierwsza część wyrażenia wygląda następująco:

class Expression { Expression arg1, arg2; int oper; final static int OP_ADD = 1; // Addition '+' final static int OP_SUB = 2; // Subtraction '-' final static int OP_MUL = 3; // Multiplication '*' final static int OP_DIV = 4; // Division '/' final static int OP_BNOT = 19; // Boolean negation '.NOT.' final static int OP_NEG = 20; // Unary minus

jak widać w powyższym kodzie, są zmienne instancji, Typ operatora o nazwie oper i dwie połówki operacji w arg1 i arg2, a następnie niektóre stałe do definiowania różnych typów. Ponadto istnieją dwa konstruktory używane przez parser wyrażeń; tworzą one nowe wyrażenie z dwóch wyrażeń. Ich źródło pokazano poniżej:

protected Expression(int op, Expression a, Expression b) throws BASICSyntaxError { arg1 = a; arg2 = b; oper = op; /* * If the operator is a boolean, both arguments must be boolean. */ if (op > OP_GE) { if ( (! (arg1 instanceof BooleanExpression)) || (! (arg2 instanceof BooleanExpression)) ) throw new BASICSyntaxError(typeError); } else { if ((arg1 instanceof BooleanExpression) || (arg2 instanceof BooleanExpression)) throw new BASICSyntaxError(typeError); } } protected Expression(int op, Expression a) throws BASICSyntaxError { arg2 = a; oper = op; if ((oper == OP_BNOT) && (! (arg2 instanceof BooleanExpression))) throw new BASICSyntaxError(typeError); }

pierwszy konstruktor buduje dowolny obiekt wyrażenia, a drugi buduje „jednoargumentowy” obiekt wyrażenia-taki jak unary minus. Należy zauważyć, że jeśli istnieje tylko jeden argument, to arg2 jest używany do przechowywania jego wartości.

metoda stosowana najczęściej w klasie Expression to value, która jest zdefiniowana następująco:

 double value(Program pgm) throws BASICRuntimeError { switch (oper) { case OP_ADD : return arg1.value(pgm) + arg2.value(pgm); case OP_SUB : return arg1.value(pgm) - arg2.value(pgm); ... etc for all of the other operator types. ...

widać, że każdy obiekt wyrażenia reprezentuje krotkę składającą się z operatora i jednego lub dwóch argumentów. Zabawną częścią projektowania mechanizmu wykonywania wyrażeń w ten sposób jest to, że kiedy konstruujesz ten zestaw krotek wyrażeń w oparciu o obiekt Expression, możesz obliczyć wartość wyrażenia, wywołując po prostu metodę value. Metoda value rekurencyjnie wywołuje metodę value dwóch argumentów składających się na to wyrażenie, stosuje do nich operację i zwraca wynik. Ten projekt został użyty, aby wyrażenia były łatwe do zrozumienia.

aby zachować czystość struktury klas, wszystkie jednostki obliczeniowe-od stałych do funkcji trygonometrycznych-są podklasami Expression. Pomysł ten, wykradziony bezwstydnie z Lispu, Całkowicie zawiera pojęcie „spowodowania” wystąpienia oceny, z faktycznej implementacji „jak” taka ocena zachodzi. Aby zademonstrować, jak ta zasada jest stosowana, musimy tylko przyjrzeć się niektórym wyspecjalizowanym podklasom Expression.

stałe w mojej wersji BASIC, którą nazwałem COCOA są reprezentowane przez klasę ConstantExpression, która podklasuje Expression i po prostu przechowuje wartość liczbową w wartości członka. Kod źródłowy ConstantExpression jest pokazany poniżej. Mówię „koncepcyjnie”, ponieważ zdecydowałem się połączyć to, co byłoby StringConstantExpression i NumericConstantExpression w jedną klasę. Tak więc Klasa real zawiera konstruktor do tworzenia stałej z argumentem string i do zwracania jej wartości jako string. Poniższy kod pokazuje, w jaki sposób Klasa ConstantExpression obsługuje stałe numeryczne.

class ConstantExpression extends Expression { private double v; ConstantExpression(double a) { super(); v = a; } double value(Program pgm) throws BASICRuntimeError { return v; }}

powyższy kod zastępuje bardziej skomplikowane konstruktory Expression prostym zapisem zmiennej instancji; metoda value jest po prostu zastąpiona zwrotem zapisanej wartości.

prawdą jest, że możesz zakodować klasę Expression tak, aby akceptowała w jej konstruktorach stałą, która zapisałaby Ci klasę. Jednak jedną z zalet projektowania Expression w sposób jaki mam jest to, że kod w Expression pozostaje maksymalnie ogólny. Uważam, że ten styl kodowania pomaga mi wyeliminować złożoność szczególnych przypadków, a zatem kiedy jestem „gotowy”z kodem Expression, mogę przejść do innych aspektów wyrażeń bez ponownego odwiedzania klasy podstawowej. Zaleta staje się wyraźniejsza, gdy zagłębimy się w inną podklasę Expression o nazwie FunctionExpression.

w klasie FunctionExpression były dwa wymagania projektowe, które moim zdaniem powinny zostać spełnione, aby interpreter był elastyczny. Pierwszym było wdrożenie standardowych podstawowych funkcji; drugim było zamknięcie parsowania argumentów funkcji w tej samej klasie, która implementowała te funkcje. Drugie Wymaganie, parsowanie, było motywowane chęcią uczynienia tego podstawowego rozszerzalnego poprzez stworzenie dodatkowych bibliotek funkcji, które mogłyby być przekazywane do parsera jako podklasy FunctionExpression. Ponadto, te zdane klasy mogą być używane przez parser do zwiększenia liczby funkcji dostępnych dla programu użytkownika.

Klasa FunctionExpression jest tylko umiarkowanie bardziej skomplikowana niż ConstantExpression i jest pokazana w skróconej formie poniżej:

 1 class FunctionExpression extends Expression { 2 3 double value(Program p) throws BASICRuntimeError { 4 try { 5 switch (oper) { 6 case RND : 7 if (r == null) 8 r = p.getRandom(); 9 return (r.nextDouble() * arg2.value(p));10 case INT :11 return Math.floor(arg2.value(p));12 case SIN :13 return Math.sin(arg2.value(p));14 15 default :16 throw new BASICRuntimeError("Unknown or non-numeric function.");17 }18 } catch (Exception e) {19 if (e instanceof BASICRuntimeError)20 throw (BASICRuntimeError) e;21 else22 throw new BASICRuntimeError("Arithmetic Exception.");23 }24 }

powyższe źródło pokazuje sposób implementacji metody value. Zmienna oper jest ponownie używana do przechowywania tożsamości funkcji, a Obiekty wyrażenia, do których odwołują się arg1 i arg2, są używane jako argumenty do samych funkcji. Wreszcie, istnieje duża Instrukcja przełącznika, która wysyła żądanie. Interesującym aspektem jest to, że metoda value wyłapuje potencjalne wyjątki arytmetyczne i konwertuje je na instancje BASICRuntimeError. Kod parsowania w FunctionExpression jest pokazany poniżej, ponownie skondensowany, aby zaoszczędzić miejsce. (Pamiętaj, że cały kod źródłowy jest dostępny za pomocą linków w sekcji Zasoby.)

 1 static FunctionExpression parse(int ty, LexicalTokenizer lt) throws BASICSyntaxError { 2 FunctionExpression result; 3 Expression a; 4 Expression b; 5 Expression se; 6 Token t; 7 8 t = lt.nextToken(); 9 if (! t.isSymbol('(')) {10 if (ty == RND) {11 lt.unGetToken();12 return new FunctionExpression(ty, new ConstantExpression(1));13 } else if (ty == FRE) {14 lt.unGetToken();15 return new FunctionExpression(ty, new ConstantExpression(0));16 }17 throw new BASICSyntaxError("Missing argument for function.");18 }19 switch (ty) {20 case RND:21 case INT:22 case SIN:23 case COS:24 case TAN:25 case ATN:26 case SQR:27 case ABS:28 case CHR:29 case VAL:30 case STR:31 case SPC:32 case TAB:33 case LOG:34 a = ParseExpression.expression(lt);35 if (a instanceof BooleanExpression) {36 throw new BASICSyntaxError(functions.toUpperCase()+" function cannot accept boolean expression.");37 }38 if ((ty == VAL) && (! a.isString()))39 throw new BASICSyntaxError(functions.toUpperCase()+" requires a string valued argument.");40 result = new FunctionExpression(ty, a);41 break; 42 default:43 throw new BASICSyntaxError("Unknown function on input.");4445 }46 t = lt.nextToken();47 if (! t.isSymbol(')')) {48 throw new BASICSyntaxError("Missing closing parenthesis for function.");49 }50 return result;51 }

zauważ, że ten kod wykorzystuje fakt, że parser wyrażenia W ParseStatement już zorientował się, że patrzy na wyrażenie i przekazał tożsamość wyrażenia w jako parametr ty. Ten parser musi wtedy tylko zlokalizować nawias otwierający i nawias zamykający, które zawierają argument(y). Ale przyjrzyj się uważnie: w wierszach od #9 do #18, parser pozwala niektórym funkcjom nie mieć argumentów (w tym przypadku RND i FRE). Pokazuje to elastyczność zapewnianą przez wbudowanie podczęści funkcji do tej klasy, zamiast zmuszać wszystkie funkcje do zgodności z jakimś predefiniowanym szablonem. Biorąc pod uwagę typ funkcji w parametrze ty, Instrukcja switch wybiera gałąź, która może analizować argumenty wymagane dla tej funkcji, czy to ciągi znaków, liczby, inne wyrażenia itd.

inne aspekty: łańcuchy znaków i tablice

dwie inne części języka BASIC są zaimplementowane przez interpreter COCOA: łańcuchy znaków i tablice. Spójrzmy najpierw na implementację ciągów.

aby zaimplementować ciągi znaków jako zmienne, Klasa Expression została zmodyfikowana tak, aby zawierała pojęcie „string” wyrażeń. Ta modyfikacja miała formę dwóch dodatków: isStringi stringValue. Źródło tych dwóch nowych metod pokazano poniżej.

 String stringValue(Program pgm) throws BASICRuntimeError { throw new BASICRuntimeError("No String representation for this."); } boolean isString() { return false; }

oczywiste jest, że nie jest zbyt użyteczne dla programu BASIC pobieranie wartości łańcuchowej wyrażenia bazowego (które jest zawsze albo wyrażeniem numerycznym, albo logicznym). Z braku użyteczności można wywnioskować, że metody te nie należały wtedy do Expression, a zamiast tego należały do podklasy Expression. Jednakże, umieszczając te dwie metody w klasie bazowej, wszystkie obiekty Expression mogą być testowane, aby sprawdzić, czy w rzeczywistości są to łańcuchy znaków.

innym podejściem projektowym jest zwracanie wartości liczbowych jako ciągów znaków przy użyciu obiektu StringBuffer do wygenerowania wartości. Tak więc, na przykład, ten sam kod można przepisać jako:

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

a jeśli powyższy kod jest używany, możesz wyeliminować użycie isString, ponieważ każde wyrażenie może zwrócić wartość łańcuchową. Ponadto można zmodyfikować metodę value, aby spróbować zwrócić liczbę, jeśli wyrażenie zostanie zwrócone do łańcucha, uruchamiając je za pomocą metody valueOfjava.lang.Double. W wielu językach, takich jak Perl, TCL i REXX, ten rodzaj amorficznego typowania jest używany z dużą korzyścią. Oba podejścia są ważne i powinieneś dokonać wyboru w oparciu o projekt tłumacza. W BASIC, interpreter musi zwrócić błąd, gdy łańcuch jest przypisany do zmiennej numerycznej, więc wybrałem pierwsze podejście (zwracając błąd).

jeśli chodzi o tablice, istnieją różne sposoby projektowania języka do ich interpretacji. C używa nawiasów kwadratowych wokół elementów tablicy, aby odróżnić odwołania do indeksów tablicy od odwołań funkcji, które mają nawiasy wokół argumentów. Jednak projektanci języka BASIC zdecydowali się używać nawiasów zarówno dla funkcji, jak i tablic, więc gdy tekst NAME(V1, V2) jest widziany przez parser, może to być wywołanie funkcji lub odwołanie do tablicy.

analizator leksykalny rozróżnia między tokenami, po których następuje nawias, najpierw zakładając, że są funkcjami i testując je. Następnie sprawdza, czy są to słowa kluczowe lub zmienne. To właśnie ta decyzja uniemożliwia twojemu programowi zdefiniowanie zmiennej o nazwie ” SIN.”Każda zmienna, której nazwa pasuje do nazwy funkcji, zostanie zwrócona przez analizator leksykalny jako token funkcji. Drugą sztuczką, której używa analizator leksykalny, jest sprawdzenie, czy po nazwie zmiennej jest natychmiast” („. Jeśli tak, analizator zakłada, że jest to odniesienie do tablicy. Analizując to w analizatorze leksykalnym, eliminujemy ciąg ’MYARRAY ( 2 ) ’ z interpretacji jako prawidłowej tablicy (zwróć uwagę na spację między nazwą zmiennej a otwartym nawiasem).

ostatnią sztuczką do implementacji tablic jest klasa Variable. Ta klasa jest używana dla instancji zmiennej, i jak omówiłem w kolumnie ostatniego miesiąca, jest to podklasa Token. Jednak ma również pewne Maszyny do obsługi tablic i to jest to, co pokażę poniżej:

class Variable extends Token { // Legal variable sub types final static int NUMBER = 0; final static int STRING = 1; final static int NUMBER_ARRAY = 2; final static int STRING_ARRAY = 4; String name; int subType; /* * If the variable is in the symbol table these values are * initialized. */ int ndx; // array indices. int mult; // array multipliers double nArrayValues; String sArrayValues;

powyższy kod pokazuje zmienne instancji powiązane ze zmienną, tak jak w klasie ConstantExpression. Trzeba dokonać wyboru co do liczby klas, które mają być używane w porównaniu do złożoności klasy. Jednym z rozwiązań może być zbudowanie klasy Variable, która zawiera tylko zmienne skalarne, a następnie dodanie podklasy ArrayVariable, aby poradzić sobie z zawiłościami tablic. Postanowiłem je połączyć, zamieniając zmienne skalarne w tablice o długości 1.

jeśli przeczytasz powyższy kod, zobaczysz indeksy tablic i mnożniki. Są one tutaj, ponieważ wielowymiarowe tablice w BASIC są implementowane przy użyciu pojedynczej liniowej tablicy Java. Indeks liniowy w tablicy Java jest obliczany ręcznie przy użyciu elementów tablicy mnożników. Indeksy używane w programie BASIC są sprawdzane pod kątem ważności, porównując je z maksymalnym indeksem prawnym w tablicy indeksów NDX.

na przykład, podstawowa tablica o trzech wymiarach 10, 10 i 8, będzie miała wartości 10, 10 i 8 zapisane w ndx. Umożliwia to ewaluatorowi wyrażenia sprawdzenie warunku „index out of bounds” poprzez porównanie liczby używanej w programie BASIC z maksymalną liczbą prawną, która jest teraz przechowywana w ndx. Tablica mnożników w naszym przykładzie będzie zawierać wartości 1, 10 i 100. Stałe te reprezentują liczby, których używa się do mapowania ze specyfikacji indeksu wielowymiarowej tablicy w specyfikację indeksu tablicy liniowej. Rzeczywiste równanie to:

Java Index = Index1 + Index2 * maksymalny rozmiar Index1 + Index3 *(maksymalny rozmiar Index1 * MaxSizeIndex 2)

Następna tablica Java w klasie Variable jest pokazana poniżej.

 Expression expns;

tablica expns jest używana do obsługi tablic zapisanych jako „A(10*B, i).”W takim przypadku indeksy są raczej wyrażeniami niż stałymi, więc odniesienie musi zawierać wskaźniki do tych wyrażeń, które są obliczane w czasie wykonywania. Wreszcie jest ten dość brzydko wyglądający fragment kodu, który oblicza indeks w zależności od tego, co zostało przekazane w programie. Ta prywatna metoda jest pokazana poniżej.

 private int computeIndex(int ii) throws BASICRuntimeError { int offset = 0; if ((ndx == null) || (ii.length != ndx.length)) throw new BASICRuntimeError("Wrong number of indices."); for (int i = 0; i < ndx.length; i++) { if ((ii < 1) || (ii > ndx)) throw new BASICRuntimeError("Index out of range."); offset = offset + (ii-1) * mult; } return offset; }

patrząc na powyższy kod, zauważysz, że kod najpierw sprawdza, czy prawidłowa liczba indeksów została użyta podczas odwoływania się do tablicy, a następnie, że każdy indeks mieści się w zakresie prawnym dla tego indeksu. Jeśli zostanie wykryty błąd, zostanie wyrzucony wyjątek do interpretera. Metody numValue i stringValue zwracają wartość ze zmiennej odpowiednio jako liczbę lub łańcuch znaków. Te dwie metody są pokazane poniżej.

 double numValue(int ii) throws BASICRuntimeError { return nArrayValues; } String stringValue(int ii) throws BASICRuntimeError { if (subType == NUMBER_ARRAY) return ""+nArrayValues; return sArrayValues; }

istnieją dodatkowe metody do ustawiania wartości zmiennej, które nie są tutaj pokazane.

ukrywając wiele złożoności tego, jak każdy element jest implementowany, kiedy w końcu przychodzi czas na wykonanie programu BASIC, kod Java jest dość prosty.

uruchamianie kodu

kod do interpretacji podstawowych instrukcji i ich wykonania znajduje się w

run

metoda

Program

klasy. Kod tej metody jest pokazany poniżej, a ja krok przez to, aby wskazać interesujące części.

 1 public void run(InputStream in, OutputStream out) throws BASICRuntimeError { 2 PrintStream pout; 3 Enumeration e = stmts.elements(); 4 stmtStack = new Stack(); // assume no stacked statements ... 5 dataStore = new Vector(); // ... and no data to be read. 6 dataPtr = 0; 7 Statement s; 8 9 vars = new RedBlackTree();1011 // if the program isn't yet valid.12 if (! e.hasMoreElements())13 return;1415 if (out instanceof PrintStream) {16 pout = (PrintStream) out;17 } else {18 pout = new PrintStream(out);19 }

powyższy kod pokazuje, że metoda run przyjmuje InputStream i OutputStream do użycia jako „konsola” dla programu wykonującego. W wierszu 3 obiekt wyliczenia e jest ustawiony na zbiór instrukcji z kolekcji o nazwie stmts. Do tej kolekcji użyłem wariacji na temat binarnego drzewa wyszukiwania zwanego drzewem „czerwono-czarnym”. (Aby uzyskać więcej informacji na temat binarnych drzew wyszukiwania, zobacz moją poprzednią kolumnę na temat budowania kolekcji generycznych.) Następnie tworzone są dwie dodatkowe Kolekcje-jedna przy użyciu Stack i jedna przy użyciu Vector. Stos jest używany jak stos w każdym komputerze, ale wektor jest używany wyraźnie dla instrukcji danych w programie BASIC. Ostatnim zbiorem jest kolejne czerwono-czarne drzewo, które zawiera odniesienia do zmiennych zdefiniowanych przez program BASIC. To drzewo jest tabelą symboli używaną przez program podczas jego wykonywania.

po inicjalizacji ustawiane są strumienie wejściowe i wyjściowe, a następnie jeśli e nie jest równe null, zaczynamy od zebrania danych, które zostały zadeklarowane. Odbywa się to w sposób pokazany w poniższym kodzie.

 /* First we load all of the data statements */ while (e.hasMoreElements()) { s = (Statement) e.nextElement(); if (s.keyword == Statement.DATA) { s.execute(this, in, pout); } }

powyższa pętla po prostu patrzy na wszystkie instrukcje, a wszelkie znalezione instrukcje danych są następnie wykonywane. Wykonanie każdej instrukcji danych wstawia wartości zadeklarowane przez tę instrukcję do wektora magazynu danych. Następnie uruchamiamy właściwy program, który odbywa się za pomocą następnego fragmentu kodu:

 e = stmts.elements(); s = (Statement) e.nextElement(); do { int yyy; /* While running we skip Data statements. */ try { yyy = in.available(); } catch (IOException ez) { yyy = 0; } if (yyy != 0) { pout.println("Stopped at :"+s); push(s); break; } 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 widać w powyższym kodzie, pierwszym krokiem jest ponowne zainicjowanie e. następnym krokiem jest pobranie pierwszej instrukcji do zmiennej s, a następnie wprowadzenie pętli wykonania. Jest pewien kod do sprawdzenia, czy oczekujące dane wejściowe są w strumieniu wejściowym, aby umożliwić przerwanie postępu programu przez wpisanie w programie, a następnie pętla sprawdza, czy instrukcja do wykonania byłaby instrukcją danych. Jeśli tak, pętla pomija polecenie, ponieważ zostało już wykonane. Wymagana jest raczej zawiła technika wykonywania wszystkich instrukcji danych, ponieważ BASIC pozwala instrukcjom danych, które spełniają instrukcję READ, pojawić się w dowolnym miejscu w kodzie źródłowym. Wreszcie, jeśli śledzenie jest włączone, wypisywany jest rekord śledzenia i wywoływana jest bardzo uninpresywna Instrukcja s = s.execute(this, in, pout);. Piękno polega na tym, że cały wysiłek zamknięcia podstawowych pojęć w łatwe do zrozumienia klasy sprawia, że końcowy kod jest trywialny. Jeśli nie jest to trywialne, być może masz wskazówkę, że może być inny sposób na podzielenie projektu.

Podsumowanie i dalsze przemyślenia

interpreter został zaprojektowany tak, aby mógł działać jako wątek, więc może być kilka wątków interpretera Cocoa działających jednocześnie w przestrzeni programu w tym samym czasie. Co więcej, przy użyciu rozszerzenia funkcji możemy zapewnić sposób, w jaki te wątki mogą ze sobą współdziałać. Był program dla Apple II, a później dla PC i Unix o nazwie C-robots, który był systemem interakcji” robotycznych ” jednostek, które były zaprogramowane przy użyciu prostego podstawowego języka pochodnego. Gra dostarczyła mi i innym wiele godzin rozrywki, ale była również doskonałym sposobem na wprowadzenie podstawowych zasad obliczeń do młodszych uczniów (którzy błędnie wierzyli, że po prostu grają, a nie uczą się). Podsystemy interpretera oparte na Javie są znacznie potężniejsze niż ich wcześniejsze odpowiedniki Javy, ponieważ są natychmiast dostępne na każdej platformie Java. COCOA działa na systemach Unix i Macintoshach tego samego dnia, w którym pracowałem na komputerze z systemem Windows 95. Podczas gdy Java zostaje pobita przez niezgodności w implementacjach thread lub window toolkit, często pomijane jest to: dużo kodu ” po prostu działa.”

Chuck McManis jest obecnie dyrektorem ds. oprogramowania systemowego w FreeGate Corp., start-upu finansowanym przez przedsięwzięcie, który bada możliwości na rynku internetowym. Przed rozpoczęciem FreeGate Chuck był członkiem grupy Java. Dołączył do grupy Java tuż po utworzeniu FirstPerson Inc. i był członkiem portable OS group (grupy odpowiedzialnej za OSportion Java). Później, gdy FirstPerson został rozwiązany, pozostał z grupą poprzez rozwój alpha i betaversji platformy Java. Stworzył pierwszą stronę główną „all Java” w Internecie, gdy w maju 1995 r.zrobił programowanie dla strony głównej Javaversion Of The Sun. Opracował również bibliotekę acryptographic dla Javy oraz wersje Java classloader, które mogą wyświetlać klasy oparte na podpisach cyfrowych.Zanim dołączył do FirstPerson, Chuck pracował w obszarze systemów operacyjnych SunSoft, rozwijając aplikacje sieciowe, gdzie wykonał wstępny projekt NIS+. Sprawdź też jego stronę główną. : END_BIO

dowiedz się więcej na ten temat

  • oto linki do plików źródłowych wymienionych powyżej:
    • ConstantExpression.java
    • FunctionExpression.program java
    • .Instrukcja java
    • .java
    • StringExpression.Java
    • zmienna.java
    • VariableExpression.java
  • a oto … Plik ZIP plików źródłowych:
    .zip
  • „Uncommon Lisp” — interpreter Lispu napisany w Javie
    http://user03.blue.aol.com/thingtone/workshop/lisp.htm
  • interpreter Netrexx Mike ’ a Cowlishawa napisany w Javie
    http://www2.hursley.ibm.com/netrexx/
  • stara Kopia Usenet BASIC FAQ (nadal zawiera przydatne informacje.)
    http://whitworth.me.ic.ac.uk/people/students/djbur/qbasic.htm
  • COCOA, podstawowy interpreter napisany w Javie
    http://www.mcmanis.com/~cmcmanis/java/javaworld/examples/BASIC.html
  • Chuck ’ s Java resources page
    http://www.mcmanis.com/~cmcmanis/java/javaworld/
  • Interpreter TCL napisany w Javie
    http://www.cs.cornell.edu/home/ioi/Jacl/
  • :
  • „Jak zbudować interpreter w Javie, CZ. 2struktura”
    sztuczka do złożenia klas podstawowych dla prostego interpretera.
  • „Jak zbudować interpreter w Javie, część 1podstawy”
    w przypadku złożonych aplikacji wymagających języka skryptowego, Java może być użyta do implementacji interpretera, dodając możliwości Skryptowe do dowolnej aplikacji Java.
  • „Analiza leksykalna, część 2budowanie aplikacji”
    jak używać obiektu StreamTokenizer do implementacji interaktywnego kalkulatora.
  • ” Analiza leksykalna i JavaPart 1″
    dowiedz się, jak konwertować tekst czytelny dla człowieka na dane czytelne dla komputera za pomocą klas StringTokenizer i StreamTokenizer.
  • „ponowne wykorzystanie kodu i systemy obiektowe”
    Użyj klasy pomocniczej, aby wymusić dynamiczne zachowanie.
  • ” obsługa kontenerów dla obiektów w Javie 1.0.2″
    organizowanie obiektów jest łatwe po umieszczeniu ich w kontenerach. W tym artykule omówimy projektowanie i implementację kontenera.
  • ” podstawy Java class loaders”
    podstawy tego kluczowego komponentu architektury Java.
  • ” nie używaj garbage collection”
    Minimalizuj thrashing sterty w programach Java.
  • „Threads and aplets and visual controls”
    ta ostatnia część serii bada odczyt wielu kanałów danych.
  • „Korzystanie z kanałów komunikacyjnych w apletach, Część 3”
    rozwijaj techniki Visual Basic-style do projektowania apletów-i konwertuj temperatury w procesie.
  • ” Synchronizowanie wątków w Javie, Część II”
    dowiedz się, jak napisać klasę kanału danych, a następnie utwórz prostą przykładową aplikację, która ilustruje rzeczywistą implementację klasy.
  • ” Synchronizowanie wątków w Javie”
    były programista zespołu Java Chuck McManis przedstawia prosty przykład ilustrujący, jak synchronizować wątki, aby zapewnić niezawodne i przewidywalne zachowanie apletu.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.