Transcript Document

Leksinė analizė (skenavimas)
DO 10 I = 1,5
• Pirmasis teksto supratimo
DO 10 I = 1.5
žingsnis – atpažinti žodžius
• Vienu iš pagrindinių leksinio analizatoriaus
užduočių, yra išskirti programos tekste tokenus
– tokenas yra leksemų rūšis (tokenas gali būti
simboliu, operatoriumi, identifikatoriumi ar
raktiniu žodžiu), tai simbolinė konstanta.
– komentarai ir tušti tarpai atmetami.
Leksinis
analizatorius
Programos tekstas
Tokenų srautas
1
Leksinė analizė (skenavimas)
Pavyzdys
sum = oldsum – value / 100;
Leksema
sum
=
oldsum
value
/
100
;
Tokenas
IDENTIFIER
ASSIGN_OP
IDENTIFIER
SUBTRACT_OP
IDENTIFIER
DIVISION_OP
INIT_LITERAL
SEMICOLON
2
Kaip aprašyti tokenus?
• Šablonas – tai taisyklė aprašanti tokeną
atitinkančias leksemas.
– Šioms taisyklėms aprašyti yra sukurtas
specialus žymėjimas – reguliarios išraiškos.
– Programavimo kalbos atominiai vienetai –
tokenai yra aprašomi reguliariomis išraiškomis
• Tarp tokenų reikia mokėti išskirti rezervuotus
žodžius, jei kalboje nėra rezervuotų žodžių,
leksinė analizė tampa labai sudėtinga
– Pavyzdžiui PL/I kalba:
IF THEN THEN THEN = ELSE;
ELSE ELSE = THEN;
– FORTRAN kalba.
3
Kalba (apibrėžimai)
• Terminas alfabetas žymi bet kokią baigtinę
simbolių aibę.
– Aibė {0, 1} yra binarinis alfabetas
– ASCII, EBCDIC yra kompiuteriniai alfabetai
• Alfabeto eilutė, tai baigtinė alfabeto simbolių
seka.
• Kalba, tai bet kokia alfabeto eilučių aibė.
– tuščia aibė irgi kalba!
– tuščių eilučių aibė {ε} irgi kalba!
– visi lietuvių kalbos sakiniai irgi sudaro kalbą.
4
Operacijos su kalbomis
• Kalbų L ir M junginys LUM={s|sєL arba sєM}
• Kalbų L ir M konkatenacija LM={st|sєL ir tєM}
• Kalbos L Kleene uždarinys L*=Ui=0∞Li
– tai nulis arba daugiau konkatenacijų
• Kalbos L teigiamas uždarinys L+=Ui=1∞Li
– tai viena arba daugiau konkatenacijų
– čia L0={ε}, Li=Li-1L.
5
Kalbų kūrimo pavyzdžiai
•
1.
2.
3.
4.
5.
6.
Tegu L={A,B,...,Z,a,b,...,z} ir M={0,1,2,...,9}
– Kadangi simbolį galima laikyti vienetinio ilgio
eilute, aibės L ir M yra baigtinės kalbos.
LUM yra raidžių ir skaičių aibė
LM yra aibė eilučių sudarytų iš raidės po kuria
seka skaičius
L4 tai visų keturraidžių eilučių aibė
L* aibė eilučių iš visų raidžių, tame tarpe ir tuščia
eilutė ε
L(LUM)* aibė eilučių iš raidžių ir skaičių
prasidedanti raide
M+ aibė eilučių sudarytų iš vieno ir daugiau
simbolių
6
Reguliarios išraiškos
•
•
•
Daugumos programavimo kalbų leksinė struktūra gali
būti apibrėžta reguliariomis išraiškomis.
Reguliarios išraiškos yra apibrėžiamos virš tam tikro
(nustatyto) alfabeto Σ
– Dauguma programavimo kalbų alfabetu naudoja
ASCII arba Unicode
Jei re yra reguliari išraiška, tuomet L(re ) yra re
sugeneruota kalba (simbolinių eilučių rinkinys)
7
Reguliarios išraiškos
•
Reguliari išraiška apibrėžiama tam tikromis taisyklėmis.
1. tuščia eilutė ε yra reguliari išraiška
2. simbolis, pavyzdžiui a yra reguliari išraiška
3. Jei R ir S yra reguliarios išraiškos, tai reguliaria
išraiška bus ir
1. R|S (reiškia R arba S)
2. RS (konkatenacija)
3. R* (nulis arba daug R tarpusavio konkatenacijų)
4. (R) (reguliarias išraiškas galima grupuoti)
•
Reguliarias išraiškas galima įvardinti: vardas → r
8
Kalbų generavimo taisyklės
re
L(re )
Pastabos
RS
L(R)L(S)
konkatenacija
R|S
L(R)UL(S)
junginys
L(R)*
Kleene
uždarinys
R*
9
Pavyzdžiai (1)
• Reguliari išraiška a|b reiškia aibę {a,b}
• Reguliari išraiška (a|b)(a|b) reiškia aibę {aa,ab,ba,bb}
– aibę visų eilučių iš a ir b, kurių ilgis 2.
– Kita šiai aibei atitinkanti reguliari išraiška
aa|ab|ba|bb
• Reguliari išraiška a* reiškia aibę {ε,a,aa,aaa,...}
• Reguliari išraiška (a|b)* reiškia aibę visų galimų eilučių
iš a ir b: {ε,a,b,aa,ab,bb,...}
– Kita šiai aibei atitinkanti reguliari išraiška (a*b*)*
10
Pavyzdžiai (2)
• Reguliari išraiška a|a*b reiškia aibę turinčią a ir
eilutes turinčias nulį ar daugiau a ir
besibaigiančias b
• Reguliari išraiška ba* reiškia aibę
{b,ba,baa,baaa,...}
• Reguliari išraiška a*|b reiškia aibę
{b,ε,a,aa,aaa,...}
• (0|1)*1 reiškia aibę binarinių skaičių
besibaigiančių 1
11
Algebrinės reguliarių išraiškų savybės
•
•
•
•
r|s=s|r – operatorius | komutatyvus
r|(s|t)=(r|s)|t – operatorius | asociatyvus
(rs)t=r(st) – konkatenacija asociatyvi
r(s|t)=rs|rt, (s|t)r=sr|tr – konkatenacija
distributyvi operatoriaus | atžvilgiu
• εr=r, rε=r – konkatenacijos atžvilgiu, ε yra
vienetinis elementas
• r*=(r|ε)* - ryšys tarp * ir ε
• r**=r*
12
Pavyzdžiai
• Paskalio identifikatorių apibrėžianti reguliari išraiška
letter → A|B|...|Z|a|b|...|z
digit → 0|1|2|3|4|5|6|7|8|9
id → letter(letter|digit)*
• Skaičius (pvz.: 1.89E-4) apibrėžianti reguliari
išraiška
digit → 0|1|...|9
digits → digit digit*
optional_fraction → .digits|ε
optional_exponent → (E(+|-|ε)digits)|ε
num → digits optional_fraction optional_exponent
13
Sutrumpinimai
• R+ (vienas ar daugiau R)
– a+ aprašo visas eilutes sudarytas iš vieno ar
daugiau a simbolių a, aa, aaa, ...
– r* = r+|ε, r+ = rr*
• R? (nulis arba vienas R)
– r? = r|ε
• [a-z], [A-Z], [0-9] (sutrumpintas simbolių klasės
žymėjimas)
– [abc] = a|b|c
– [a-z] = a|b|...|z
– Paskalio identifikatorius [A-Za-z] [A-Za-z0-9]*
14
Pavyzdys
• Skaičius (pvz.: 1.89E-4) apibrėžianti reguliari
išraiška, naudojanti sutrumpinimus
digit → [0-9]
digits → digit+
optional_fraction → (.digits)?
optional_exponent → (E(+|-)?digits)?
num → digits optional_fraction optional_exponent
15
Prioritetai
1.
2.
3.
•
Operatoriai *, + ir ? turi aukščiausią prioritetą.
Konkatenacijos prioritetas žemesnis.
| turi žemiausią prioritetą.
Visi operatoriai yra asociatyvūs iš kairės.
•
Pavyzdžiui, šių susitarimų dėka išraiška
(a)|((b)*(c)) yra ekvivalenti a|b*c
– Tai aibė eilučių kurias sudaro arba vienintelis
a, arba nulis arba keletas b, po kurių seka
vienintelis c.
16
Leksinių analizatorių istorija
• LEX
– Leksinis analizatorius sukurtas Lesk ir Schmidt iš
Bell Labs 1975 UNIX operacinei sistemai
– Šiuo metu egzistuoja daugumoje operacinių sistemų
– LEX generuoja leksinį analizatorių - C kalba parašytą
programą
– LEX nurodytoms reguliarioms išraiškoms įgalina
atlikti nurodytas veikas
• JLex
– sukurtas Elliot Berk iš Princeton University 1996
– tai Lex generuojantis leksinį analizatorių - Java
kalba parašytą programą
– JLex pats parašytas Java kalba
17
Tokenų apibrėžimas
Reguliarios Išraiškos
JLex
veikla
JLex
Java Failas: Scanner Class
(Yylex, leksinę analizę atlieka metodas yylex())
Tokenų atpažinimas
Regular expression NFA DFA lexer
18
JLex aprašymo failo struktūra
vartotojo kodas (user code)
%%
JLex direktyvos (JLex directives)
%%
reguliarių išraiškų taisyklės (regular expression rules)
• Komentarai
– prasideda //
– arba keletui eilučių /* */ galimi tik pirmose
dvejose dalyse
19
vartotojo kodas
• JLex suteikia vartotojui galimybę, esant reikalui,
panaudoti savo parašytą programinį kodą.
• Vartotojo kodas bus be pakeitimų įrašytas į JLex
išvesties failą, pačioje jo pradžioje.
• Dažniausiai tai būna:
– paketų deklaracijos
– import deklaracijos
– Papildomos, vartotojo parašytos klasės
20
JLex direktyvos
• Šiame skyriuje pateikiami
– makrosų apibrėžimai (macro definitions) –
reguliarių išraiškų sutrumpinti pavadinimai
• naudojami apibrėžti kas yra raidės, skaičiai ir
tušti tarpai.
– būsenų deklaracijos
– analizatoriaus savybių pasirinkimai
– standartinės leksinio analizatoriaus klasės
papildymai
• Kiekviena JLex direktyva turi būti užrašoma
atskiroje eilutėje ir turi pradėti tą eilutę.
21
Reguliarių išraiškų taisyklės
• Šį skyrių sudaro taisyklių rinkinys nurodantis kaip
suskaidyti įvesties srautą į tokenus.
• Reguliarių išraiškų taisyklės yra sudarytos iš trijų dalių:
– būsenų sąrašas (nebūtinas)
– reguliari išraiška
– susieta veika (Java kodo fragmentai)
• Kiekviena veika turi grąžinti reikšmę apibrėžtą
%type deklaracijoje esančioje antrojoje JLex
aprašo dalyje.
• Jei veika nieko negrąžina, einamasis tokenas
atmetamas ir leksinis analizatorius dar kartą
iškviečia pats save.
[<būsenos>] <reg. išraiška> { <veika>}
22
JLex direktyvos (1)
• Direktyva %{...%} leidžia vartotojui įrašyti Java kodą
tiesiai į leksinio analizatoriaus klasę.
• Direktyvos naudojimo pavyzdys:
%{
<kodas>
%}
• JLex įrašys Java kodą į sukuriamą leksinio analizatoriaus
klasę:
class Yylex {
... <kodas> ...
}
• Tai leidžia aprašyti papildomus vidinius leksinio
analizatoriaus klasės kintamuosius ir metodus.
• Pažymėtina, kad negalima naudoti kintamųjų vardų
prasidedančių yy – tokius vardus naudoja pati leksinio
analizatoriaus klasė.
23
JLex direktyvos (2)
• Direktyva %init{ ... %init} leidžia vartotojui
įrašyti Java kodą tiesiai į leksinio analizatoriaus
klasės konstruktorių
%init{
<kodas>
%init}
• JLex įrašys Java kodą į sukuriamą leksinio
analizatoriaus klasės konstruktorių:
class Yylex {
Yylex () {
... <kodas> ...
}
• Ši direktyva leidžia atlikti papildomą leksinio
analizatoriaus klasės inicializaciją.
24
JLex direktyvos (3)
• Makrosų paskirtis:
– vieną kartą apibrėžus reguliarią išraišką, toliau
atitinkamose vietose galima naudoti tik jos
vardą (daugelį kartų).
– Praktiškai būtini didesnėms reguliarioms
išraiškoms.
• Makrosų apibrėžimo formatas:
<vardas> = <apibrėžimas>
– Makroso vardas yra identifikatorius, t.y. gali
būti sudarytas iš raidžių, skaitmenų ir apatinių
brūkšnių, bei turi prasidėti raide arba apatiniu
brūkšniu.
– Makroso apibrėžimas turi būti teisingai užrašyta
reguliari išraiška.
• Makrosų apibrėžimuose gali būti panaudoti kiti
makrosai - {<vardas>}
25
JLex direktyvos (4)
• Leksinių būsenų pagalba kontroliuojamas reguliarių
išraiškų atitikimas.
• Leksinių būsenų deklaravimo formatas
%state state[0][, state[1], state[2], ...]
• Leksinės būsenos vardas turi būti identifikatorius.
• Pagal nutylėjimą JLex pats deklaruoja vieną būseną
- YYINITIAL, kuria sukurtas leksinis
analizatorius pradeda leksinę analizę.
• Jei būsenų sąrašas nenurodytas, reguliarios
išraiškos atitikimas neribojamas.
• Jei būsenų sąrašas nurodytas, reguliariai išraiškai
bus leidžiama atitikti tik tuomet, jei leksinis
analizatorius bus vienoje iš nurodytų būsenų.
• Būsenų vardai turi būti unikalūs – tam tikslui
patartina juos pradėti didžiaja raide.
26
JLex direktyvos (5)
• JavaCUP – sintaksinio analizatoriaus generatorius
Java kalbai buvo sukurtas Scott Hudson iš Georgia
Tech universiteto, ir išvystytas Frank Flannery,
Dan Wang, ir C. Scott Ananian pastangomis.
– Smulkiau apie šį įrankį:
http://www.cs.princeton.edu/~appel/modern/j
ava/CUP/
• Suderinamumas su JavaCUP aktyvuojamas
sekančia JLex direktyva
%cup
27
Simbolių ir eilučių skaičiavimo direktyvos
• Kartais naudinga žinoti kur tekste yra tokenas. Tokeno
padėtis nusakoma jo eilutės ir jo pirmojo simbolio eilės
numeriu tekste.
• Simbolių skaičiavimas aktyvuojamas direktyva “%char”
– Sukuriamas kintamasis yychar (leks. analizatoriaus);
– jo reikšmė – pirmojo atitikusioje šabloną leksemoje
simbolio eilės numeris.
• Eilučių skaičiavimas aktyvuojamas direktyva “%line”
– Sukuriamas kintamasis yyline;
– jo reikšmė – atitikusios šabloną simbolinės eilutės
pirmosios eilutės eilės numeris.
• Pavyzdys:
“int” { return (new
Yytoken(4,yytext(),yyline,yychar,yychar+3)); }
28
Reguliarių išraiškų taisyklės
• Jei nuskaitymo metu simbolinei eilutei atitinka keletas
taisyklių, jos skaidymas į tokenus vyksta pagal tą
taisyklę, kuri atitinka ilgiausią tokeną.
• Jei keletas taisyklių atitinka to pačio ilgio simbolinę
eilutę, pasirenkama ta taisyklė, kuri JLex aprašyme yra
pirmoji (ankstesnės taisyklės turi aukštesnį prioritetą).
• JLex aprašyme reikia numatyti reguliarias taisykles
visiems galimiems atvejams.
– Jei leksinio analizatoriaus įvestis neatitiks jokiai
aprašytai taisyklei, leksinis analizatorius nutrauks
darbą.
– Galima apsidrausti, JLex aprašymo pabaigoje
patalpinus sekančią taisyklę:
. { java.lang.System.out.println("Unmatched input: "
+ yytext()); }
– Čia taškas (.) nurodo atitikimą bet kokiai įvesčiai,
išskyrus naują eilutę (newline).
29
Reguliarios išraiškos (1)
• JLex alfabetas yra ASCII simboliai nuo 0 iki 127
imtinai.
– Šie simboliai yra reguliarios išraiškos patys sau.
• Reguliariose išraiškose negalima tiesiogiai naudoti
tarpų - tarpas laikomas reguliarios išraiškos
pabaiga.
– Jei tuščias tarpas reikalingas reguliarioje
išraiškoje, jį reikia nurodyti tarp dvigubų
kabučių: " "
• Sekantys simboliai yra meta simboliai, turintys
JLex reguliariose išraiškose specialią reikšmę:
? * + | ( ) ^ $ . [ ] { } " \
30
Reguliarios išraiškos (2)
• ef viena po kitos einančios reguliarios išraiškos
reiškia jų konkatenaciją.
• e|f vertikalus brūkšnys | nurodo kad atitikti gali
arba išraiška e arba f.
• \b reiškia Backspace
• \n reiškia newline
• \t reiškia Tab
• \f reiškia Formfeed
• \r reiškia Carriage return
• \^C reiškia Control character
• \c - bet koks simbolis po \ reiškia save patį.
31
Reguliarios išraiškos (3)
• $ žymi eilutės pabaigą.
– Jei reguliari išraiška baigiasi $ jos atitikimas
tikrinamas tik eilutės pabaigoje (t.y. iš kito galo).
• . atitinka bet kokį simbolį išskyrus naują eilutę.
– Taigi, ši išraiška ekvivalenti [^\n].
• "..." metasimboliai tampa paprastais simboliais dvigubose
kabutėse.
– Pažymėsime, kad \" reiškia simbolį "
• {vardas} nurodo čia bus išskleistas makrosas su nurodytu
vardu.
• * pažymi Kleene uždarinį nurodantį nulį ar daugiau
reguliarios išraiškos pasikartojimų.
• + nurodo vieną ar daugiau reguliarios išraiškos
pasikartojimų.Taigi e+ yra ekvivalenti ee*
• ? nurodo, kad reguliari išraiška gali būti taikoma arba ne.
32
Reguliarios išraiškos (4)
• (...) skliaustai naudojami reguliarių išraiškų
grupavimui.
• [...] pažymi simbolių klasę - reguliari išraiška
atitinka visiems klasės simboliams.
– Jei pirmasis simbolis [...] yra (^), tai nurodo kad
reguliari išraiška atitinka visiems simboliams
išskyrus nurodytus skliaustuose.
• Pavyzdžiui,
– [a-z] atitinka visas mažasias raides,
– [^0-9] atitinka viską išskyrus skaičius,
– [\-\\] atitinka -\,
– ["A-Z"] atitinka tris simbolius: A-Z,
– [+-] ir [-+] atitinka + ir -.
33
Pavyzdžiai
• “a|b”
atitinka a|b bet ne a arba b
• ^main atitiks leksemą “main” tik tuomet kai ji bus
eilutės pradžioje.
• main$ atitiks leksemą “main” tik tuomet kai ji bus
eilutės pabaigoje.
• [a bc] yra ekvivalentus a|" "|b|c
34
Susieta veika (action)
• Ši veika atliekama kai nurodytas šablonas yra
atpažystamas
• Veika, tai Java sakiniai (standartiniai) grąžinantys
tokenus.
35
Leksinis analizatorius
• JLex taisyklingai parašytą aprašą transformuoja į
java programą vardu Yylex.
• Ši klasė turi du konstruktorius turinčius vieną
argumentą - įvesties srautą (input stream) kuri
reikia išskaidyti į tokenus.
– Įvesties srautas gali būti atitinkamai arba
java.io.InputStream, arba java.io.Reader
(pavyzdžiui StringReader).
– Konstruktorius java.io.Reader naudojamas jei
įvesties sraute gali būti unicode simbolių.
• Sekantį tokeną iš įvesties srauto grąžina leksinio
analizatoriaus metodas Yylex.yylex().
– Grąžinamos reikšmės tipas yra Yytoken.
36
Specialūs JLex kintamieji/metodai
• yytext() – grąžina simbolinę eilutę kuriai leksinis
analizatorius rado atitikmenį (galima nustatyti šios
simbolinės eilutės semantinę reikšmę)
– t.y. tai tokeno, kurį grąžina yylex() leksema
• yylength() – grąžina atitiktos simbolinės eilutės ilgį
• yychar – saugo atitiktos eilutės (matched string)
pradžios padėtį faile
– tokeno pradžia: yychar
– tokeno pabaiga: yychar + yylength()
• yylex() paprastai grąžina Yytoken klasės
egzempliorių, nors galima deklaruoti ir kitą tipą
direktyva %type
37
Tokenai
• Paprastai kiekvienas tokenas yra klasės Symbol iš
paketo - java_cup.runtime elementas.
• Šį paketą eksportuoja analizatorius JavaCUP
• Symbol klasėje nustatomi sveiki skaičiai
atitinkantys kiekvienam tokenui.
• Pavyzdžiui, čia aprašytos sveiko tipo konstantos,
tokios kaip sym.ID, sym.IF
38
Reguliarios išraiškos pavyzdys
reguliariIšraiška{ veika }
• Pavyzdys:
{IDENTIFIER}{ System.out.println("ID is ..." +
yytext());}
• Prasmė:
– Pirmuosiuose skliaustuose šablonas, kurio
atitikmens ieškome;
– antruosiuose skliaustuose kodas, kuris bus
vykdomas jei atitikmuo bus rastas.
39
Makrosų pavyzdžiai
• Apibrėžimas (antroji JLex aprašymo dalis):
IDENTIFIER = [a-zA-z_][a-zA-Z0-9_]*
LETTER=[A-Za-z_]
DIGIT=[0-9]
WHITESPACE= [ \t\n]
ALPHA_NUMERIC={LETTER}|{DIGIT}
• Panaudojimas (trečioji JLex aprašymo dalis):
{IDENTIFIER} {return new Token(ID,
yytext());
40
Būsenų deklaracijos (1)
• Kai kurioms simbolinėms eilutėms atitikmuo turi
būti ieškomas su skirtingomis reguliariomis
išraiškomis.
• Taigi reikia turėti galimybę pervesti leksinį
analizatorių į įvairias būsenas, kuriose jis
funkcionuotų skirtingai, t.y. naudotų kitas
reguliarias išraiškas.
• Pradėdamas darbą leksinis analizatorius yra
būsenoje YYINITIAL.
• Jei norime naudoti savo būsenas, aprašome jas
antrojoje JLex aprašo dalyje.
– Pavyzdžiui: %state COMMENTS
• Perėjimui tarp būsenų naudojamas metodas
yybegin().
41
Būsenų deklaracijos (2)
• Pavyzdys:
<YYINITIAL> "//" {yybegin(COMMENTS);}
<COMMENTS> [^\n] {}
<COMMENTS> [\n] {yybegin(YYINITIAL);}
• Jei Yylex yra pradinėje YYINITIAL būsenoje ir
atpažysta //, tuomet jis pereina į būseną
COMMENTS
• Jei jis yra būsenoje COMMENTS ir atpažysta bet
kokį simbolį išskyrus \n, tuomet jokia veika
neatliekama
• Jei jis yra būsenoje COMMENTS ir atpažysta \n,
tuomet jis grįžta atgal į būseną YYINITIAL
42
minimal.lex
package Example;
import java_cup.runtime.Symbol;
%%
%cup
%%
";" { return new Symbol(sym.SEMI); }
"+" { return new Symbol(sym.PLUS); }
"*" { return new Symbol(sym.TIMES); }
"(" { return new Symbol(sym.LPAREN); }
")" { return new Symbol(sym.RPAREN); }
[0-9]+ { return new Symbol(sym.NUMBER, new
Integer(yytext())); }
[ \t\r\n\f] { /* ignore white space. */ }
. { System.err.println("Illegal character: "+yytext()); }
43
import java_cup.runtime.Symbol;
• Ši eilutė importuoja klasę Symbol.
• Kai sintaksinis analizatorius iškviečia Yylex
sekančiam tokenui, Yylex objektas grąžina Symbol
klasės egzempliorių.
44
Symbol klasė
• Symbol klasės egzempliorius turi keletą
konstruktorių.
• "+" { return new Symbol(sym.PLUS); }
– Paprasčiausias naudoja tik tokeno eilės numerį
(t.y. vieną iš sugeneruotos sym klasės konstantų)
– Šioms konstantoms vardus duodame mes patys, o
reikšmes priskiria JavaCUP generuodamas
sym.java failą.
• [0-9]+ { return new Symbol(sym.NUMBER,
new Integer(yytext())); }
– Sudėtingesnis konstruktorius dar naudoja leksinę
reikšmę kurią apibrėžia tam skirtas objektas
• čia naudojamas Java objektas
• gali būti naudojamas ir mūsų sukurtos klasės
objektas
45
minimal.lex tokenams atitinkantys sveiki skaičiai
//---------------------------------------------------// The following code was generated by CUP v0.10k
// Sat Oct 02 09:56:56 EEST 2004
//----------------------------------------------------
package Example;
/** CUP generated class containing symbol constants. */
public class sym {
/* terminals */
public static final int RPAREN = 6;
public static final int error = 1;
public static final int PLUS = 3;
public static final int NUMBER = 7;
public static final int SEMI = 2;
public static final int LPAREN = 5;
public static final int TIMES = 4;
public static final int EOF = 0;
}
46
Įvesties failo panaudojimas JLex/CUP
47
Anglų kalbos gramatika
A sentence is a noun
phrase, a verb, and a
noun phrase.
<S> ::= <NP> <V> <NP>
A noun phrase is an
article and a noun.
<NP> ::= <A> <N>
A verb is…
<V> ::= loves | hates | eats
An article is…
<A> ::= a | the
A noun is...
<N> ::= dog | cat | rat
48
Gramatikos taikymas
• Gramatika yra taisyklių rinkinys nusakantis kaip
sukonstruoti sintaksinį medį – a parse tree
• Medžio šaknyje (root) patalpiname aukščiausią
hierarchijos elementą – t.y. <S>
• Gramatikos taisyklės nusako kaip toliau yra
pridedami sekantys mazgai - vaikai.
– Pavyzdžiui, taisyklė <S> ::= <NP> <V> <NP>
parodo kokia tvarka pridedami mazgai <NP>,
<V> ir <NP> – t.y. <S> vaikai.
49
start symbol
<S> ::= <NP> <V> <NP>
a production
<NP> ::= <A> <N>
<V> ::= loves | hates|eats
<A> ::= a | the
non-terminal
symbols
<N> ::= dog | cat | rat
tokens
50
A Parse Tree
<S>
<NP> <V> <NP>
<A> <N>
the
dog
loves
<A> <N>
the
cat
51
Kontekstiškai laisva gramatika
(Context free grammar)
• Programavimo kalbų sintaksė aprašoma kontekstiškai
laisvomis gramatikomis (CFG).
• CFG taisyklės yra daugumoje rekursyvios.
• Sintaksinis analizatorius tikrina, ar programa tenkina
CFG taisykles ar ne. Jei tenkina, sintaksinis
analizatorius kuria programai sintaksinį medį (parse
tree)
• CFG aprašomos (Backus Naur Form), pavyzdžiui:
assignment -> identifier := expression
expression -> identifier
expression -> number
expression -> expression + expression
• T.y. sintaksinis analizatorius tikrina, ar programą
galima išvesti naudojantis nustatytos gramatikos BNF
52
Kontekstiškai laisvos gramatikos
Kontekstiškai laisvą gramatiką sudaro
• V: baigtinis neterminalų skaičius
• Σ: baigtinis terminalų skaičius
• R: Baigtinis taisyklių skaičius, kurių bendra forma
neterminalas -> {neterminalas, terminalas}*
• S: pradinis neterminalas
Dauguma programavimo kalbų yra kontekstiškai laisvos
53
Kontekstiškai laisvos gramatikos
komponentai
1.
2.
3.
4.
Tokenai (terminalai)
Neterminalai
Produkcijos
Pradinis neterminalas
Gramatika nusakoma
išvardinant jos produkcijas.
Tokenų eilutės pagimdytos
pradinio simbolio sudaro
gramatika apibrėžtą kalbą.
Pavyzdys:
list → list + digit | list – digit | digit
digit → 0|1|2|3|4|5|6|7|8|9
54
BNF gramatikos apibrėžimas (1)
• BNF gramatiką sudaro keturios dalys:
– Tokenų rinkinys
– Non-terminal symbols rinkinys
– Pradinis simbolis (start symbol)
– Produkcijų (productions) rinkinys
• Tokenai yra mažiausi (atominiai) sintaksės vienetai.
• Neterminalai atstovauja stambesnius sintaksės
vienetus
– Vaizduojami nelygybių skliaustuose
– gramatikos taisyklės apibrėžia kaip jie yra
išskleidžiami į tokenų literalus.
55
BNF gramatikos apibrėžimas (2)
• Satrtinis simbolis yra išskirtinis neterminalas
sudarantis atitnkamos gramatikos parse tree šaknį.
• Produkcijos yra medžio konstravimo taisyklės.
• Kiekviena turi kairiąją pusę, skirtuką (separator)
::= ir dešiniąją pusę.
– Kairioji pusė yra vienas neterminalas
– Dešiniąją pusę gali sudaryti seka kurioje gali
būti kaip terminalai, taip ir neterminalai
– Dešinės pusės narių seka nusako kokia tvarka
turi būti konstruojamas parse tree.
56
Hierarchinės programos struktūros
išreiškimo rekursinėmis taisyklėmis
pavyzdys
• Kiekvienas identifikatorius (identifier) yra išraiška
(expression).
• Kiekvienas skaičius (number) yra išraiška
(expression).
• Jei expression1 ir expression2 yra išraiškos, tai
išraiškomis yra ir
expression1 + expression2
expression1 * expression2
(expression1).
57
Programavimo Kalbos Gramatika
<exp> ::= <exp> + <exp> | <exp> * <exp> | ( <exp> )
|a|b|c
• Išaiška gali būti (arba)
– dviejų išraiškų suma
– dviejų išraiškų sandauga
– apskliausta išraiška
– vienu iš kintamųjų a, b arba c
58
Pavyzdys
<exp> ::= <exp> + <exp> | <exp> * <exp> | ( <exp> )
|a|b|c
Šioje gramatikoje ištiesų yra šešios produkcijos.
T.y. viena ši produkcija yra ekvivalenti šioms šešioms:
<exp> ::= <exp> + <exp>
<exp> ::= <exp> * <exp>
<exp> ::= ( <exp> )
<exp> ::= a
<exp> ::= b
<exp> ::= c
59
BNF Realiems skaičiams
<real-number> ::= <integer-part> . <fraction>
<integer-part> ::= <digit> | <integer-part> <digit>
<fraction> ::= <digit> | <digit> <fraction>
<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Realaus skaičiaus 21.89 išvedimas: rašome išraiškų seką kuriose
neterminalai žingsnis po žingsnio keičiami keičiami terminalais;
real-number 
integer-part . fraction 
integer-part digit . fraction 
digit digit . fraction 
2 digit . fraction 
2 1 . fraction 
2 1 . digit fraction 
2 1 . 8 fraction 
2 1 . 8 digit 
21.89
60
Empty
• Specialus neterminalas <empty> naudojamas kai
numanoma, kad gramatika toliau nebeturi nieko
daugiau generuoti.
• Šio neterminalo panaudojimo pavyzdys: if-then
konstrukcija su galima else dalimi:
<if-stmt> ::= if <expr> then <stmt> <else-part>
<else-part> ::= else <stmt> | <empty>
61
Sukonstruojame gramatiką
Java primityvių tipų deklaracijoms
float a;
boolean a,b,c;
int a=1, b, c=1+2;
<var-dec> ::= <type-name> <declarator-list> ;
<type-name> ::= boolean | byte | short | int
| long | char | float | double
<declarator-list> ::= <declarator>
| <declarator> , <declarator-list>
<declarator> ::= <variable-name>
| <variable-name> = <expr>
62
EBNF
• papildoma sintaksė supaprastinanti produkcijų
žymėjimą:
– {x} reiškia nulį ar daugiau x pasikartojimų
– [x] reiškia, kad x yra papildomas (t.y. x |
<empty>)
– () naudojamas grupavimui
– | reiškia pasirinkimą tarp alternatyvų
– Šie meta simboliai kabutėse reiškia atitinkamus
tokenus
63
EBNF pavyzdžiai
<if-stmt> ::= if <expr> then <stmt> [else <stmt>]
<stmt-list> ::= {<stmt> ;}
<thing-list> ::= { (<stmt> | <declaration>) ;}
64
Sintaksinės analizės medis
(Parse Tree)
• Kodėl reikalingas sintaksinės analizės medis?
– Sintaksinio medžio struktūra išreiškia jo
generuojamos išraiškos semantiką.
• Konstruojant parse tree pradinis simbolis
talpinamas medžio šaknyje
• Pridedant neterminalams vaikų mazgus sekama tam
neterminalui skirtomis gramatikos konstrukcijomis
• Medis baigtas, kai visi jo lapai yra tokenai
• Medžio lapai skaitomi iš kairės į dešinę – tai sakinys
kuriam sukonstruotas medis
65
Pavyzdys
<exp>
((a+b)*c)
( <exp> )
<exp> * <exp>
( <exp> )
c
<exp> + <exp>
a
b
66
Sintaksinės analizės medis
Sintaksinis analizatorius (parser) programai kuria tam
tikrą sintaksinę struktūrą – sintaksinį medį (parse tree).
• Sintaksiniame medyje visi
position = initial + rate + 60 terminalai yra lapuose.
• Kontekstiškai laisvoje gramatikoje
assignment operator
(context free grammar) visi vidiniai
mazgai yra neterminalai.
identifier
expression
=
position expression
+ expression
identifier expression + expression
initial
identifier
rate
number
60
Sintaksiniame medyje
yra daug nereikalingos
informacijos
67
Pavyzdys: gramatika generuoja du medžius
<exp> ::= <exp> + <exp> | <mulexp>
<mulexp> ::= <mulexp> * <mulexp>
| (<exp>) | a | b | c
<exp>
<exp>
+
<exp>
a+b+c
<exp>
<exp>
+
<mulexp>
<exp>
a
<mulexp>
<mulexp>
<mulexp>
<mulexp>
b
c
a
b
+
<exp>
<exp>
+
<exp>
<exp>
<mulexp>
c
Deja pirmasis medis neatitinka įprasto sudėties
operatoriaus asociatyvumo
68
Asociatyvumo problema ištaisoma modifikuojant
gramatiką taip, kad sudėčiai medis augtų kairėn ir
žemyn
<exp> ::= <exp> + <mulexp> | <mulexp>
<mulexp> ::= <mulexp> * <rootexp> | <rootexp>
<rootexp> ::= (<exp>)| a | b | c
<exp>
Ši gramatika
išraiškai
<exp>
+
<mulexp>
a+b+c
<rootexp>
generuoja
<exp>
+ <mulexp>
teisingą
c
<mulexp>
<rootexp>
medį
<rootexp>
a
b
69
Abstraktus sintaksinis medis
• Programavimo kalbos paprastai saugo supaprastintą
sintaksinio medžio versiją vadinamą abstrakčiu
sintaksiniu medžiu (abstract syntax tree).
• Tokiame medyje kiekvienam operatoriui skiriamas
atskiras mazgas, o kiekvienam operandui skiriamas
submedis.
70
<exp>
<exp>
<exp>
<mulexp>
<rootexp>
+
+
<mulexp>
sintaksinis medis
<mulexp>
<rootexp>
<rootexp>
c
b
+
a
+
c
abstraktus sintaksinis medis
a
b
71
JavaCUP (Construct Useful Parser)
YACC analogas
• JavaCUP tai Java kalba parašytas generatorius
(Bottom up Parser) sukuriantis Java kalba parašytą
sintaksinį analizatorių.
JavaCUP Specifikacija
(cup failas)
JavaCUP
Sintaksinis
analizatorius
72
CUP aprašymo struktūra
1.
2.
3.
4.
5.
package ir import specifikacija (papildoma)
Vartotojo kodo komponentai (papildomi, leidžia
vartotojui deklaruoti kodą, kuris bus įtrauktas
į sugeneruotą sintaksinį analizatorių)
Simbolių sąrašai (terminalai ir neterminalai)
Prioritetų deklaracijos (papildoma)
Gramatika
73
package ir import specifikacija
• Specifikacija prasideda papildomomis package ir
import deklaracijomis.
• Deklaracijos turi tą pačią sintaksę ir vaidina tą
patį vaidmenį kaip ir Java kalboje.
• Deklaracija package nurodo kuriame pakete (ir
kataloge) bus patalpintos CUP sukurtos sym ir
parser klasės.
• Deklaracijoje import nurodyti paketai bus
deklaruoti parser klasėje, tuo pačiu tais paketais
galės naudotis CUP specifikacijoje patalpintas
vartotojo Java kodas.
74
Vartotojo kodo komponentai (1)
• action code{: ... :} Leidžia įtraukti vartotojo kodą
į CUP$actions klasę ir kuris savo ruožtu bus
naudojamas gramatikoje esančiu kodu.
– Ši dalis dažniausiai skiriama darbui su simbolių
lentele.
• parser code{: ... :} Ši dalis panaši į action code,
tačiau kodas bus įtrauktas į klasę parser, todėl
juo galės naudotis šios klasės metodai
(pavyzdžiui, scan()).
– Ši dalis dažnai skiriama standartinei įvesčiai
aptarnauti.
75
parser code{: ... :} pavyzdyje minimal.cup
parser code {:
public static void main(String args[]) throws Exception {
new parser(new Yylex(System.in)).parse();
}
:}
76
Vartotojo kodo komponentai (2)
• init with{: ... :} Šioje dalyje pateikiamas kodas,
kuris bus atliekamas pats pirmas, dar prieš
analizatoriui užklausiant pirmojo tokeno.
– Paprastai naudojama analizatoriui, įvairioms
lentelėms ir struktūroms inicijuoti
• scan with{: ... :} Nurodo kaip sintaksinis
analizatorius turi užklausti leksinį analizatorių
sekančio tokeno.
– Šioje dalyje esantis kodas grąžinimui turi
naudoti tipą java_cup.runtime.Symbol
77
Simbolių sąrašai
• Šioje dalyje deklaruojamas kiekvienas naudojamos
gramatikos terminalas ir neterminalas.
terminal name1, name2, ...;
terminal classname name1, name2, ...;
non terminal name1, name2, ...;
non terminal classname name1, name2, ...;
• Klasės naudojamos terminalų deklaracijose turi
būti klasės java_cup.runtime.token poklasės.
• Klasės naudojamos neterminalų deklaracijose turi
būti klasės java_cup.runtime.symbol poklasės.
78
Terminalų ir neterminalų vardams negalima
naudoti CUP rezervuotus žodžius:
"code", "action", "parser", "terminal", "non",
"nonterminal", "init", "scan", "with", "start",
"precedence", "left", "right", "nonassoc",
"import", "package"
79
Prioritetų deklaracijos (1)
• Yra trys prioriteto/asociatyvumo deklaracijų tipai:
precedence left terminal[, terminal...];
precedence right terminal[, terminal...];
precedence nonassoc terminal[, terminal...];
• Deklaracijų sąraše prioritetai auga iš viršaus į
apačią
• Pavyzdžiui, jei nebus prioritetų, analizatorius
nežinos kuria tvarka apskaičiuoti išraišką 3 + 4 * 8
• Jei terminalo nėra prioritetų deklaracijų sąraše, jo
prioritetas laikomas mažiausiu.
80
Prioritetų deklaracijos (2)
• Produkcijos prioritetas yra laikomas lygiu
žemiausio jos terminalų prioritetui.
• Jei produkcija neturi terminalų, jos prioritetas yra
žemiausias.
• Pavyzdžiui, produkcijos
expr ::= expr TIMES expr
prioritetas lygus TIMES prioritetui
• Jei produkcijų prioritetai lygūs, toliau išraiškų
vertinimo tvarką nustato terminalų asociatyvumas.
81
Prioritetų deklaracijos (3)
• Yra trys asociatyvumo tipai: left, right ir nonassoc.
• Pavyzdžiui, jei PLUS asociatyvumas nustatytas left,
tuomet 3 + 4 + 5 + 6 + 7 bus vertinama iš kairės į
dešinę (pradedant 3 + 4); jei PLUS asociatyvumas
nustatytas right bus vertinama iš dešinės į kairę
(pradedant 6 + 7)
• Jei terminalas deklaruotas kaip nonassoc, tuomet
sekantys vienas po kito jo taikymai su vienodu
prioritetu bus interpretuojami kaip klaida.
• Pavyzdžiui, jei == asociatyvumas nustatytas
nonassoc, tuomet 6 == 7 == 8 == 9 reikš klaidą.
82
Gramatikos produkcijos (1)
non_terminal symbol::= { actions, terminal symbols,
non_terminal symbols } ;
• actions įterpiamos tarp skirtukų {: ... :}
– action vykdoma tuomet, kai analizatorius
atpažįsta jai atitinkančią produkcijos dalį.
• Kaip terminalai, taip ir neterminalai sąraše gali būti
pažymėti vardu atskirtu dvitaškiu.
– Šis vardas atstovauja kode jo pažymėtą objektą.
83
Gramatikos produkcijos (2)
• Jei neterminalas turi keletą produkcijų, jos
deklaruojamos kartu ir atskiriamos iš dešinės (|)
simboliu.
• Galima nurodyti analizatoriui kuriuo neterminalu jis
turi pradėti darbą:
start with non_terminal;
– Jei tokios deklaracijos nėra, analizatorius
pradeda darbą pirmąja produkcija.
84
Aritmetinių išraiškų su sveikais skaičiais
interpretatorius
• Interpretatorius turi nuskaityti aritmetines
išraiškas (besibaigiančias kabliataškiu) iš
standartinės įvesties (standard input).
• Interpretatorius turi apskaičiuoti išraiškas ir
resultatą išvesti į standartinę išvestį (standard
output).
85
Aritmetinių išraiškų su sveikais skaičiais
gramatika
expr_list
::= expr_list expr_part | expr_part
expr_part
::= expr ';'
expr
::= expr '+' expr | expr '-' expr | expr '*' expr
| expr '/' expr | expr '%' expr | '(' expr ')'
| '-' expr | number
86
Terminalų ir neterminalų deklaracija
• Pirmasis žingsnis aprašant sintaksinį analizatorių
(kuriant jo specifikacijų failą) yra deklaruoti
naudojamus terminalus ir neterminalus.
• Neterminalai:
– expr_list, expr_part ir expr
• Terminalai:
– SEMI, PLUS, MINUS, TIMES, DIVIDE, MOD,
NUMBER, LPAREN, ir RPAREN
• Šiuos terminalų vardus mes pasirinkome
patys JLex specifikacijų faile.
– Jei terminalui nenurodome tipo, tai jis negali
įgyti reikšmės.
87
Terminalų ir neterminalų deklaracija
pavyzdyje minimal.cup
• terminal SEMI, PLUS, TIMES, LPAREN, RPAREN;
• terminal Integer NUMBER;
• non terminal expr_list, expr_part;
• non terminal Integer expr;
88
Operatorių prioritetai ir asociatyvumas
• Mūsų naudojama gramatika nėra vienareikšmė.
• Gramatika taps vienareikšme nustačius operatorių
prioritetus ir asociatyvumo taisykles.
• Pavyzdžiui:
precedence left PLUS, MINUS;
precedence left TIMES, DIVIDE, MOD;
precedence left UMINUS;
• Sakinių prioritetai auga iš viršaus į apačią.
89
Leksinio analizatoriaus kuriamos klasės
• sym saugo terminalams skirtas konstantas
• parser klasė realizuoja analizatorių
– ji yra java_cup.runtime.lr_parser poklasė
• CUP$action klasė skirta kaip gramatikoje
nurodyto vartotojo kodo veikoms, taip ir veikų
kodui deklaracijose.
90
Inerpretatoriaus kūrimas
• Apskaičiuoti išraiškas ir išvesti rezultatą galima tik
su atitinkamu Java kodu.
• Šis Java kodas turi būti atitinkamose vietose
patalpintas tarp žymių {: ir :}
expr:l PLUS expr:r
{: RESULT=new Integer(l.intValue() + r.intValue()); :}
– Pirmasis neterminalas pažymėtas l, o antrasis r.
• Kiekvienos produkcijos kairioji pusė visuomet
žymima identifikatoriumi RESULT
– ši reikšmė (RESULT) priskiriama analizatoriaus
grąžinamam objektui Symbol.
91
minimal.cup
package Example;
import java_cup.runtime.*;
parser code {:
public static void main(String args[]) throws Exception {
new parser(new Yylex(System.in)).parse();
}
:}
terminal SEMI, PLUS, TIMES, LPAREN, RPAREN;
terminal Integer NUMBER;
non terminal expr_list, expr_part;
non terminal Integer expr;
precedence left PLUS;
precedence left TIMES;
expr_list ::= expr_list expr_part | expr_part;
expr_part ::= expr:e {: System.out.println(" = "+e+";"); :} SEMI;
expr ::= NUMBER:n {: RESULT=n; :}
| expr:l PLUS expr:r
{: RESULT=new Integer(l.intValue() + r.intValue()); :}
| expr:l TIMES expr:r
{: RESULT=new Integer(l.intValue() * r.intValue()); :}
| LPAREN expr:e RPAREN {: RESULT=e; :}
;
92
minimal.lex
package Example;
import java_cup.runtime.Symbol;
%%
%cup
%%
";" { return new Symbol(sym.SEMI); }
"+" { return new Symbol(sym.PLUS); }
"*" { return new Symbol(sym.TIMES); }
"(" { return new Symbol(sym.LPAREN); }
")" { return new Symbol(sym.RPAREN); }
[0-9]+ { return new Symbol(sym.NUMBER, new Integer(yytext())); }
[ \t\r\n\f] { /* ignore white space. */ }
. { System.err.println("Illegal character: "+yytext()); }
93