Ohjelmointi

Rakenna omia kieliä JavaCC: llä

Mietitkö koskaan kuinka Java-kääntäjä toimii? Täytyykö sinun kirjoittaa jäsentimiä merkintäasiakirjoille, jotka eivät tilaa vakiomuotoja, kuten HTML tai XML? Vai haluatko toteuttaa oman pienen ohjelmointikielesi vain sen helvetin vuoksi? JavaCC antaa sinun tehdä kaikki tämä Javalla. Joten riippumatta siitä, haluatko vain oppia lisää kääntäjien ja tulkkien toiminnasta tai onko sinulla konkreettisia tavoitteita luoda Java-ohjelmointikielen seuraaja, ole hyvä ja liity kanssani tämän kuukauden tutkimustyöhön JavaCC, korostettu rakentamalla kätevä pieni komentorivilaskin.

Kääntäjän rakentamisen perusteet

Ohjelmointikielet jaetaan usein keinotekoisesti käännettyihin ja tulkittuihin kieliin, vaikka rajat ovat hämärtyneet. Sellaisena älä huoli siitä. Tässä käsitellyt käsitteet soveltuvat yhtä hyvin sekä käännettyihin että tulkittuihin kieliin. Käytämme sanaa kääntäjä alla, mutta tämän artikkelin soveltamisalaan siinä on sisällytettävä tulkki.

Kääntäjien on suoritettava kolme päätehtävää, kun ne esitetään ohjelmatekstin (lähdekoodin) kanssa:

  1. Leksikaalinen analyysi
  2. Syntaktinen analyysi
  3. Koodin luominen tai suorittaminen

Suurin osa kääntäjän työstä keskittyy vaiheiden 1 ja 2 ympärille, joihin sisältyy ohjelman lähdekoodin ymmärtäminen ja sen syntaktisen oikeellisuuden varmistaminen. Kutsumme tätä prosessia jäsentäminen, kumpi on jäsentäjä 'vastuu.

Leksikaalinen analyysi (lexing)

Leksikaalinen analyysi tarkastelee ohjelman lähdekoodia pinnallisesti ja jakaa sen oikeaan rahakkeet. Tunnus on merkittävä osa ohjelman lähdekoodia. Tunnusesimerkkejä ovat avainsanat, välimerkit, kirjaimet, kuten numerot, ja merkkijonot. Nontokenit sisältävät tyhjän tilan, jota usein ei oteta huomioon, mutta jota käytetään tunnusten erottamiseen, ja kommentit.

Syntaktinen analyysi (jäsentäminen)

Syntaktisen analyysin aikana jäsennin poimii merkityksen ohjelman lähdekoodista varmistamalla ohjelman syntaktisen oikeellisuuden ja rakentamalla ohjelman sisäinen esitys.

