Programiranje

Sestavite svoje jezike z JavaCC

Se kdaj vprašate, kako deluje prevajalnik Java? Ali morate napisati razčlenjevalnike za označevalne dokumente, ki niso naročeni na standardne formate, kot sta HTML ali XML? Ali pa želite uporabiti svoj mali programski jezik, samo za vraga? JavaCC vam omogoča, da vse to naredite v Javi. Torej, če vas zanima le več o tem, kako delujejo prevajalniki in tolmači, ali pa imate konkretne ambicije ustvariti naslednika programskega jezika Java, se mi pridružite v mesečevem prizadevanju za raziskovanje JavaCC, ki ga poudarja priročen majhen kalkulator ukazne vrstice.

Osnove izdelave prevajalnika

Programski jeziki so pogosto nekoliko umetno razdeljeni na prevedene in interpretirane jezike, čeprav so meje postale zamegljene. Ne skrbite zaradi tega. Tu obravnavani koncepti se enako dobro nanašajo na prevedene in interpretirane jezike. Uporabili bomo besedo prevajalnik spodaj, vendar za področje uporabe tega člena to vključuje pomen tolmač.

Prevajalniki morajo opraviti tri glavne naloge, če jim je predstavljeno programsko besedilo (izvorna koda):

  1. Leksikalna analiza
  2. Sintaksična analiza
  3. Generiranje ali izvajanje kode

Glavnina dela prevajalnika se osredotoča na koraka 1 in 2, ki vključuje razumevanje izvorne kode programa in zagotavljanje njegove skladenjske pravilnosti. Temu pravimo postopek razčlenjevanje, ki je razčlenjevalnik 's odgovornostjo.

Leksikalna analiza (lexing)

Leksikalna analiza bežno pogleda izvorno kodo programa in jo razdeli na pravilno žetoni. Žeton je pomemben del izvorne kode programa. Primeri žetonov vključujejo ključne besede, ločila, dobesedna besedila, kot so številke in nizi. Nontokens vključujejo presledek, ki je pogosto prezrt, vendar se uporablja za ločevanje žetonov in komentarjev.

Sintaktična analiza (razčlenitev)

Med sintaksično analizo razčlenjevalnik izvleče pomen iz izvorne kode programa tako, da zagotavlja skladenjsko pravilnost programa in z izgradnjo notranje predstavitve programa.

Teorija računalniškega jezika govori o programi,slovnica, in jezikov. V tem smislu je program zaporedje žetonov. Dobesedno besedilo je osnovni element računalniškega jezika, ki ga ni mogoče dodatno zmanjšati. Slovnica opredeljuje pravila za gradnjo skladenjsko pravilnih programov. Pravilni so samo programi, ki se igrajo po pravilih, opredeljenih v slovnici. Jezik je preprosto nabor vseh programov, ki ustrezajo vsem vašim slovničnim pravilom.

Med sintaksično analizo prevajalnik preuči izvorno kodo programa glede na pravila, določena v slovnici jezika. Če je katero koli slovnično pravilo kršeno, prevajalnik prikaže sporočilo o napaki. Med preučevanjem programa med preučevanjem programa ustvari enostavno obdelano notranjo predstavitev računalniškega programa.

Slovnična pravila računalniškega jezika je mogoče nedvoumno in v celoti določiti z zapisom EBNF (Extended Backus-Naur-Form) (več o EBNF glejte Viri). EBNF slovnice opredeljuje v smislu proizvodnih pravil. Pravilo izdelave določa, da lahko slovnični element - bodisi dobesedni bodisi sestavljeni elementi - sestoji iz drugih slovničnih elementov. Nezmanjšane dobesedne besede so ključne besede ali fragmenti statičnega besedila programa, na primer ločila. Sestavljeni elementi so pridobljeni z uporabo proizvodnih pravil. Pravila izdelave imajo naslednjo splošno obliko:

GRAMMAR_ELEMENT: = seznam slovničnih elementov | nadomestni seznam slovničnih elementov 

Kot primer si oglejmo slovnična pravila za majhen jezik, ki opisuje osnovne aritmetične izraze:

izraz: = število | izraz '+' izraz | izraz '-' izraz | izraz '*' izraz | izraz '/' izraz | '(' izraz ')' | - izraz številka: = številka + ('.' številka +)? številka: = '0' | "1" | "2" | "3" | "4" | "5" | '6' | '7' | '8' | '9' 

Tri slovnična pravila opredeljujejo slovnične elemente:

  • ekspr
  • številko
  • številka

