Programmering

Byg en tolk i Java - Implementer udførelsesmotoren

Forrige 1 2 3 Side 2 Næste Side 2 af 3

Andre aspekter: Strenge og arrays

To andre dele af BASIC-sproget implementeres af COCOA-tolk: strenge og arrays. Lad os se på implementeringen af ​​strenge først.

For at implementere strenge som variabler, Udtryk klasse blev ændret til at omfatte begrebet "streng" -udtryk. Denne ændring tog form af to tilføjelser: isString og stringValue. Kilden til disse to nye metoder er vist nedenfor.

 String stringValue (Program pgm) kaster BASICRuntimeError {kast ny BASICRuntimeError ("Ingen strengrepræsentation for dette."); } boolsk isString () {returner falsk; } 

Det er klart, at det ikke er så nyttigt for et BASIC-program at få strengværdien af ​​et basisudtryk (som altid er enten numerisk eller boolsk udtryk). Du kan af den manglende nytte konkludere, at disse metoder derefter ikke hørte hjemme i Udtryk og hørte hjemme i en underklasse af Udtryk i stedet. Men ved at placere disse to metoder i basisklassen, alle Udtryk objekter kan testes for at se, om de faktisk er strenge.

En anden designtilgang er at returnere de numeriske værdier som strenge ved hjælp af a StringBuffer objekt for at generere en værdi. Så for eksempel kunne den samme kode omskrives som:

 String stringValue (Program pgm) kaster BASICRuntimeError {StringBuffer sb = ny StringBuffer (); sb.append (denne. værdi (pgm)); returner sb.toString (); } 

Og hvis ovenstående kode bruges, kan du fjerne brugen af isString fordi hvert udtryk kan returnere en strengværdi. Yderligere kan du ændre værdi metode til at prøve at returnere et tal, hvis udtrykket evalueres til en streng ved at køre det gennem Værdi af metode til java.lang. dobbelt. På mange sprog som Perl, TCL og REXX bruges denne form for amorf skrivning med stor fordel. Begge tilgange er gyldige, og du bør foretage dit valg ud fra designet af din tolk. I BASIC skal tolken returnere en fejl, når en streng tildeles en numerisk variabel, så jeg valgte den første tilgang (returnering af en fejl).

Med hensyn til arrays er der forskellige måder, hvorpå du kan designe dit sprog til at fortolke dem. C bruger de firkantede parenteser omkring arrayelementer til at skelne arrayets indeksreferencer fra funktionsreferencer, der har parenteser omkring deres argumenter. Imidlertid valgte sprogdesignerne for BASIC at bruge parenteser til begge funktioner og arrays, så når teksten NAVN (V1, V2) ses af parseren, kan det enten være et funktionsopkald eller en matrixreference.

Den leksikale analysator skelner mellem tokens, der efterfølges af parenteser ved først at antage, at de er funktioner og teste for det. Derefter fortsætter det med at se, om det er nøgleord eller variabler. Det er denne beslutning, der forhindrer dit program i at definere en variabel med navnet "SIN." Enhver variabel, hvis navn matchede et funktionsnavn, returneres af den leksikale analysator som et funktionstoken i stedet. Det andet trick, som den leksikale analysator bruger, er at kontrollere, om variabelnavnet straks efterfølges af '('. Hvis det er tilfældet, antager analysatoren, at det er en matrixreference. Ved at analysere dette i den leksikale analysator eliminerer vi strengen 'MYARRAY (2)'fra at blive fortolket som et gyldigt array (bemærk mellemrummet mellem variabelnavnet og den åbne parentes).

Det sidste trick til implementering af arrays er i Variabel klasse. Denne klasse bruges til en forekomst af en variabel, og som jeg diskuterede i sidste måneds kolonne, er den en underklasse af Polet. Det har dog også nogle maskiner til understøttelse af arrays, og det er det, jeg vil vise nedenfor:

