Programmēšana

Veidojiet savas valodas, izmantojot JavaCC

Vai jūs kādreiz domājat, kā darbojas Java kompilators? Vai jums ir jāraksta parsētāji marķēšanas dokumentiem, kuri nav abonējuši standarta formātus, piemēram, HTML vai XML? Vai arī vēlaties ieviest savu mazo programmēšanas valodu tikai tā dēļ? JavaCC ļauj to visu izdarīt Java valodā. Tātad, neatkarīgi no tā, vai jūs vienkārši interesējaties uzzināt vairāk par sastādītāju un tulku darbību, vai arī jums ir konkrēti mērķi izveidot Java programmēšanas valodas pēcteci, lūdzu, pievienojieties man šī mēneša meklējumos, lai izpētītu JavaCC, ko izceļ ērta maza komandrindas kalkulatora uzbūve.

Sastādītāja būvniecības pamati

Programmēšanas valodas bieži tiek mākslīgi sadalītas apkopotās un interpretētās valodās, kaut arī robežas ir izplūdušas. Kā tāds, neuztraucieties par to. Šeit apspriestie jēdzieni vienlīdz labi attiecas gan uz apkopotajām, gan uz interpretētajām valodām. Mēs izmantosim vārdu sastādītājs punktā, bet attiecībā uz šī panta darbības jomu tajā jāiekļauj tulks.

Sastādītājiem, veicot programmas tekstu (pirmkodu), ir jāveic trīs galvenie uzdevumi:

  1. Leksiskā analīze
  2. Sintaktiskā analīze
  3. Kodu ģenerēšana vai izpilde

Lielākā daļa sastādītāja darba koncentrējas ap 1. un 2. darbību, kas ietver programmas avota koda izpratni un tā sintaktiskās pareizības nodrošināšanu. Mēs to saucam par procesu parsējot, kas ir parsētājss atbildība.

Leksiskā analīze (leksika)

Leksiskā analīze virspusēji aplūko programmas pirmkodu un sadala to pareizajā žetoni. Marķieris ir nozīmīgs programmas avota koda gabals. Žetonu piemēri ietver atslēgvārdus, pieturzīmes, literāļus, piemēram, skaitļus, un virknes. Nontokens ietver atstarpi, kas bieži tiek ignorēta, bet tiek izmantota žetonu atdalīšanai, un komentāri.

Sintaktiskā analīze (parsēšana)

Sintaktiskās analīzes laikā parsētājs izraksta nozīmi no programmas avota koda, nodrošinot programmas sintaktisko pareizību un izveidojot programmas iekšējo attēlojumu.

Datorvalodas teorija runā programmas,gramatika, un valodās. Šajā ziņā programma ir žetonu secība. Burtnieks ir pamata datorvalodas elements, kuru nevar vēl vairāk samazināt. Gramatika nosaka noteikumus sintaktiski pareizu programmu veidošanai. Pareizas ir tikai programmas, kas spēlē pēc gramatikā definētajiem noteikumiem. Valoda ir vienkārši visu programmu kopa, kas atbilst visiem jūsu gramatikas noteikumiem.

Sintaktiskās analīzes laikā sastādītājs pārbauda programmas pirmkodu, ņemot vērā valodas gramatikā noteiktos noteikumus. Ja tiek pārkāpti kādi gramatikas noteikumi, kompilators parāda kļūdas ziņojumu. Pārbaudot programmu, kompilators izveido viegli apstrādājamu datorprogrammas iekšējo attēlojumu.

Datorvalodas gramatikas likumus var viennozīmīgi un pilnībā norādīt ar EBNF (Extended Backus-Naur-Form) apzīmējumu (vairāk par EBNF skat. Resursos). EBNF nosaka gramatikas attiecībā uz ražošanas noteikumiem. Ražošanas noteikums nosaka, ka gramatikas elements - vai nu literāļi, vai komponēti elementi - var sastāvēt no citiem gramatikas elementiem. Neveicami literāļi ir statiskās programmas teksta atslēgvārdi vai fragmenti, piemēram, pieturzīmes. Komponētos elementus iegūst, piemērojot ražošanas noteikumus. Ražošanas noteikumiem ir šāds vispārīgs formāts:

GRAMMAR_ELEMENT: = gramatikas elementu saraksts | alternatīvs gramatikas elementu saraksts 

Apskatīsim nelielas valodas gramatikas likumus, kas apraksta aritmētiskās pamatformulas:

expr: = skaitlis | expr '+' expr | expr '-' expr | expr * * expr | expr '/' expr | '(' expr ')' | - izteiksmes numurs: = cipars + ('.' cipars +)? cipars: = '0' | “1” | '2' | '3' | "4" | "5" | "6" | "7" | "8" | "9" 

Trīs ražošanas noteikumos ir definēti gramatikas elementi:

  • izteikt
  • numuru
  • cipars

