Kako izraditi tumač u Javi, 1. dio: OSNOVE

Kad sam prijatelju rekao da sam napisao prevoditelja BASIC na Javi, nasmijao se toliko da je sodu koju je držao zamalo prosuo po odjeći. "Zašto biste uopće stvorili OSNOVNI tumač na Javi?" bilo je predvidljivo prvo pitanje iz njegovih usta. Odgovor je jednostavan i složen. Jednostavan odgovor je da je bilo zabavno pisati tumača na Javi, a ako bih htio napisati tumača, mogao bih napisati i onog za koji me vežu lijepe uspomene iz ranih dana osobnog računanja. Sa složene strane, primijetio sam da su mnogi ljudi koji danas koriste Javu prešli točku stvaranja klonulih Dukeovih apleta i prelaze na ozbiljne programe. Često biste prilikom izrade aplikacije željeli da se ona može konfigurirati.Mehanizam izbora za ponovnu konfiguraciju je neka vrsta mehanizma za dinamičko izvršavanje.

Poznato kao makro jezici ili konfiguracijski jezici, dinamičko izvršavanje je značajka koja omogućuje da korisnik "programira" aplikaciju. Prednost dinamičkog pokretačkog mehanizma je u tome što se alati i aplikacije mogu prilagoditi za izvršavanje složenih zadataka bez zamjene alata. Java platforma nudi široku paletu mogućnosti dinamičkog izvršenja.

HotJava i druge vruće opcije

Istražimo ukratko neke od dostupnih opcija pokretačkog mehanizma za dinamičko izvršavanje, a zatim detaljno razmotrimo implementaciju mog tumača. Stroj za dinamičko izvršavanje ugrađeni je tumač. Za rad tumača potrebna su tri sredstva:

  1. Sredstvo za učitavanje uputa
  2. Format modula za pohranu uputa koje treba izvršiti
  3. Model ili okruženje za interakciju s glavnim programom

HotJava

Najpoznatiji ugrađeni tumač mora biti HotJava "aplet" okruženje koje je u potpunosti preoblikovalo način na koji ljudi gledaju na web preglednike.

Model "apleta" HotJava zasnovan je na predodžbi da bi Java aplikacija mogla stvoriti generičku osnovnu klasu s poznatim sučeljem, a zatim dinamički učitati potklase te klase i izvršiti ih u vrijeme izvođenja. Ti su apleti pružili nove mogućnosti i, unutar granica osnovne klase, omogućili dinamičko izvršavanje. Ova mogućnost dinamičkog izvršavanja temeljni je dio Java okruženja i jedna od stvari koja ga čini tako posebnim. U određenom ćemo stupnju detaljno razmotriti ovo okruženje.

GNU EMACS

Prije dolaska HotJave, možda najuspješnija aplikacija s dinamičkim izvršavanjem bila je GNU EMACS. Makro jezik nalik ovom LISP-u ovog uređivača postao je glavna stvar za mnoge programere. Ukratko, EMACS LISP okruženje sastoji se od interpretera LISP-a i mnogih funkcija tipa uređivanja koje se mogu koristiti za sastavljanje najsloženijih makronaredbi. Ne treba smatrati iznenađujućim da je EMACS editor izvorno napisan u makronaredbama dizajniranim za editor pod nazivom TECO. Dakle, dostupnost bogatog (ako nečitkog) makro jezika u TECO-u omogućila je izgradnju potpuno novog uređivača. Danas je GNU EMACS osnovni uređivač, a čitave igre napisane su samo u EMACS LISP kodu, poznatom kao el-code. Ova konfiguracijska sposobnost učinila je GNU EMACS glavnim urednikom,dok su terminali VT-100 na kojima je dizajniran za rad postali puke fusnote u pisačevoj kolumni.

REXX

Jedan od mojih najdražih jezika, koji nikada nije uspio probuditi zasluženo, bio je REXX, koji je dizajnirao Mike Cowlishaw iz IBM-a. Tvrtki je bio potreban jezik za upravljanje aplikacijama na velikim mainframe računalima koji rade s VM operativnim sustavom. Otkrio sam REXX na Amigi gdje je bio usko povezan sa širokim spektrom aplikacija putem "REXX portova". Ti su priključci omogućili daljinsko upravljanje aplikacijama putem REXX tumača. Ova sprega tumača i aplikacije stvorila je puno snažniji sustav nego što je to bilo moguće s njegovim sastavnim dijelovima. Srećom, jezik živi u NETREXX-u, verziji koju je napisao Mike, a koja je kompajlirana u Java kod.