Jezik, ki ga določa ta slovnica, nam omogoča, da določimo aritmetične izraze. An ekspr je številka ali eden od štirih operatorjev infiks, ki se uporabljajo za dva eksprs, an ekspr v oklepaju ali negativno ekspr. A številko je število s plavajočo vejico z neobveznim decimalnim ulomkom. Določimo a številka biti ena izmed znanih decimalnih številk.

Generiranje ali izvajanje kode

Ko razčlenjevalnik uspešno razčleni program brez napak, obstaja v notranji predstavitvi, ki jo prevajalnik enostavno obdela. Zdaj je razmeroma enostavno ustvariti strojno kodo (ali bajtno kodo Java) iz notranje predstavitve ali pa jo izvršiti neposredno. Če naredimo prvo, sestavljamo; v zadnjem primeru govorimo o tolmačenju.

JavaCC

JavaCC, ki je na voljo brezplačno, je generator razčlenjevalnika. Ponuja razširitev jezika Java za določanje slovnice programskega jezika. JavaCC je prvotno razvil Sun Microsystems, zdaj pa ga vzdržuje MetaMata. Kot vsako spodobno programsko orodje JavaCC je bil dejansko uporabljen za določanje slovnice JavaCC vhodna oblika.

Poleg tega JavaCC nam omogoča, da slovnice definiramo na način, podoben EBNF, kar olajša prevajanje slovnic EBNF v JavaCC format. Nadalje, JavaCC je najbolj priljubljen generator razčlenjevalnikov za Javo s številnimi vnaprej določenimi JavaCC slovnice, ki so na voljo za uporabo kot izhodišče.

Razvoj preprostega kalkulatorja

Zdaj smo znova obiskali svoj mali aritmetični jezik, da bi v Javi ustvarili preprost kalkulator ukazne vrstice JavaCC. Najprej moramo prevesti slovnico EBNF JavaCC format in ga shranite v datoteko Aritmetika.jj:

možnosti {LOOKAHEAD = 2; } PARSER_BEGIN (Arithmetic) javni razred Arithmetic {} PARSER_END (Arithmetic) SKIP: "\ t" TOKEN: double expr (): {} term () ("+" expr () double term (): {} "/" term ()) * dvojni unary (): {} "-" element () dvojni element (): {} "(" expr () ")" 

Zgornja koda bi vam morala predstavljati, kako določiti slovnico JavaCC. The opcije odsek na vrhu določa nabor možnosti za to slovnico. Določimo lookahead 2. Nadzor dodatnih možnosti JavaCCfunkcije za odpravljanje napak in še več. Te možnosti je mogoče podati na JavaCC ukazna vrstica.

The PARSER_BEGIN stavek določa, da sledi definicija razreda razčlenjevalnika. JavaCC ustvari en razred Java za vsak razčlenjevalnik. Kličemo razred razčlenjevalnika Aritmetika. Za zdaj potrebujemo le prazno definicijo razreda; JavaCC mu bo pozneje dodal izjave, povezane z razčlenjevanjem. Definicijo razreda končamo z PARSER_END klavzulo.

The SKIP poglavje določa znake, ki jih želimo preskočiti. V našem primeru so to presledki. Nato opredelimo žetone našega jezika v ŽETON odsek. Števila in številke definiramo kot žetone. Upoštevajte to JavaCC razlikuje med definicijami za žetone in definicijami za druga proizvodna pravila, kar se razlikuje od EBNF. The SKIP in ŽETON oddelki določajo leksikalno analizo te slovnice.

Nato določimo proizvodno pravilo za ekspr, slovnični element najvišje ravni. Upoštevajte, kako se ta opredelitev izrazito razlikuje od definicije ekspr v EBNF. Kaj se dogaja? No, izkazalo se je, da je zgornja definicija EBNF dvoumna, saj omogoča več predstavitev istega programa. Preglejmo na primer izraz 1+2*3. Lahko se ujemamo 1+2 v ekspr Mehek izraz * 3, kot je na sliki 1.

Ali pa bi se lahko najprej ujemali 2*3 v ekspr kaže v 1 + izraz, kot je prikazano na sliki 2.

S JavaCC, slovnična pravila moramo določiti nedvoumno. Kot rezultat smo izločili definicijo ekspr v tri produkcijska pravila, ki opredeljujejo slovnične elemente ekspr, izraz, unary, in element. Zdaj pa izraz 1+2*3 je razčlenjen, kot je prikazano na sliki 3.

Iz ukazne vrstice lahko zaženemo JavaCC za preverjanje naše slovnice:

javacc Arithmetic.jj Java Compiler Compiler različica 1.1 (generator razčlenjevalnika) Copyright (c) 1996-1999 Sun Microsystems, Inc. Copyright (c) 1997-1999 Metamata, Inc. (vnesite "javacc" brez argumentov za pomoč) Branje iz datoteke Aritmetika.jj. . . Opozorilo: Preverjanje ustreznosti Lookahead se ne izvaja, ker je možnost LOOKAHEAD več kot 1. Možnost FORCE_LA_CHECK nastavite na true za prisilno preverjanje. Razčlenjevalnik ustvarjen z 0 napakami in 1 opozorilom. 

Naslednje preverja našo slovnično definicijo za težave in generira nabor izvornih datotek Java:

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

Te datoteke skupaj izvajajo razčlenjevalnik v Javi. Ta razčlenjevalnik lahko prikličete tako, da ustvarite primerek datoteke Aritmetika razred:

javni razred Arithmetic izvaja ArithmeticConstants {public Arithmetic (java.io.InputStream stream) {...} public Arithmetic (java.io.Reader stream) {...} public Arithmetic (ArithmeticTokenManager tm) {...} static final public double expr () vrže ParseException {...} static final public double term () vrže ParseException {...} static final public double unary () vrže ParseException {...} static final public double element () vrže ParseException {. ..} statična javna praznina ReInit (tok java.io.InputStream) {...} statična javna praznina ReInit (tok java.io.Reader) {...} javna praznina ReInit (ArithmeticTokenManager tm) {...} statična final public Token getNextToken () {...} static final public Token getToken (int index) {...} static final public ParseException generirajParseException () {...} static final void enable_tracing () {...} static končna javna praznina disable_tracing () {...}} 

Če ste želeli uporabiti ta razčlenjevalnik, morate z enim konstruktorjem ustvariti primerek. Konstruktorji vam omogočajo, da vnesete v InputStream, a Bralecali ArithmeticTokenManager kot vir izvorne kode programa. Nato določite glavni slovnični element svojega jezika, na primer:

Aritmetični razčlenjevalnik = nova aritmetika (System.in); razčlenjevalnik.expr (); 

Vendar se še veliko ne zgodi, ker v Aritmetika.jj definirali smo le slovnična pravila. Še nismo dodali kode, potrebne za izvajanje izračunov. Za to slovničnim pravilom dodamo ustrezna dejanja. Calcualtor.jj vsebuje celoten kalkulator, vključno z dejanji:

možnosti {LOOKAHEAD = 2; } PARSER_BEGIN (Kalkulator) kalkulator javnega razreda {javna statična void main (String args []) vrže ParseException {Kalkulator parser = nov kalkulator (System.in); while (true) {parser.parseOneLine (); }}} PARSER_END (kalkulator) SKIP: "\ t" TOKEN: void parseOneLine (): {double a; } {a = expr () {System.out.println (a); } | | {System.exit (-1); }} dvojni izraz (): {dvojni a; dvojni b; } {a = izraz () ("+" b = izraz () {a + = b;} | "-" b = izraz () {a - = b;}) * {vrni a; }} dvojni izraz (): {dvojni a; dvojni b; } {a = unary () ("*" b = term () {a * = b;} | "/" b = term () {a / = b;}) * {return a; }} double unary (): {dvojno a; } {"-" a = element () {return -a; } | a = element () {vrni a; }} dvojni element (): {žeton t; dvojno a; } {t = {return Double.parseDouble (t.toString ()); } | "(" a = expr () ")" {vrni a; }} 

Glavna metoda najprej ustvari objekt razčlenjevalnika, ki bere s standardnega vnosa in nato pokliče parseOneLine () v neskončno zanko. Metoda parseOneLine () sam je opredeljen z dodatnim slovničnim pravilom. To pravilo preprosto določa, da pričakujemo vsak izraz v vrstici sam po sebi, da lahko vnesemo prazne vrstice in program zaključimo, če pridemo do konca datoteke.

Za vrnitev smo spremenili vrsto vrnitve prvotnih slovničnih elementov dvojno. Ustrezne izračune izvedemo tam, kjer jih razčlenimo, in rezultate izračuna posredujemo po drevesu klicev. Prav tako smo preoblikovali definicije slovničnih elementov, da bi rezultate shranili v lokalne spremenljivke. Na primer, a = element () razčleni an element in rezultat shrani v spremenljivko a. To nam omogoča uporabo rezultatov razčlenjenih elementov v kodi dejanj na desni strani. Dejanja so bloki kode Java, ki se izvedejo, ko je povezano slovnično pravilo našlo ujemanje v vhodnem toku.

Upoštevajte, kako malo kode Java smo dodali, da je kalkulator popolnoma funkcionalen. Poleg tega je dodajanje dodatnih funkcij, kot so vgrajene funkcije ali celo spremenljivke, enostavno.

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