package prekladac; import java.io.Reader; import java.util.Scanner; //import vlasních souborů import static prekladac.LexSymbol.*; //deklarace klíčových slov - lexikálních symbolů - statická - sdílená celým balíkem import prekladac.Pozice.*; /** * Implementace lexikálního analyzátoru * @author Jan Dvořák */ public class Lexer { //načítání vstupu, pozice private Scanner scn; //stream, ze kterého čteme private String vstupniRadek; //řádek streamu private int cisloSloupce; //Pozice x vzhledem ke vstupu private int cisloRadku=0; // y private int znak; //aktuálně načtený znak ze vstupu private int zacatekSlova =0;//x pozice začátku aktuálně načteného slova private int minSlovoSloupec =0;//pozice předešlého slova (x) - použije se při kontrolování syntaxe private int minSlovoRadek =0;// (y) //identifikátor, čísla private String slovo=""; //jednodušší než pole charu, nemusíme si pamatovat poslední pozici - string postupně rozšiřujeme o nové nebílé znaky -> čísla, ident, kl. slova,... private Number cislo; //tuto třídu jsem použil, protože potřebuji vracet jak INT tak DOUBLE, a ty mají společnou nadtřídu Number private int nacitameString=0; //0 - nic, 1 - provádíme načítání, 2 - ukončení načítání . čekání na uvozovku /*******************************************************************************************/ /** * konstruktoru předáváme jako vstup STREAM, to je vhodne pro provadeni testů - můžeme stream napojit jak na soubor, klávesnici, či jiný stream */ public Lexer(Reader r) throws Exception { //vytvoříme stream dat, načteme první řádek vstupu (první symbol prvního řádku) try { scn=new Scanner(r); }catch(Exception exp){ System.err.println("Nepodarilo se otevrit vstupni soubor"); throw exp; } // znak=nactiDalsiZnak(); } /*******************************************************************************************/ /** * Tato funkce vrátí další lexikální symbol */ public LexSymbol vratDalsiLexikalniSymbol() throws Exception { //uchováme starou pozici minSlovoRadek=cisloRadku; minSlovoSloupec=zacatekSlova; //pokud je symbol bílým znakem, nalezneme první nebílý znak while( jeBilyZnak(znak) ){znak=nactiDalsiZnak();} //konec vstupu if(znak==-1)return EOF; //EOF - konec vstupu //přeskočení komenáře if(znak=='\'') { while( !(znak=='\n') ){znak=nactiDalsiZnak();} //nalezneme zalomení - dodávám ho na konec každého řádku return vratDalsiLexikalniSymbol(); //zavoláme kurzivně sami na sebe, aby sme mohli opět přeskočit bílé znaky, komentáře a pod. } //nastavíme počáteční pozici nového slova, slovo vynulujeme zacatekSlova=cisloSloupce; slovo=""; //provedeme samotné vyhodnocení //to provedeme ve dvou kategoriích CISELNE a NECISELNE vyrazy. Ty poznámje podle začátku: číslo + desetinná tečka || písmeno //string if(nacitameString==1){ //čteme dokud nenajdeme uvozovku, nebo konec řádku - chyba while(znak!='\"' && znak!='\n') { slovo+=(char)znak; znak=nactiDalsiZnak(); } nacitameString=2; if(znak=='\"')return STROBSAH; //OK napisChybu("textovy retezec musi byt zakoncen znakem <\">"); //čísla }else if( jeCislo(znak) ){ //pokusíme se načíst číslo int pocetDesMist=0; while( jeCislo(znak) ) { if(znak=='.')pocetDesMist++; slovo+=(char)znak; znak=nactiDalsiZnak(); } //další znak musí být bílým znakem, nebo speciální if( !jeBilyZnak(znak) && !jeSpecialni(znak) && znak!='\'' ) { napisChybu("indetifikator musi zacinat pismenem (<"+slovo+(char)znak+">)"); } //vyhodnocení - double nebo int if(pocetDesMist==0) { //int try { cislo=Integer.valueOf(slovo); return INTEGER_CONST; }catch(Exception e){napisChybu("cele cislo <"+slovo+"> se nepodarilo prevest - pravdepodobne z duvodu prekroceni rozsahu");} }else if(pocetDesMist==1){ //double try { cislo=Double.valueOf(slovo); return DOUBLE_CONST; }catch(Exception e){napisChybu("realne cislo <"+slovo+"> se nepodarilo prevest - pravdepodobne z duvodu prekroceni rozsahu");} }else{ //chybně zadané číslo napisChybu("vyraz <"+slovo+"> neni platnou ciselnou konstantou"); } // } //identifikátor a klíčová slova : začínající písmenem, končící písmenem nebo číslicí if( jePismeno(znak) ) { //klíčové slovo nebo identifikátor slovo+=(char)znak; znak=nactiDalsiZnak(); while( jeCislo(znak) || jePismeno(znak) ) { slovo+=(char)znak; znak=nactiDalsiZnak(); } //nyní vyhodnotíme načtený výraz return vyhodnotLexSymbol(slovo); } //ostatní - porování, početní operace, ... if( jeSpecialni(znak) ) { slovo+=(char)znak; znak=nactiDalsiZnak(); //musíme odlišit dvouznakové a jednoznakové skupiny, dvojznakové skupiny končí všehny znakem '=' if(znak=='=') { //pokusíme se vyhledat tento dvojznak LexSymbol ls=vyhodnotLexSymbol(slovo+'='); if(ls!=IDENT) { //načteme další znak, vrátíme dvojici znak=nactiDalsiZnak(); return ls; } } //nyní vyhodnotíme načtený výraz LexSymbol ls=vyhodnotLexSymbol(slovo); if(ls !=IDENT ) { //jedná se o definované symboly if(ls==UVOZOVKY) { //začátek / konec načítání stringu if(nacitameString==0)nacitameString=1; else if(nacitameString==2)nacitameString=0; } return ls; }else{ //zadaná sekvence nebyla nalezena v definovaných symbolech napisChybu("sekvence znaku <"+slovo+"> neni platna"); } // } napisChybu("byl nalezen neznamy symbol <"+(char)znak+">"); return null; } /*******************************************************************************************/ /** * tato funkce vrací hodnotu aktuálního identifikátoru */ public String vratIdentifikator() { return slovo; } /*******************************************************************************************/ /** * tato funkce vrací hodnotu aktuálního čísla (int nebo double) */ public Number vratHodnotu() { return cislo; } /*******************************************************************************************/ /** * sada funkcí pro určení polohy */ public Pozice vratPolohuPocatkuSymbolu() {return new Pozice(zacatekSlova,cisloRadku);} //vrátí polohu počátku aktuálního kl. slova [x,y] public Pozice vratPolohuPoslednihoSymbolu(){return new Pozice(minSlovoSloupec,minSlovoRadek);} //vrátí polohu konce předešlého kl. slova [x,y] public Pozice vratCisloRadku() {return new Pozice(0,minSlovoRadek);} /*******************************************************************************************/ /** * Funkce vrací další znak vstupního řádku */ private int nactiDalsiZnak() { //strategie: vrátíme další symbol z aktuálního řádku, pokud není načten, nebo již byl celý přečten, načteme nový int x=-1; //návratová hodota (=konec vstupu) if(vstupniRadek!=null && cisloSloupce='0' && x<='9') || x=='.' ) return true; else return false; } private boolean jePismeno(int x) { if( (x>='a' && x<='z') || (x>='A' && x<='Z') ) return true; else return false; } private boolean jeSpecialni(int x) { if(x=='<' || x=='>' || x=='=' || x=='!' || x=='+' || x=='-' || x=='*' || x=='/' || x=='\\' || x=='%' || x=='(' || x==')' || x==';' || x==',' || x==':' || x=='\"') return true; else return false; } /*******************************************************************************************/ }