Ohjelmointi

Tulkin rakentaminen Java-sovellukseen, osa 1: PERUSASIAT

Kun kerroin ystävällesi, että olin kirjoittanut BASIC-tulkin Java-kielellä, hän nauroi niin kovasti, että melkein vuodatti soodaa, jota hänellä oli pitkin vaatteita. "Miksi maailmassa rakennat BASIC-tulkin Java-sovellukseen?" oli ennustettavissa oleva ensimmäinen kysymys suustaan. Vastaus on sekä yksinkertainen että monimutkainen. Yksinkertainen vastaus on, että oli hauskaa kirjoittaa tulkki Java-kielellä, ja jos aion kirjoittaa tulkin, voisin yhtä hyvin kirjoittaa sellaisen, josta minulla on hyviä muistoja henkilökohtaisen laskennan alkuajoista. Monimutkaisella puolella olen huomannut, että monet ihmiset, jotka käyttävät nykyään Java-ohjelmaa, ovat ohittaneet pudottavien Duke-sovelmien luomisen ja siirtyneet vakaviin sovelluksiin. Usein sovellusta rakennettaessa haluat, että se on konfiguroitavissa. Uudelleen konfiguroinnin valintamekanismi on jonkinlainen dynaaminen suoritusmoottori.

Dynaaminen suoritus on ominaisuus, joka tunnetaan makrokielinä tai määrityskielinä, jonka avulla käyttäjä voi "ohjelmoida" sovelluksen. Dynaamisen suoritusmoottorin etuna on, että työkalut ja sovellukset voidaan räätälöidä suorittamaan monimutkaisia ​​tehtäviä korvaamatta työkalua. Java-alusta tarjoaa laajan valikoiman dynaamisia suoritusmoottorivaihtoehtoja.

HotJava ja muut kuumia vaihtoehtoja

Tutkitaan lyhyesti joitain käytettävissä olevia dynaamisia suoritusmoottorivaihtoehtoja ja tarkastellaan sitten tulkkani toteutusta perusteellisesti. Dynaaminen suoritusmoottori on upotettu tulkki. Tulkki vaatii toimiakseen kolme palvelua:

  1. Keino ladata ohjeet
  2. Moduulimuoto suoritettavien ohjeiden tallentamiseen
  3. Malli tai ympäristö vuorovaikutuksessa isäntäohjelman kanssa

HotJava

Tunnetuimman sulautetun tulkin on oltava HotJava-sovelmaympäristö, joka on muuttanut täysin tapaa, jolla ihmiset katsovat verkkoselaimia.

HotJava-sovelmamalli perustui käsitykseen, että Java-sovellus voisi luoda yleisen perusluokan tunnetulla käyttöliittymällä ja ladata sitten dynaamisesti kyseisen luokan alaluokat ja suorittaa ne ajon aikana. Nämä sovelmat antoivat uusia ominaisuuksia ja perusluokan rajoissa dynaamisen suorituksen. Tämä dynaaminen suoritusominaisuus on olennainen osa Java-ympäristöä ja yksi niistä asioista, jotka tekevät siitä niin erikoisen. Tarkastelemme tätä ympäristöä perusteellisesti myöhemmässä sarakkeessa.

GNU EMACS

Ennen HotJavan saapumista kenties menestynein sovellus dynaamisella suorituksella oli GNU EMACS. Tämän toimittajan LISP-tyyppisestä makrokielestä on tullut peruselementti monille ohjelmoijille. Lyhyesti sanottuna EMACS LISP -ympäristö koostuu LISP-tulkista ja monista muokkaustyyppisistä toiminnoista, joita voidaan käyttää monimutkaisimpien makrojen muodostamiseen. Ei pidä pitää yllättävänä, että EMACS-editori on alun perin kirjoitettu makroille, jotka on suunniteltu TECO-nimiselle editorille. Siksi rikkaan (jos ei ole luettavissa) makrokielen saatavuus TECO: ssa mahdollisti täysin uuden editorin rakentamisen. Nykyään GNU EMACS on perusmuokkaaja, ja kokonaiset pelit on kirjoitettu muuhun kuin EMACS LISP -koodiin, joka tunnetaan nimellä el-code. Tämä määrityskyky on tehnyt GNU EMACS: sta tukitoiminnon, kun taas VT-100-päätelaitteista, joilla se on suunniteltu toimimaan, on tullut pelkkä alaviite kirjoittajan sarakkeessa.