Ar šo gramatiku noteiktā valoda ļauj mums norādīt aritmētiskās izteiksmes. An izteikt ir skaitlis vai viens no četriem infiksu operatoriem, kas lietoti diviem izteikts, an izteikt iekavās vai negatīvs izteikt. A numuru ir peldošā komata skaitlis ar izvēles decimāldaļu. Mēs definējam a cipars jābūt vienam no pazīstamajiem cipariem aiz komata.

Kodu ģenerēšana vai izpilde

Kad parsētājs veiksmīgi parsē programmu bez kļūdām, tas pastāv iekšējā attēlojumā, kuru kompilators viegli apstrādā. Tagad ir diezgan viegli ģenerēt mašīnkodu (vai Java bytecode šim jautājumam) no iekšējās attēlojuma vai tieši izpildīt iekšējo attēlojumu. Ja mēs darām pirmo, mēs apkopojam; pēdējā gadījumā mēs runājam par tulkošanu.

JavaCC

JavaCC, pieejams bez maksas, ir parsēšanas ģenerators. Tas nodrošina Java valodas paplašinājumu, lai norādītu programmēšanas valodas gramatiku. JavaCC sākotnēji izstrādāja Sun Microsystems, bet tagad to uztur MetaMata. Tāpat kā jebkurš pienācīgs programmēšanas rīks, JavaCC tika faktiski izmantots, lai precizētu gramatiku JavaCC ievades formāts.

Turklāt, JavaCC ļauj mums noteikt gramatikas līdzīgi kā EBNF, padarot to viegli tulkot EBNF gramatikas valodā JavaCC formātā. Tālāk, JavaCC ir vispopulārākais Java parsēšanas ģenerators, ar daudzām iepriekš definētām JavaCC gramatikas, kuras var izmantot kā sākumpunktu.

Vienkārša kalkulatora izstrāde

Mēs tagad atkārtoti apmeklējam mūsu mazo aritmētisko valodu, lai izveidotu vienkāršu komandrindas kalkulatoru Java JavaCC. Pirmkārt, mums jāpārvērš EBNF gramatika JavaCC formātā un saglabājiet to failā Aritmētika.jj:

opcijas {LOOKAHEAD = 2; } PARSER_BEGIN (aritmētika) publiskās klases aritmētika {} PARSER_END (aritmētika) SKIP: "\ t" TOKEN: double expr (): {} term () ("+" expr () double term (): {} "/" termins ()) * double unary (): {} "-" element () double element (): {} "(" expr () ")" 

Iepriekš norādītajam kodam vajadzētu dot priekšstatu par to, kā norādīt gramatiku JavaCC. The iespējas Sadaļā augšpusē ir norādīta opciju kopa šai gramatikai. Mēs norādām skatienu no 2. Papildu opciju vadība JavaCCatkļūdošanas funkcijas un daudz ko citu. Šīs opcijas var arī norādīt uz JavaCC komandrinda.

The PARSER_BEGIN klauzula norāda, ka seko parsētāja klases definīcija. JavaCC katram parsētājam ģenerē vienu Java klasi. Mēs saucam parsēšanas klasi Aritmētika. Pagaidām mums ir nepieciešama tikai tukša klases definīcija; JavaCC vēlāk pievienos ar parsēšanu saistītās deklarācijas. Mēs beidzam klases definīciju ar PARSER_END klauzula.

The Izlaist sadaļā ir norādītas rakstzīmes, kuras vēlamies izlaist. Mūsu gadījumā tie ir baltās vietas rakstzīmes. Pēc tam mēs definējam mūsu valodas žetonus TOKEN sadaļā. Mēs definējam ciparus un ciparus kā marķierus. Pieraksti to JavaCC atšķir žetonu definīcijas un citu ražošanas noteikumu definīcijas, kas atšķiras no EBNF. The Izlaist un TOKEN sadaļās norādīta šīs gramatikas leksiskā analīze.

Tālāk mēs definējam ražošanas kārtulu izteikt, augstākā līmeņa gramatikas elements. Ievērojiet, kā šī definīcija ievērojami atšķiras no definīcijas izteikt EBNF. Kas notiek? Nu, izrādās, ka iepriekš minētā EBNF definīcija ir neskaidra, jo tā ļauj vairākkārt attēlot vienu un to pašu programmu. Piemēram, pārbaudīsim izteicienu 1+2*3. Mēs varam saskaņot 1+2 uz izteikt piekāpšanās izteiciens * 3, kā parādīts 1. attēlā.

Vai arī mēs vispirms varētu saspēlēties 2*3 uz izteikt kā rezultātā 1 + izteiksme, kā parādīts 2. attēlā.

Ar JavaCC, mums nepārprotami jānorāda gramatikas likumi. Rezultātā mēs izjaucam definīciju izteikt trīs ražošanas noteikumos, definējot gramatikas elementus izteikt, jēdziens, unāra, un elements. Tagad izteiciens 1+2*3 ir parsēts, kā parādīts 3. attēlā.

No komandrindas mēs varam palaist JavaCC lai pārbaudītu mūsu gramatiku:

javacc Arithmetic.jj Java kompilatora kompilatora versija 1.1 (Parsētāja ģenerators) Autortiesības (c) 1996-1999 Sun Microsystems, Inc. Autortiesības (c) 1997-1999 Metamata, Inc. (ierakstiet "javacc" bez argumentiem par palīdzību) Lasīšana no faila Aritmētika.jj. . . Brīdinājums: uzmeklētāja atbilstības pārbaude netiek veikta, jo opcija LOOKAHEAD ir lielāka par 1. Iestatiet opciju FORCE_LA_CHECK uz true to force to force. Parsētājs ģenerēts ar 0 kļūdām un 1 brīdinājumu. 

Šis pārbauda, ​​vai mūsu gramatikas definīcijā nav problēmu, un ģenerē Java avota failu kopu:

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

Šie faili kopā ievieš Java parsētāju. Jūs varat izsaukt šo parsētāju, veicot tūlītēju Aritmētika klase:

public class Aritmētika realizē ArithmeticConstants {public Arithmetic (java.io.InputStream straume) {...} public Arithmetic (java.io.Reader stream) {...} public Arithmetic (ArithmeticTokenManager tm) {...} static final public double expr () throws ParseException {...} static final public double term () throws ParseException {...} static final public double unary () throws ParseException {...} static final public double element () throws ParseException {. ..} static public void ReInit (java.io.InputStream stream) {...} static public void ReInit (java.io.Reader stream) {...} public void ReInit (ArithmeticTokenManager tm) {...} static final public Token getNextToken () {...} static final public Token getToken (int index) {...} static final public ParseException geneParseException () {...} static final public void enable_tracing () {...} static final public void disable_tracing () {...}} 

Ja vēlaties izmantot šo parsētāju, jums jāizveido instance, izmantojot vienu no konstruktoriem. Konstruktori ļauj jums iet vai nu InputStream, a Lasītājsvai an ArithmeticTokenManager kā programmas avota koda avotu. Pēc tam jūs norādāt galveno valodas gramatikas elementu, piemēram:

Aritmētiskais parsētājs = jauns aritmētiskais (System.in); parser.expr (); 

Tomēr nekas daudz vēl nenotiek, jo Aritmētika.jj mēs esam definējuši tikai gramatikas likumus. Mēs vēl neesam pievienojuši kodu, kas nepieciešams aprēķinu veikšanai. Lai to izdarītu, mēs pievienojam atbilstošas ​​darbības gramatikas likumiem. Calcualtor.jj satur pilnu kalkulatoru, ieskaitot darbības:

opcijas {LOOKAHEAD = 2; } PARSER_BEGIN (Kalkulators) public class Calculator {public static void main (String args []) throws ParseException {Calculator parser = new Calculator (System.in); while (true) {parser.parseOneLine (); }}} PARSER_END (Kalkulators) PĀRTRAUKT: "\ t" TOKEN: void parseOneLine (): {double a; } {a = expr () {System.out.println (a); } | | {Sistēma.iziet (-1); }} double expr (): {double a; dubultā b; } {a = termins () ("+" b = expr () {a + = b;} | "-" b = expr () {a - = b;}) * {atgriež a; }} dubultvārds (): {dubultā a; dubultā b; } {a = vienreizējs () ("*" b = termins () {a * = b;} | "/" b = termins () {a / = b;}) * {atgriež a; }} dubultā unāra (): {dubultā a; } {"-" a = elements () {atgriešanās -a; } | a = elements () {atgriež a; }} dubultelements (): {Žetons t; dubultā a; } {t = {atgriešanās Double.parseDouble (t.toString ()); } | "(" a = expr () ")" {atgriež a; }} 

Galvenā metode vispirms instancē parsētāja objektu, kas nolasa no standarta ievades un pēc tam zvana parseOneLine () bezgalīgā cilpā. Metode parseOneLine () pati par sevi ir definēta ar papildu gramatikas likumu. Šis noteikums vienkārši nosaka, ka mēs sagaidām katru rindas izteicienu pats par sevi, ka ir pareizi ievadīt tukšas rindas un ka mēs pārtraucam programmu, ja esam nonākuši faila beigās.

Mēs esam mainījuši sākotnējo gramatikas elementu atgriešanas veidu, lai atgrieztos dubultā. Mēs veicam atbilstošus aprēķinus tieši tur, kur tos parsējam, un veicam aprēķinu rezultātus līdz zvana kokam. Mēs esam pārveidojuši arī gramatikas elementu definīcijas, lai to rezultātus saglabātu vietējos mainīgajos. Piemēram, a = elements () parsē an elements un saglabā rezultātu mainīgajā a. Tas ļauj mums parsēto elementu rezultātus izmantot darbību kodā labajā pusē. Darbības ir Java koda bloki, kas tiek izpildīti, kad saistītais gramatikas noteikums ievades straumē ir atradis atbilstību.

Lūdzu, ņemiet vērā, cik maz Java koda mēs pievienojām, lai kalkulators darbotos pilnībā. Turklāt ir viegli pievienot papildu funkcionalitāti, piemēram, iebūvētās funkcijas vai pat mainīgos.

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