Dok sam gledao NETREXX i mnogo raniji jezik (LISP na Javi), pogodilo me da su ti jezici činili važne dijelove priče o Java aplikaciji. Ima li boljeg načina da ispričamo ovaj dio priče nego da ovdje napravimo nešto zabavno - poput uskrsnuća BASIC-80? Još važnije, bilo bi korisno prikazati jedan način na koji se skriptni jezici mogu pisati na Javi i, kroz njihovu integraciju s Javom, pokazati kako mogu poboljšati mogućnosti vaših Java aplikacija.

OSNOVNI zahtjevi za poboljšanje vaših Java aplikacija

OSNOVNI je, jednostavno, osnovni jezik. Postoje dvije škole mišljenja o tome kako netko može pisati tumača za to. Jedan od pristupa je pisanje programske petlje u kojoj program tumača čita jedan redak teksta iz protumačenog programa, raščlanjuje ga, a zatim poziva potprogram da ga izvrši. Slijed čitanja, raščlanjivanja i izvršavanja ponavlja se sve dok jedna od izjava protumačenog programa ne kaže tumaču da prestane.

Drugi i mnogo zanimljiviji način rješavanja projekta zapravo je raščlamba jezika na stablo raščlanjivanja, a zatim izvršavanje stabla raščlanjivanja "na mjestu". Ovako djeluju tokenizirani prevoditelji i način na koji sam odabrao postupak. Tokeniziranje tumača je također brže jer im nije potrebno ponovno skenirati ulaz svaki put kad izvrše naredbu.

Kao što sam gore spomenuo, tri komponente potrebne za postizanje dinamičkog izvršavanja su sredstvo za učitavanje, format modula i okruženje izvršenja.

Prvom komponentom, sredstvom za učitavanje, bavit će se Java InputStream. Kako su ulazni tokovi temeljni u I / O arhitekturi Java, sustav je dizajniran za čitanje u programu iz programa InputStreami pretvaranje u izvršni oblik. Ovo predstavlja vrlo fleksibilan način unošenja koda u sustav. Naravno, protokol za podatke koji prolaze kroz ulazni tok bit će OSNOVNI izvorni kod. Važno je napomenuti da se može koristiti bilo koji jezik; nemojte pogriješiti misleći da se ova tehnika ne može primijeniti na vašu aplikaciju.

Nakon unosa izvornog koda protumačenog programa u sustav, sustav pretvara izvorni kod u interni prikaz. Odlučio sam koristiti stablo raščlanjivanja kao format internog predstavljanja za ovaj projekt. Jednom kada se stvori stablo raščlanjivanja, njime se može manipulirati ili izvršiti.

Treća komponenta je okruženje izvršenja. Kao što ćemo vidjeti, zahtjevi za ovu komponentu prilično su jednostavni, ali implementacija ima nekoliko zanimljivih zaokreta.

Vrlo brza osnovna tura

Za one od vas koji možda nikada nisu čuli za BASIC, dat ću vam kratki uvid u jezik kako biste mogli razumjeti izazove raščlanjivanja i izvršavanja. Za više informacija o BASIC-u toplo preporučujem resurse na kraju ovog stupca.

BASIC je skraćenica od Početnički višenamjenski simbolički nastavni kod, a razvijen je na Sveučilištu Dartmouth kako bi podučavao računske koncepte studentima dodiplomskih studija. Od svog razvoja, BASIC se razvio u razne dijalekte. Najjednostavniji od ovih dijalekata koriste se kao upravljački jezici za regulatore industrijskih procesa; najsloženiji dijalekti su strukturirani jezici koji uključuju neke aspekte objektno orijentiranog programiranja. Za svoj sam projekt odabrao dijalekt poznat kao BASIC-80 koji je bio popularan u operativnom sustavu CP / M krajem sedamdesetih. Ovaj je dijalekt tek umjereno složeniji od najjednostavnijih dijalekata.

Sintaksa iskaza

Svi su retci izjava oblika

[: [: ...]]

gdje je "Linija" broj retka iskaza, "Ključna riječ" je OSNOVNA ključna riječ izraza, a "Parametri" su skup parametara povezanih s tom ključnom riječi.

Broj retka ima dvije svrhe: Služi kao oznaka za naredbe koje kontroliraju tijek izvršavanja, poput gotoizraza, i služi kao oznaka za sortiranje izjava umetnutih u program. Kao oznaka za sortiranje, broj retka olakšava okruženje za uređivanje redaka u kojem se uređivanje i obrada naredbi miješaju u jednoj interaktivnoj sesiji. Usput, to je bilo potrebno kad ste imali samo teletip. :-)