REXX

Yksi suosikkikielistäni, joka ei koskaan saanut aikaan ansaittua roiskeita, oli REXX, jonka on suunnitellut Mike Cowlishaw IBM: stä. Yritys tarvitsi kielen hallitakseen sovelluksia suurissa keskusyksiköissä, joissa on VM-käyttöjärjestelmä. Löysin REXXin Amigasta, jossa se oli tiiviisti yhdistetty moniin erilaisiin sovelluksiin "REXX-porttien" kautta. Nämä portit mahdollistivat sovellusten ajamisen etäyhteyden kautta REXX-tulkin kautta. Tämä tulkin ja sovelluksen yhdistäminen loi paljon tehokkaamman järjestelmän kuin sen komponenttien kanssa oli mahdollista. Onneksi kieli elää NETREXX: ssä, jonka Mike kirjoitti ja joka käännettiin Java-koodiksi.

Tarkastellessani NETREXX -ohjelmaa ja paljon aikaisempaa kieltä (LISP Java-kielellä) hämmästytti minua, että nämä kielet muodostivat tärkeän osan Java-sovellustarinoista. Mikä olisi parempi tapa kertoa tämä tarinan osa kuin tehdä jotain hauskaa täällä - kuten herättää BASIC-80? Vielä tärkeämpää on, että olisi hyödyllistä näyttää yksi tapa, jolla komentosarjakielet voidaan kirjoittaa Java-muodossa, ja niiden integroinnin avulla Java-ohjelmaan näyttää, kuinka ne voivat parantaa Java-sovellusten ominaisuuksia.

PERUSVAATIMUKSET Java-sovellusten parantamiseen

BASIC on yksinkertaisesti peruskieli. On olemassa kaksi ajatuskoulua siitä, miten voisi kirjoittaa tulkin sille. Yksi tapa on kirjoittaa ohjelmointisilmukka, jossa tulkkiohjelma lukee yhden tekstirivin tulkitusta ohjelmasta, jäsentää sen ja kutsuu sen jälkeen aliohjelman. Lukemisen, jäsentämisen ja suorittamisen sarja toistetaan, kunnes yksi tulkitun ohjelman lauseista käskee tulkin lopettamaan.

Toinen ja paljon mielenkiintoisempi tapa puuttua projektiin on jäsentää kieli jäsennyspuuksi ja suorittaa sitten jäsentelypuu "paikalleen". Näin tunnusten tulkkaus toimii ja tapa, jolla päätin edetä. Tulkkien merkitseminen on myös nopeampaa, koska heidän ei tarvitse skannata syötettä uudelleen aina, kun he suorittavat lauseen.

Kuten edellä mainitsin, dynaamisen suorituksen saavuttamiseksi tarvittavat kolme komponenttia ovat tapa ladata, moduulimuoto ja suoritusympäristö.

Ensimmäisen komponentin, lataamisen keinon, käsittelee Java InputStream. Koska tulovirrat ovat perustavanlaatuisia Java I / O -arkkitehtuurissa, järjestelmä on suunniteltu lukemaan ohjelmassa InputStream ja muuntaa se suoritettavaan muotoon. Tämä on erittäin joustava tapa syöttää koodia järjestelmään. Tietysti tulovirran läpi kulkevan datan protokolla on BASIC-lähdekoodi. On tärkeää huomata, että mitä tahansa kieltä voidaan käyttää; älä tee virhettä ajattelemalla, että tätä tekniikkaa ei voida soveltaa sovellukseesi.

Kun tulkitun ohjelman lähdekoodi on syötetty järjestelmään, järjestelmä muuntaa lähdekoodin sisäiseksi esitykseksi. Päätin käyttää jäsentelypuuta tämän projektin sisäisenä esitysmuotona. Kun jäsennepuu on luotu, sitä voidaan manipuloida tai suorittaa.

Kolmas komponentti on suoritusympäristö. Kuten näemme, tämän komponentin vaatimukset ovat melko yksinkertaisia, mutta toteutuksessa on muutama mielenkiintoinen käänne.

Erittäin nopea BASIC-kiertue

Niille teistä, jotka eivät ehkä ole koskaan kuulleet BASICista, annan teille lyhyen välähdyksen kielestä, jotta voitte ymmärtää edessä olevat jäsentämis- ja toteutushaasteet. Lisätietoja BASICista suosittelen voimakkaasti tämän sarakkeen lopussa olevia resursseja.