Tietokonekieliteoria puhuu ohjelmat,kielioppi, ja Kieli (kielet. Tässä mielessä ohjelma on merkkijono. Kirjaimellinen on tietokonekielen peruselementti, jota ei voida pienentää edelleen. Kielioppi määrittelee säännöt syntaktisesti oikeiden ohjelmien rakentamiseksi. Vain ohjelmat, jotka pelaavat kieliopissa määriteltyjen sääntöjen mukaan, ovat oikeita. Kieli on yksinkertaisesti kaikkien ohjelmien joukko, joka täyttää kaikki kieliopisi säännöt.

Syntaktisen analyysin aikana kääntäjä tutkii ohjelman lähdekoodin kielen kieliopissa määriteltyjen sääntöjen suhteen. Jos jotakin kielioppisääntöä rikotaan, kääntäjä näyttää virheilmoituksen. Matkan varrella, kääntäessään ohjelmaa, kääntäjä luo helposti käsiteltävän sisäisen esityksen tietokoneohjelmasta.

Tietokonekielen kielioppisäännöt voidaan määrittää yksiselitteisesti ja kokonaisuudessaan EBNF (Extended Backus-Naur-Form) -merkinnällä (lisätietoja EBNF: stä, katso Resurssit). EBNF määrittelee kieliopit tuotantosääntöjen perusteella. Tuotantosäännössä todetaan, että kielioppi-elementti - joko literaaleja tai koostettuja elementtejä - voi koostua muista kielioppielementeistä. Literaalit, joita ei voida lukea, ovat staattisen ohjelmatekstin avainsanoja tai fragmentteja, kuten välimerkkejä. Komponentit johdetaan soveltamalla tuotantosääntöjä. Tuotantosäännöillä on seuraava yleinen muoto:

GRAMMAR_ELEMENT: = luettelo kielioppielementeistä | vaihtoehtoinen luettelo kieliopin elementeistä 

Tarkastellaan esimerkiksi pienen kielen kielioppisääntöjä, jotka kuvaavat aritmeettisia peruslausekkeita:

lauseke: = numero | lauseke '+' lauseke | expr '-' expr | lauseke '*' lauseke | expr '/' expr | '(' lauseke ')' | - lauseke: = numero + ('.' numero +)? numero: = '0' | "1" | '2' | '3' | '4' | '5' | "6" | '7' | "8" | '9' 

Kolme tuotantosääntöä määrittelee kielioppielementit:

  • lauseke
  • määrä
  • numero

Kieliopin määrittelemä kieli antaa meille mahdollisuuden määrittää aritmeettiset lausekkeet. An lauseke on joko numero tai yksi neljästä infix-operaattorista, joita sovelletaan kahteen lausekes, an lauseke sulkeissa tai negatiivinen lauseke. A määrä on liukuluku, jonka desimaaliosa on valinnainen. Määritämme a numero olla yksi tutuista desimaaliluvuista.

Koodin luominen tai suorittaminen

Kun jäsentäjä jäsentää ohjelman virheettömästi, se on sisäisessä esityksessä, jonka kääntäjä on helppo käsitellä. Nyt on suhteellisen helppoa luoda konekoodi (tai Java-tavukoodi tälle asialle) sisäisestä esityksestä tai suorittaa sisäinen esitys suoraan. Jos teemme edellisen, koomme; jälkimmäisessä tapauksessa puhumme tulkkauksesta.

JavaCC

JavaCC, saatavana ilmaiseksi, on jäsenningeneraattori. Se tarjoaa Java-kielen laajennuksen ohjelmointikielen kieliopin määrittämiseksi. JavaCC kehitti alun perin Sun Microsystems, mutta nyt sitä ylläpitää MetaMata. Kuten kaikki kunnolliset ohjelmointityökalut, JavaCC oli tosiasiallisesti käytetty määrittämään kielioppi JavaCC syöttömuoto.

Lisäksi, JavaCC antaa meille mahdollisuuden määritellä kieliopit samalla tavalla kuin EBNF, mikä helpottaa EBNF-kieliopien kääntämistä JavaCC muoto. Edelleen, JavaCC on Java: n suosituin jäsenningeneraattori, jossa on joukko ennalta määritettyjä JavaCC kieliopit käytettävissä käytettäväksi lähtökohtana.

Yksinkertaisen laskimen kehittäminen

Käymme nyt läpi pienen aritmeettisen kielemme rakentaaksemme yksinkertaisen komentorivilaskurin Java-sovelluksessa JavaCC. Ensin meidän on käännettävä EBNF-kielioppi JavaCC muodossa ja tallenna se tiedostoon Aritmeettinen.jj:

vaihtoehdot {LOOKAHEAD = 2; } PARSER_BEGIN (aritmeettinen) julkisen luokan aritmeettinen {} PARSER_END (aritmeettinen) ohitus: "\ t" TOKEN: double expr (): {} term () ("+" expr () double term (): {} "/" termi ()) * double unary (): {} "-" element () double element (): {} "(" lauseke () ")" 

Yllä olevan koodin pitäisi antaa sinulle käsitys siitä, miten kielioppi määritetään JavaCC. vaihtoehtoja yläosassa oleva osio määrittää joukon vaihtoehtoja kyseiselle kieliopille. Määritämme etsinnän 2. Lisäasetusten hallinta JavaCCvirheenkorjausominaisuudet ja paljon muuta. Nämä vaihtoehdot voidaan vaihtoehtoisesti määrittää JavaCC komentorivi.

PARSER_BEGIN lauseke määrittää, että jäsenninluokan määritelmä noudattaa. JavaCC luo yhden Java-luokan kullekin jäsentäjälle. Kutsumme jäsentäjäluokkaa Aritmeettinen. Toistaiseksi vaaditaan vain tyhjä luokan määritelmä; JavaCC lisää jäsentämiseen liittyvät ilmoitukset siihen myöhemmin. Lopetamme luokan määritelmän PARSER_END lauseke.

OHITA -osassa yksilöidään merkit, jotka haluamme ohittaa. Meidän tapauksessamme nämä ovat välilyöntihahmoja. Seuraavaksi määritämme kielemme tunnukset TOKEN -osiossa. Määritämme numerot ja numerot rahakkeiksi. Ota huomioon, että JavaCC erottaa tunnusten määritelmät ja muiden tuotantosääntöjen määritelmät, mikä eroaa EBNF: stä. OHITA ja TOKEN osiot määrittelevät tämän kieliopin leksikaalisen analyysin.

Seuraavaksi määritämme tuotantosäännön lauseke, ylätason kielioppi-elementti. Huomaa, kuinka määritelmä eroaa merkittävästi määritelmästä lauseke EBNF: ssä. Mitä tapahtuu? On käynyt ilmi, että yllä oleva EBNF-määritelmä on epäselvä, koska se sallii saman ohjelman useita esityksiä. Tarkastellaan esimerkiksi lauseketta 1+2*3. Voimme sovittaa 1+2 osaksi lauseke tuottaa lauseke * 3, kuten kuvassa 1.

Tai vaihtoehtoisesti voimme ensin ottelu 2*3 osaksi lauseke johtaen 1 + lauseke, kuten kuvassa 2 on esitetty.

Kanssa JavaCC, meidän on määriteltävä kielioppisäännöt yksiselitteisesti. Tämän seurauksena eritellään määritelmä lauseke kolmeen tuotantosääntöön, jotka määrittelevät kielioppielementit lauseke, termi, unaryja elementti. Nyt ilmaisu 1+2*3 on jäsennelty kuvan 3 mukaisesti.

Komentoriviltä voimme suorittaa JavaCC tarkistaa kielioppi:

javacc Arithmetic.jj Java-kääntäjän kääntäjän versio 1.1 (jäsentimen luonti) Tekijänoikeudet (c) 1996-1999 Sun Microsystems, Inc. Tekijänoikeudet (c) 1997-1999 Metamata, Inc. (kirjoita "javacc" ilman argumentteja apua varten) Lukeminen tiedostosta Aritmeettinen.jj. . . Varoitus: Hakusenttien riittävyyden tarkistusta ei suoriteta, koska vaihtoehto LOOKAHEAD on yli 1. Aseta vaihtoehdon FORCE_LA_CHECK arvoksi true to force. Parseri luotu 0 virheellä ja yhdellä varoituksella. 

Seuraava tarkistaa kieliopin määrittelyn ongelmien varalta ja luo joukon Java-lähdetiedostoja:

TokenMgrError.java ParseException.java Token.java ASCII_CharStream.java Arithmetic.java ArithmeticConstants.java ArithmeticTokenManager.java 

Yhdessä nämä tiedostot toteuttavat jäsentimen Java-sovelluksessa. Voit kutsua tämän jäsentimen alustamalla Aritmeettinen luokka:

public class Arithmetic toteuttaa ArithmeticConstants {public Arithmetic (java.io.InputStream stream) {...} public Arithmetic (java.io.Reader stream) {...} public Arithmetic (ArithmeticTokenManager tm) {...} staattinen lopullinen julkinen double expr () heittää ParseException {...} staattinen lopullinen julkinen double term () heittää ParseException {...} staattinen final public double unary () heittää ParseException {...} staattinen final public double element () heittää ParseException {. ..} static public void ReInit (java.io.InputStream stream) {...} static public void ReInit (java.io.Reader stream) {...} public void ReInit (ArithmeticTokenManager tm) {...} staattinen final public token getNextToken () {...} static final public token getToken (int index) {...} static final public ParseException generParseException () {...} static final public void enable_tracing () {...} static lopullinen julkinen void disable_tracing () {...}} 

Jos haluat käyttää tätä jäsentäjää, sinun on luotava ilmentymä käyttämällä yhtä konstruktoreista. Rakentajien avulla voit kulkea joko InputStream, a Lukijatai ArithmeticTokenManager ohjelman lähdekoodin lähteenä. Seuraavaksi määrität kielesi pääkielioppielementin, esimerkiksi:

Aritmeettinen jäsennin = uusi aritmeettinen (System.in); parser.expr (); 

Mitään ei kuitenkaan vielä tapahdu, koska Aritmeettinen.jj olemme määrittäneet vain kielioppisäännöt. Emme ole vielä lisänneet laskelmien suorittamiseen tarvittavaa koodia. Tätä varten lisäämme asianmukaiset toiminnot kielioppisääntöihin. Calcualtor.jj sisältää täydellisen laskimen, mukaan lukien toiminnot:

vaihtoehdot {LOOKAHEAD = 2; } PARSER_BEGIN (Laskin) public class Calculator {public static void main (String args []) heittää ParseException {Calculator parser = uusi laskin (System.in); while (true) {parser.parseOneLine (); }}} PARSER_END (Laskin) OHITUS: "\ t" TOKEN: void parseOneLine (): {double a; } {a = lauseke () {System.out.println (a); } | | {System.exit (-1); }} double expr (): {double a; kaksinkertainen b; } {a = termi () ("+" b = lauseke () {a + = b;} | "-" b = lauseke () {a - = b;}) * {palauta a; }} kaksoistermi (): {kaksinkertainen a; kaksinkertainen b; } {a = unary () ("*" b = termi () {a * = b;} | "/" b = termi () {a / = b;}) * {palauta a; }} kaksinkertainen unary (): {double a; } {"-" a = elementti () {return -a; } | a = elementti () {palauttaa a; }} kaksoiselementti (): {Tunnus t; kaksinkertainen a; } {t = {palauta Double.parseDouble (t.toString ()); } | "(" a = lauseke () ")" {palauta a; }} 

Päämenetelmä välittää ensin jäsenninobjektin, joka lukee vakiotulosta ja kutsuu sitten parseOneLine () loputtomassa silmukassa. Menetelmä parseOneLine () itse määritellään ylimääräisellä kielioppisäännöllä. Tämä sääntö yksinkertaisesti määrittelee, että odotamme jokaisen rivin lausekkeen itsestään, että on OK kirjoittaa tyhjiä rivejä ja että lopetamme ohjelman, kun pääsemme tiedoston loppuun.

Olemme muuttaneet alkuperäisten kielioppielementtien palautustyypin palatuksi kaksinkertainen. Suoritamme asianmukaiset laskelmat siellä, missä ne jäsennetään ja välitetään laskutulokset puhelupuuhun. Olemme myös muuttaneet kielioppielementtien määritelmiä tallentaaksemme tulokset paikallisiin muuttujiin. Esimerkiksi, a = elementti () jäsentää elementti ja tallentaa tuloksen muuttujaan a. Tämä antaa meille mahdollisuuden käyttää jäsennettyjen elementtien tuloksia oikeanpuoleisen toiminnan koodissa. Toiminnot ovat Java-koodilohkoja, jotka suoritetaan, kun siihen liittyvä kielioppisääntö on löytänyt vastaavuuden syötevirrasta.

Huomaa, kuinka vähän Java-koodia lisäsimme, jotta laskin olisi täysin toimiva. Lisäksi lisätoimintojen, kuten sisäänrakennettujen toimintojen tai jopa muuttujien, lisääminen on helppoa.

$config[zx-auto] not found$config[zx-overlay] not found