While not very elegant, line numbers do give the interpreter environment the ability to update the program one statement at a time. This ability stems from the fact that a statement is a single parsed entity and can be linked in a data structure with line numbers. Without line numbers, often it is necessary to re-parse the entire program when a line changes.

The keyword identifies the BASIC statement. In the example, our interpreter will support a slightly extended set of BASIC keywords, including goto, gosub, return, print, if, end, data, restore, read, on, rem, for, next, let, input, stop, dim, randomize, tron, and troff. Obviously, we won't go over all of these in this article, but there will be some documentation online in my next month's "Java In Depth" for you to explore.

Each keyword has a set of legal keyword parameters that can follow it. For example, the goto keyword must be followed by a line number, the if statement must be followed by a conditional expression as well as the keyword then -- and so on. The parameters are specific to each keyword. I'll cover a couple of these parameter lists in detail a bit later.

Expressions and operators

Often, a parameter specified in a statement is an expression. The version of BASIC I'm using here supports all of the standard mathematical operations, logical operations, exponentiation, and a simple function library. The most important component of the expression grammar is the ability to call functions. The expressions themselves are fairly standard and similar to the ones parsed by the example in my previous StreamTokenizer column.

Variables and data types

Part of the reason BASIC is such a simple language is because it has only two data types: numbers and strings. Some scripting languages, such as REXX and PERL, don't even make this distinction between data types until they are used. But with BASIC, a simple syntax is used to identify data types.

Variable names in this version of BASIC are strings of letters and numbers that always start with a letter. Variables are not case-sensitive. Thus A, B, FOO, and FOO2 are all valid variable names. Furthermore, in BASIC, the variable FOOBAR is equivalent to FooBar. To identify strings, a dollar sign ($) is appended to the variable name; thus, the variable FOO$ is a variable containing a string.

Finally, this version of the language supports arrays using the dim keyword and a variable syntax of the form NAME(index1, index2, ...) for up to four indices.

Program structure

Programs in BASIC start by default at the lowest numbered line and continue until there are either no more lines to process or the stop or end keywords are executed. A very simple BASIC program is shown below:

100 REM This is probably the canonical BASIC example 110 REM Program. Note that REM statements are ignored. 120 PRINT "This is a test program." 130 PRINT "Summing the values between 1 and 100" 140 LET total = 0 150 FOR I = 1 TO 100 160 LET total = total + i 170 NEXT I 180 PRINT "The total of all digits between 1 and 100 is " total 190 END 

The line numbers above indicate the lexical order of the statements. When they are run, lines 120 and 130 print messages to the output, line 140 initializes a variable, and the loop in lines 150 through 170 update the value of that variable. Finally, the results are printed out. As you can see, BASIC is a very simple programming language and therefore an ideal candidate for teaching computation concepts.

Organizing the approach

Typical of scripting languages, BASIC involves a program composed of many statements that run in a particular environment. The design challenge, then, is to construct the objects to implement such a system in a useful way.

When I looked at the problem, a straightforward data structure fairly leaped out at me. That structure is as follows:

The public interface to the scripting language shall consist of

  • A factory method that takes source code as input and returns an object representing the program.
  • An environment that provides the framework in which the program executes, including "I/O" devices for text input and text output.
  • A standard way of modifying that object, perhaps in the form of an interface, that allows the program and the environment to be combined to achieve useful results.

Internally, the structure of the interpreter was a bit more complicated. The question was how to go about factoring the two facets of the scripting language, parsing and execution? Three groups of classes resulted -- one for parsing, one for the structural framework of representing parsed and executable programs, and one that formed the base environment class for execution.

In the parsing group, the following objects are required:

  • Leksička analiza za obradu koda kao teksta
  • Raščlanjivanje izraza, za konstrukciju raščlanjivanja stabala izraza
  • Raščlanjivanje izjava za izradu stabala za raščlanjivanje samih izjava
  • Klase pogrešaka za prijavljivanje pogrešaka u raščlanjivanju

Okvirnu skupinu čine objekti koji sadrže stabla raščlanjivanja i varijable. To uključuje:

  • Objekt izraza s mnogo specijaliziranih potklasa za predstavljanje raščlanjenih izraza
  • Objekt izraza koji predstavlja izraze za ocjenu
  • Promjenjivi objekt s mnogo specijaliziranih potklasa za predstavljanje atomskih primjeraka podataka