BASIC on lyhenne sanoista Beginners All-purpose Symbolic Instructional Code, ja se kehitettiin Dartmouthin yliopistossa opettamaan laskentakonsepteja perustutkinto-opiskelijoille. Kehityksestään lähtien BASIC on kehittynyt erilaisiksi murteiksi. Yksinkertaisimpia näistä murteista käytetään teollisten prosessien ohjaimien ohjauskielinä; monimutkaisimmat murteet ovat rakenteellisia kieliä, jotka sisältävät joitain olio-ohjelmoinnin näkökohtia. Projektiin valitsin BASIC-80-nimisen murteen, joka oli suosittu CP / M-käyttöjärjestelmässä seitsemänkymmenen lopulla. Tämä murre on vain kohtalaisen monimutkaisempi kuin yksinkertaisimmat murteet.

Lausekkeen syntakse

Kaikki lauserivit ovat muodoltaan

[ : [ : ... ] ]

missä "Rivi" on lauseen rivinumero, "Avainsana" on PERUS lausekkeen avainsana ja "Parametrit" ovat joukko kyseiseen avainsanaan liittyviä parametreja.

Rivinumerolla on kaksi tarkoitusta: Se toimii tarrana lauseille, jotka ohjaavat suorituksen kulkua, kuten a mene lauseke, ja se toimii lajittelutagina ohjelmaan lisättyihin lauseisiin. Lajittelutagina rivin numero helpottaa rivin muokkausympäristöä, jossa muokkaus ja komentojen käsittely sekoitetaan yhdessä interaktiivisessa istunnossa. Muuten, tämä vaadittiin, kun sinulla oli vain teletyyppi. :-)

Vaikka rivit eivät olekaan kovin tyylikkäitä, ne antavat tulkkiympäristölle mahdollisuuden päivittää ohjelmaa yksi lause kerrallaan. Tämä kyky johtuu siitä, että lause on yksi jäsennetty kokonaisuus ja se voidaan linkittää tietorakenteeseen rivinumeroilla. Ilman rivinumeroita on usein tarpeen jäsentää koko ohjelma, kun rivi muuttuu.

Avainsana identifioi BASIC-käskyn. Esimerkissä tulkkimme tukee hieman laajennettua BASIC-avainsanojen joukkoa, mukaan lukien mene, gosub, palata, Tulosta, jos, loppuun, tiedot, palauttaa, lukea, päällä, Rem, varten, Seuraava, päästää, tulo, lopettaa, himmeä, satunnaistaa, tronja troff. Emme selvästikään käsittele kaikkia näitä tässä artikkelissa, mutta ensi kuun "Java In Depth" -osiossa on joitain asiakirjoja, joita voit tutkia.

Jokaisella avainsanalla on joukko laillisia avainsanaparametreja, jotka voivat seurata sitä. Esimerkiksi mene avainsanan on seurattava rivinumero, jos lauseen perässä on oltava ehdollinen lauseke sekä avainsana sitten -- ja niin edelleen. Parametrit ovat kullekin avainsanalle ominaisia. Käsittelen muutaman näistä parametriluetteloista yksityiskohtaisesti hieman myöhemmin.

Lausekkeet ja operaattorit

Usein lauseessa määritetty parametri on lauseke. Tässä käyttämäni BASIC-versio tukee kaikkia tavanomaisia ​​matemaattisia operaatioita, loogisia operaatioita, eksponentointia ja yksinkertaista toimintokirjastoa. Lausekieliopin tärkein osa on kyky kutsua toimintoja. Lausekkeet ovat itsessään melko vakiomaisia ​​ja samanlaisia ​​kuin edellisessä StreamTokenizer -sarakkeessani jäsennetyt.

Muuttujat ja tietotyypit

Osa syystä BASIC on niin yksinkertainen kieli, koska sillä on vain kaksi tietotyyppiä: numerot ja merkkijonot. Jotkut komentosarjakielet, kuten REXX ja PERL, eivät edes tee tätä eroa tietotyyppien välillä, ennen kuin niitä käytetään. Mutta BASIC: n kanssa tietotyyppien tunnistamiseen käytetään yksinkertaista syntaksia.

