Andre aspekter: Strenger og arrays
To andre deler av BASIC-språket er implementert av COCOA-tolk: strenger og arrays. La oss se på implementeringen av strenger først.
For å implementere strenger som variabler, er Uttrykk
klasse ble endret for å inkludere begrepet "streng" -uttrykk. Denne modifikasjonen hadde form av to tillegg: isString
og strengverdi
. Kilden for disse to nye metodene er vist nedenfor.
String stringValue (Program pgm) kaster BASICRuntimeError {kast ny BASICRuntimeError ("Ingen strengrepresentasjon for dette."); } boolsk isString () {return false; }
Det er tydeligvis ikke så nyttig for et BASIC-program å få strengverdien til et basisuttrykk (som alltid er enten numerisk eller boolsk uttrykk). Du kan konkludere fra mangelen på nytte at disse metodene da ikke hørte hjemme Uttrykk
og hørte hjemme i en underklasse av Uttrykk
i stedet. Imidlertid, ved å sette disse to metodene i basisklassen, alle Uttrykk
objekter kan testes for å se om de faktisk er strenger.
En annen designtilnærming er å returnere de numeriske verdiene som strenger ved hjelp av a StringBuffer
objekt for å generere en verdi. Så for eksempel kan den samme koden skrives om som:
String stringValue (Program pgm) kaster BASICRuntimeError {StringBuffer sb = new StringBuffer (); sb.append (denne.verdien (pgm)); returner sb.toString (); }
Og hvis koden ovenfor brukes, kan du eliminere bruken av isString
fordi hvert uttrykk kan returnere en strengverdi. Videre kan du endre verdi
metode for å prøve å returnere et tall hvis uttrykket evalueres til en streng ved å kjøre det gjennom verdien av
Metode av java.lang. dobbelt
. På mange språk som Perl, TCL og REXX brukes denne typen amorf typing til stor fordel. Begge tilnærmingene er gyldige, og du bør ta et valg basert på tolkens design. I BASIC må tolken returnere en feil når en streng er tildelt en numerisk variabel, så jeg valgte den første tilnærmingen (returnerer en feil).
Når det gjelder matriser, er det forskjellige måter du kan designe språket ditt for å tolke dem. C bruker firkantede parenteser rundt matriseelementer for å skille mellom matrisens indeksreferanser fra funksjonsreferanser som har parenteser rundt argumentene. Imidlertid valgte språkdesignerne for BASIC å bruke parenteser for begge funksjoner og matriser så når teksten NAVN (V1, V2)
blir sett av parseren, kan det være enten en funksjonsanrop eller en matrisereferanse.
Den leksikale analysatoren skiller mellom tokens som blir fulgt av parenteser ved først å anta at de er funksjoner og teste for det. Så fortsetter det å se om det er nøkkelord eller variabler. Det er denne avgjørelsen som forhindrer at programmet ditt definerer en variabel som heter "SIN." Enhver variabel hvis navn samsvarer med et funksjonsnavn, vil i stedet bli returnert av den leksikale analysatoren som et funksjonstoken. Det andre trikset den leksikale analysatoren bruker, er å sjekke om variabelnavnet blir umiddelbart fulgt av `('. Hvis det er det, antar analysatoren at det er en matereferanse. Ved å analysere dette i den leksikale analysatoren, eliminerer vi strengen`MYARRAY (2)
'fra å bli tolket som en gyldig matrise (merk mellomrom mellom variabelnavnet og den åpne parentesen).
Det siste trikset for å implementere matriser er i Variabel
klasse. Denne klassen brukes til en forekomst av en variabel, og som jeg diskuterte i forrige måneds kolonne, er den en underklasse av Token
. Imidlertid har den også noen maskiner for å støtte matriser, og det er det jeg vil vise nedenfor:
klasse Variabel utvider Token {// Juridiske variabler undertyper endelig statisk int NUMBER = 0; endelig statisk int STRING = 1; endelig statisk int NUMBER_ARRAY = 2; endelig statisk int STRING_ARRAY = 4; Strengnavn; int undertype; / * * Hvis variabelen er i symboltabellen, initialiseres disse verdiene *. * / int ndx []; // matriseindekser. int mult []; // array multiplikatorer doble nArrayValues []; Streng sArrayValues [];
Ovennevnte kode viser forekomstvariablene knyttet til en variabel, som i ConstantExpression
klasse. Man må ta et valg om antall klasser som skal brukes kontra kompleksiteten til en klasse. Et designvalg kan være å bygge en Variabel
klasse som bare inneholder skalarvariabler og deretter legge til en ArrayVariable
underklasse for å håndtere kompleksiteten i arrays. Jeg valgte å kombinere dem, og forvandle skalarvariabler i hovedsak til matriser med lengde 1.
Hvis du leser koden ovenfor, vil du se matriseindekser og multiplikatorer. Dette er her fordi flerdimensjonale matriser i BASIC er implementert ved hjelp av en enkelt lineær Java-matrise. Den lineære indeksen i Java-arrayet beregnes manuelt ved hjelp av elementene i multiplikator-arrayet. Indeksene som brukes i BASIC-programmet blir sjekket for gyldighet ved å sammenligne dem med den maksimale lovlige indeksen i indeksenes ndx array.
For eksempel vil en BASIC-matrise med tre dimensjoner på 10, 10 og 8 ha verdiene 10, 10 og 8 lagret i ndx. Dette gjør at uttrykksevaluereren kan teste for en "indeks utenfor grensene" -tilstand ved å sammenligne antallet som brukes i BASIC-programmet med det maksimale lovlige antallet som nå er lagret i ndx. Multiplikatorarrayet i vårt eksempel vil inneholde verdiene 1, 10 og 100. Disse konstantene representerer tallene man bruker for å kartlegge fra en flerdimensjonal arrayindeksspesifikasjon til en lineær arrayindeksspesifikasjon. Den faktiske ligningen er:
Java Index = Index1 + Index2 * Maks størrelse på Index1 + Index3 * (MaxSize of Index1 * MaxSizeIndex 2)Neste Java-array i Variabel
klasse er vist nedenfor.
Uttrykk expns [];
De expns array brukes til å håndtere matriser som er skrevet som "A (10 * B, i)
. "I så fall er indeksene faktisk uttrykk i stedet for konstanter, så referansen må inneholde pekere til de uttrykkene som evalueres i løpetid. Endelig er det dette ganske styggt kodestykket som beregner indeksen avhengig av hva ble sendt i programmet. Denne private metoden er vist nedenfor.
private int computeIndex (int ii []) kaster BASICRuntimeError {int offset = 0; if ((ndx == null) || (ii.length! = ndx.length)) throw new BASICRuntimeError ("Feil antall indekser."); for (int i = 0; i <ndx.length; i ++) {if ((ii [i] ndx [i])) kast ny BASICRuntimeError ("Indeks utenfor rekkevidde."); forskyvning = forskyvning + (ii [i] -1) * mult [i]; } return offset; }
Når du ser på koden ovenfor, vil du merke at koden først sjekker for å se at riktig antall indekser ble brukt når det henvises til matrisen, og deretter at hver indeks var innenfor det lovlige området for den indeksen. Hvis det oppdages en feil, kastes et unntak til tolken. Metodene numValue
og strengverdi
returnere en verdi fra variabelen som henholdsvis et tall eller en streng. Disse to metodene 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)]; }
Det er flere metoder for å sette verdien til en variabel som ikke vises her.
Ved å skjule mye av kompleksiteten i hvordan hvert stykke implementeres, når det endelig er på tide å utføre BASIC-programmet, er Java-koden ganske grei.
Kjører koden
Koden for å tolke de grunnleggende uttalelsene og utføre dem er inneholdt i
løpe
metoden for
Program
klasse. Koden for denne metoden er vist nedenfor, og jeg går gjennom den for å påpeke de interessante delene.
1 offentlig tomkjøring (InputStream inn, OutputStream ut) kaster BASICRuntimeError {2 PrintStream pout; 3 Oppregning e = stmts.elements (); 4 stmtStack = ny stabel (); // antar ingen stablede utsagn ... 5 dataStore = new Vector (); // ... og ingen data å lese. 6 dataPtr = 0; 7 Erklæring s; 8 9 vars = nye RedBlackTree (); 10 11 // hvis programmet ennå ikke er gyldig. 12 hvis (! E.hasMoreElements ()) 13 returnerer; 14 15 if (out instanceof PrintStream) {16 pout = (PrintStream) out; 17} annet {18 pout = ny PrintStream (ut); 19}
Ovennevnte kode viser at løpe
metoden tar en InputStream
og en OutputStream
for bruk som "konsoll" for kjøringsprogrammet. I linje 3, oppregningsobjektet e er satt til settet med uttalelser fra samlingen som heter stmts. For denne samlingen brukte jeg en variant på et binært søketre kalt et "rød-svart" tre. (For mer informasjon om binære søketrær, se min forrige kolonne om å bygge generiske samlinger.) Deretter opprettes to ekstra samlinger - en ved hjelp av en Stable
og en som bruker en Vector
. Stakken brukes som stakken i hvilken som helst datamaskin, men vektoren brukes uttrykkelig for DATA-setningene i BASIC-programmet. Den endelige samlingen er et annet rød-svart tre som inneholder referanser for variablene definert av BASIC-programmet. Dette treet er symboltabellen som brukes av programmet mens det kjøres.
Etter initialiseringen blir inngangs- og utgangsstrømmene satt opp, og deretter hvis e er ikke null, begynner vi med å samle inn data som er erklært. Det gjøres som vist i følgende kode.
/ * Først laster vi alle dataoppgavene * / while (e.hasMoreElements ()) {s = (Statement) e.nextElement (); if (s.keyword == Statement.DATA) {s.execute (this, in, pout); }}
Ovennevnte sløyfe ser bare på alle utsagnene, og eventuelle DATA-utsagn den finner blir deretter utført. Utførelsen av hver DATA-setning setter inn verdiene som er erklært av denne uttalelsen i dataStore vektor. Deretter utfører vi riktig program, som gjøres ved hjelp av dette neste kodestykket:
e = stmts.elements (); s = (Statement) e.nextElement (); gjør {int yyy; / * Mens du kjører hopper vi over datauttalelser. * / prøv {yyy = in.available (); } fangst (IOException ez) {ååå = 0; } hvis (ååå! = 0) {pout.println ("Stoppet ved:" + s); skyve (r); gå i stykker; } if (s.keyword! = Statement.DATA) {if (traceState) {s.trace (this, (traceFile! = null)? traceFile: pout); } s = s. utfør (dette, i, pout); } annet s = neste Uttalelse (r); } mens (s! = null); }
Som du kan se i koden ovenfor, er det første trinnet å starte på nytt e. Neste trinn er å hente den første setningen i variabelen s og deretter for å gå inn i kjøringsløyfen. Det er noe kode å sjekke for ventende inngang i inngangsstrømmen for å tillate at progresjonen til programmet blir avbrutt ved å skrive på programmet, og deretter kontrollerer sløyfen om utsagnet som skal utføres, vil være en DATA-setning. Hvis det er det, hopper løkken utsagnet slik det allerede var utført. Den ganske innviklede teknikken for å utføre alle datasettene først er nødvendig fordi BASIC tillater at DATA-setningene som tilfredsstiller en LES-setning, vises hvor som helst i kildekoden. Til slutt, hvis sporing er aktivert, skrives det ut en sporingspost og den veldig lite imponerende uttalelsen s = s. utfør (dette, i, pout);
påberopes. Skjønnheten er at all innsatsen med å kapsle grunnkonseptene inn i lettfattelige klasser gjør den endelige koden triviell. Hvis det ikke er trivielt, har du kanskje en anelse om at det kan være en annen måte å dele designet ditt på.
Innpakning og videre tanker
Tolken ble designet slik at den kunne kjøre som en tråd, og dermed kan det være flere COCOA-tolketråder som kjører samtidig i programområdet ditt samtidig. Videre kan vi ved bruk av funksjonsutvidelse gi et middel der disse trådene kan samhandle med hverandre. Det var et program for Apple II og senere for PC og Unix kalt C-roboter som var et system av samspillende "robotiske" enheter som ble programmert ved hjelp av et enkelt BASIC-derivat språk. Spillet ga meg og andre mange timers underholdning, men var også en utmerket måte å introdusere de grunnleggende prinsippene for beregning for yngre studenter (som feilaktig trodde de bare spilte og ikke lærte). Java-baserte tolkedelsystemer er mye kraftigere enn deres kolleger før Java, fordi de er umiddelbart tilgjengelige på hvilken som helst Java-plattform. COCOA kjørte på Unix-systemer og Macintoshes samme dag som jeg jobbet på en Windows 95-basert PC. Mens Java blir slått opp av inkompatibiliteter i implementeringen av verktøy eller verktøysett i vinduet, blir det ofte oversett dette: Mye kode "fungerer bare."