klasse Variabel udvider Token {// Legal variable sub types final static int NUMBER = 0; endelig statisk int STRING = 1; endelig statisk int NUMBER_ARRAY = 2; endelig statisk int STRING_ARRAY = 4; Strengnavn; int undertype; / * * Hvis variablen er i symboltabellen, initialiseres disse værdier *. * / int ndx []; // matrixindekser. int mult []; // array multiplikatorer dobbelt nArrayValues ​​[]; String sArrayValues ​​[]; 

Ovenstående kode viser de forekomstvariabler, der er knyttet til en variabel, som i ConstantExpression klasse. Man skal træffe et valg om antallet af klasser, der skal bruges kontra kompleksiteten af ​​en klasse. Et designvalg kan være at bygge en Variabel klasse, der kun indeholder skalarvariabler og derefter tilføje en ArrayVariable underklasse til at håndtere kompleksiteten af ​​arrays. Jeg valgte at kombinere dem og omdanne skalarvariabler i det væsentlige til arrays med længde 1.

Hvis du læser ovenstående kode, vil du se matrixindekser og multiplikatorer. Disse er her, fordi flerdimensionelle arrays i BASIC implementeres ved hjælp af et enkelt lineært Java-array. Det lineære indeks i Java-arrayet beregnes manuelt ved hjælp af elementerne i multiplikatorarrayet. De indekser, der anvendes i BASIC-programmet, kontrolleres for gyldighed ved at sammenligne dem med det maksimale lovlige indeks i indekserne ' ndx array.

For eksempel vil et BASIC-array med tre dimensioner på 10, 10 og 8 have værdierne 10, 10 og 8 gemt i ndx. Dette gør det muligt for ekspressionsevaluatoren at teste for en "indeks uden for grænser" -tilstand ved at sammenligne det anvendte nummer i BASIC-programmet med det maksimale lovlige antal, der nu er gemt i ndx. Multiplikatorarrayet i vores eksempel ville indeholde værdierne 1, 10 og 100. Disse konstanter repræsenterer de tal, man bruger til at kortlægge fra en flerdimensionel matrixindeksspecifikation til en lineær arrayindeksspecifikation. Den aktuelle ligning er:

Java-indeks = Index1 + Index2 * Maks. Størrelse på Index1 + Index3 * (MaxSize af Index1 * MaxSizeIndex 2)

Det næste Java-array i Variabel klasse er vist nedenfor.

 Ekspression udløber []; 

Det expns array bruges til at håndtere arrays, der er skrevet som "A (10 * B, i). "I så fald er indekserne faktisk udtryk snarere end konstanter, så henvisningen skal indeholde henvisninger til de udtryk, der evalueres ved kørselstid. Endelig er der dette ret grimt stykke kode, der beregner indekset afhængigt af hvad blev bestået i programmet. Denne private metode er vist nedenfor.

 private int computeIndex (int ii []) kaster BASICRuntimeError {int offset = 0; hvis ((ndx == null) || (ii.længde! = ndx.længde)) smider nyt BASICRuntimeError ("Forkert antal indekser."); for (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) smid ny BASICRuntimeError ("Indeks uden for rækkevidde."); offset = offset + (ii [i] -1) * mult [i]; } return offset; } 

Når du ser på koden ovenfor, vil du bemærke, at koden først kontrollerer for at se, at det korrekte antal indekser blev brugt, når der henvises til arrayet, og derefter at hvert indeks var inden for det lovlige interval for det indeks. Hvis der opdages en fejl, kastes en undtagelse til tolken. Metoderne numValue og stringValue returnere en værdi fra variablen som henholdsvis et tal eller en streng. Disse to metoder er vist nedenfor.

 dobbelt numValue (int ii []) kaster BASICRuntimeError {return nArrayValues ​​[computeIndex (ii)]; } String stringValue (int ii []) kaster BASICRuntimeError {if (subType == NUMBER_ARRAY) return "" + nArrayValues ​​[computeIndex (ii)]; returner sArrayValues ​​[computeIndex (ii)]; } 

Der er yderligere metoder til at indstille værdien af ​​en variabel, der ikke vises her.

Ved at skjule meget af kompleksiteten af, hvordan hvert stykke implementeres, når det endelig er tid til at udføre BASIC-programmet, er Java-koden ret ligetil.

Kører koden

Koden til fortolkning af BASIC-erklæringerne og udførelse af dem er indeholdt i

løb

metode til

Program