Muuttujien nimet tässä BASIC-versiossa ovat kirjainmerkkijonoja, jotka alkavat aina kirjaimella. Muuttujat eivät erottele isoja ja pieniä kirjaimia. Täten A, B, FOO ja FOO2 ovat kaikki kelvollisia muuttujien nimiä. Lisäksi BASICissa muuttuja FOOBAR vastaa FooBaria. Merkkijonojen tunnistamiseksi muuttujan nimeen lisätään dollarin merkki ($); siis muuttuja FOO $ on muuttuja, joka sisältää merkkijonon.

Lopuksi tämä kieliversio tukee taulukoita, joissa käytetään himmeä avainsana ja muuttuja syntaksin muodosta NAME (indeksi1, indeksi2, ...) enintään neljälle indeksille.

Ohjelman rakenne

BASIC-ohjelmat alkavat oletusarvoisesti pienimmällä numeroidulla rivillä ja jatkuvat, kunnes käsiteltäviä rivejä ei ole enää lopettaa tai loppuun avainsanat suoritetaan. Alla on esitetty hyvin yksinkertainen BASIC-ohjelma:

100 REM Tämä on luultavasti kanoninen BASIC-esimerkki 110 REM -ohjelma. Huomaa, että REM-lauseet jätetään huomiotta. 120 PRINT "Tämä on testiohjelma." 130 PRINT "Yhteenveto arvoista välillä 1 ja 100" 140 LET yhteensä = 0 150 FOR I = 1-100 160 LET yhteensä = yhteensä + i 170 SEURAAVA I 180 PRINT "Kaikkien numeroiden 1 ja 100 välinen kokonaismäärä on" yhteensä 190 LOPPU 

Yllä olevat viivanumerot osoittavat lauseiden leksikaalisen järjestyksen. Kun ne suoritetaan, rivit 120 ja 130 tulostavat viestit ulostuloon, rivi 140 alustaa muuttujan, ja rivillä 150 - 170 oleva silmukka päivittää kyseisen muuttujan arvon. Lopuksi tulokset tulostetaan. Kuten näette, BASIC on hyvin yksinkertainen ohjelmointikieli ja siksi ihanteellinen ehdokas laskentakonseptien opettamiseen.

Lähestymistavan organisointi

Komentosarjakielille tyypillisesti BASIC sisältää ohjelman, joka koostuu monista lausekkeista, jotka suoritetaan tietyssä ympäristössä. Suunnitteluhaaste on siis rakentaa objektit tällaisen järjestelmän toteuttamiseksi hyödyllisellä tavalla.

Kun tarkastelin ongelmaa, suoraviivainen tietorakenne hyppäsi minulle melkoisesti. Tämä rakenne on seuraava:

Komentosarjakielen julkisen käyttöliittymän on sisällettävä

  • Tehdasmenetelmä, joka ottaa lähdekoodin syötteeksi ja palauttaa ohjelman edustavan objektin.
  • Ympäristö, joka tarjoaa kehyksen, jossa ohjelma suoritetaan, mukaan lukien "I / O" -laitteet tekstinsyöttöä ja tekstin ulostuloa varten.
  • Tavallinen tapa muokata kyseistä objektia, kenties käyttöliittymän muodossa, jonka avulla ohjelma ja ympäristö voidaan yhdistää hyödyllisten tulosten saavuttamiseksi.

Sisäisesti tulkin rakenne oli hieman monimutkaisempi. Kysymys oli siitä, miten voidaan ottaa huomioon komentosarjakielen kaksi puolta, jäsentäminen ja toteutus? Tuloksena oli kolme luokkaryhmää - yksi jäsentämistä varten, yksi jäsennettyjen ja suoritettavien ohjelmien edustamisen rakennekehykselle ja yksi, joka muodosti suorituksen perusympäristön luokan.

Jäsennysryhmässä vaaditaan seuraavat objektit:

  • Leksikaalinen analyysi koodin käsittelemiseksi tekstinä
  • Lausekkeiden jäsentely, jäsennellä lausekkeiden jäsentelypuita
  • Lausekkeen jäsentäminen, jotta itse lausekkeista voidaan jäsentää puita
  • Virheluokat raportointivirheiden ilmoittamiseksi

Runkoryhmä koostuu objekteista, jotka sisältävät jäsentelypuita ja muuttujia. Nämä sisältävät:

  • Lauseobjekti, jolla on monia erikoistuneita alaluokkia edustamaan jäsennettyjä lauseita
  • Lausekappale, joka edustaa lausekkeita arviointia varten
  • Muuttujaobjekti, jolla on monia erikoistuneita alaluokkia edustamaan atomien datan esiintymiä