klasse. Koden til denne metode er vist nedenfor, og jeg går igennem den for at påpege de interessante dele.

 1 offentlig ugyldig kørsel (InputStream ind, OutputStream ud) kaster BASICRuntimeError {2 PrintStream pout; 3 Optælling e = stmts.elements (); 4 stmtStack = ny stak (); // antage ingen stablede udsagn ... 5 dataStore = new Vector (); // ... og ingen data, der skal læses. 6 dataPtr = 0; 7 Erklæring s; 8 9 vars = nye RedBlackTree (); 10 11 // hvis programmet endnu ikke er gyldigt. 12 hvis (! E.hasMoreElements ()) 13 returnerer; 14 15 hvis (ud for eksempel af PrintStream) {16 pout = (PrintStream) ud; 17} ellers {18 pout = ny PrintStream (ud); 19} 

Ovenstående kode viser, at løb metoden tager en InputStream og en OutputStream til brug som "konsol" til eksekveringsprogrammet. I linje 3 er optællingsobjektet e er indstillet til sæt sætninger fra den navngivne samling stmts. Til denne samling brugte jeg en variation på et binært søgetræ kaldet et "rød-sort" træ. (For yderligere information om binære søgetræer, se min tidligere kolonne om opbygning af generiske samlinger.) Derefter oprettes to yderligere samlinger - en ved hjælp af en Stak og en bruger en Vektor. Stakken bruges som stakken i enhver computer, men vektoren bruges udtrykkeligt til DATA-udsagnene i BASIC-programmet. Den endelige samling er et andet rød-sort træ, der indeholder referencerne for de variabler, der er defineret af BASIC-programmet. Dette træ er symboltabellen, der bruges af programmet, mens det udføres.

Efter initialiseringen er input- og outputstrømmene oprettet, og derefter hvis e er ikke nul, starter vi med at indsamle de data, der er erklæret. Det gøres som vist i den følgende kode.

 / * Først indlæser vi alle datasætningerne * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); hvis (s.keyword == Statement.DATA) {s.execute (dette, i, pout); }} 

Ovenstående sløjfe ser simpelthen på alle udsagnene, og eventuelle DATA-udsagn, den finder, udføres derefter. Udførelsen af ​​hver DATA-sætning indsætter de værdier, der er erklæret af denne erklæring, i dataStore vektor. Derefter udfører vi det korrekte program, hvilket gøres ved hjælp af dette næste stykke kode:

 e = stmts.elements (); s = (Erklæring) e.nextElement (); gør {int yyy; / * Mens vi kører, springer vi over dataudsagn. * / prøv {yyy = in.available (); } fangst (IOException ez) {ååå = 0; } hvis (ååå! = 0) {pout.println ("Stoppet ved:" + s); skubbe (r) pause; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (this, (traceFile! = null)? traceFile: pout); } s = s. udfør (dette, i, pout); } andet s = næsteStatement (er); } mens (s! = null); } 

Som du kan se i koden ovenfor, er det første trin at geninitialisere e. Det næste trin er at hente den første sætning i variablen s og derefter for at komme ind i eksekveringssløjfen. Der er en kode, der skal kontrolleres for afventende input på inputstrømmen for at muliggøre afbrydelse af programmets forløb ved at skrive på programmet, og derefter kontrollerer sløjfen for at se, om udsagnet, der skal udføres, ville være en DATA-sætning. Hvis det er tilfældet, springer løkken over udsagnet, da det allerede var udført. Den temmelig indviklede teknik til at udføre alle datasæt først er påkrævet, fordi BASIC tillader, at DATA-sætningerne, der tilfredsstiller en LÆS-sætning, kan vises overalt i kildekoden. Endelig, hvis sporing er aktiveret, udskrives en sporingspost og den meget ikke-imponerende erklæring s = s. udfør (dette, i, pout); påberåbes. Skønheden er, at al indsats for at indkapsle basisbegreberne i letforståelige klasser gør den endelige kode triviel. Hvis det ikke er trivielt, har du måske en anelse om, at der muligvis er en anden måde at opdele dit design på.

Indpakning og yderligere tanker

Tolken blev designet, så den kunne køre som en tråd, og der kan således være flere COCOA-tolketråde, der kører samtidigt i dit programrum på samme tid. Desuden kan vi ved hjælp af funktionsudvidelse tilvejebringe et middel, hvorved disse tråde kan interagere med hinanden. Der var et program til Apple II og senere til pc'en og Unix kaldet C-robotter, der var et system af interagerende "robotiske" enheder, der blev programmeret ved hjælp af et simpelt BASIC-afledt sprog. Spillet gav mig og andre mange timers underholdning, men var også en glimrende måde at introducere de grundlæggende principper for beregning for yngre studerende (som fejlagtigt mente, at de bare spillede og ikke lærte). Java-baserede tolkeundersystemer er meget mere kraftfulde end deres kolleger før Java, fordi de er tilgængelige med det samme på enhver Java-platform. COCOA kørte på Unix-systemer og Macintoshes samme dag, hvor jeg arbejdede på en Windows 95-baseret pc. Mens Java bliver slået op af inkompatibiliteter i tråd- eller vinduesværktøjsimplementeringerne, er det ofte, der overses, dette: En masse kode "virker bare."