Pisi-Algoli leksikaanalüsaator

Eespoolvaadeldud näidete põhjal on lihtne "kokku panna" kogu programmeerimiskeele Pisi-Algol leksikaanalüsaatorit (skannerit). Ka Pisi-Algoli leksikaanalüsaator saadakse keelte süntaksi kirjelduse põhjal koostatud tunnistavale programmile väljundtegevuste lisamisega.

Leksikaanalüsaatori (skanneri) töö tulemuseks on kaks andmestruktuuri: lähtetekstile vastav lekseemide jada ja nn nimede tabel, kus säilitatakse info programmi muutujate (identifikaatorite) kohta. Väljundisse (lekseemideks) ei tule enam paljud eraldajatest (tühikud, komad, semikoolonid jne), sest need on oma töö teinud (eraldanud üksteisest muid lekseeme); eraldajatest tulevad lekseemideks vaid need, millel on programmis sisuline tähendus - operatsioonimärgid, keele võtmesõnad, aritmeetilistes avaldistes esinevad sulud.

Kõik lekseemid on (siin) kolmeväljalised kirjed (record).

Lekseemi esimene väli, klass, kirjeldab, millisesse lekseemiklassi vastav lekseem kuulub; kõigi identifikaatorite klass on IDENT (s.t. ühesugune kõigil identifikaatoritel, sest transleerimise järgneval etapil, süntaksianalüüsil, identifikaatori kuju ei ole oluline); arvkonstantide (arvude) klass on ARV (ka kõigi arvude klass on sama), operatsioonimärkide ja keele võtmesõnade klass on see märk või võtmesõna.

Lekseemi teine väli, spetsifikatsioon, on identifikaatorite korral viit nimede tablisse SYMTAB, kus säilitatakse identifikaatori tüüpi, nimekuju (alpha) ja viit mäluväljadele, kus salvestatakse identifikaatori väärtus. Arvkonstantide spetsifikatsioon on nende väärtus ja operatsioonimärkide ja võtmesõnade spetsifikatsioon on tühi.

Lekseemi kolmas väli on selle positsioon lähtetekstis; protseduur READ salvestab selle iga uue lähteteksti sümboli lugemisel globaalses muutujas POSITION.

Skanneri loogika (struktuuri) määrab eespool esitatud leksika kirjeldus, millele on lisatud väljundtegevused OutArv, OutWord (see omakorda käivitab protseduurid OutIdent, OutKeyword) ja OutOperator. Kogu skanneri struktuuri kirjeldab produktsioon :

PROGRAMM (ERALDAJA | SÕNA OutWord ERALDAJA | ARV OutArv ERALDAJA )*

Programmi jaoks tuleb defineerida lisaks eespool vaadeldud leksika tähestikku kirjeldavatele andmetüüpidele veel üks, kus on loetletud keele võtmesõnad. Kuna siin ei lubata võtmesõnade kasutamist identifikaatoritena, siis võib neid nimetada ka keele reserveeritud sõnadeks.

type SYMBOL is ( 'A','B',...,'Z','0','1',...,'9','+','-','*','/','=','>','<','(',')',';',' ');
subtype TÄHT is SYMBOL range 'A'..'Z';
subtype NUMBER is SYMBOL range '0'..'9';
subtype MÄRK is SYMBOL range +..' ';
type KEYWORD is (IF, THEN, ENDIF, FOR, WHILE, DO, ENDLOOP, PRINT, READ);

Skanneri loogika (struktuuri) määrab leksika kirjeldus, millele on lisatud väljundtegevused OutArv, OutWord (see omakorda käivitab protseduurid OutIdent, OutKeyword) ja OutOperator:

PROGRAMM (ERALDAJA | SÕNA OutWord ERALDAJA | ARV OutArv ERALDAJA )*

procedure SCANNER(SISEND: in FILE) is
begin READ; -- CH väärtuseks saab sisendi esimene sümbol while CH in SYMBOL loop -- PROGRAMM (ERALDAJA | IDENT ERALDAJA | CONST ERALDAJA )*
case CH is when TÄHT SÕNA; ERALDAJA; when NUMBER ARV; ERALDAJA; when MÄRK ERALDAJA; end case; end loop;
if CH='#' then ACCEPT else RECORD_ERROR(0,POSITION) -- # - sisendteksti lõpusümbol
-- veakood 0: lubamatu sümbol
end SCAN;

Protseduurid SÕNA ja ARV on samasugused kui eespool esitatud:

procedure SÕNA is
begin FirstChar; while CH in TÄHT or CH in NUMBER loop READ; NextChar; end loop; end while; OutWord; end WORD;

Protseduur FirstChar- annab string-tüüpi muutujale WORD algväärtuse:

procedure FirstChar
(WORD: in out STRING (CH: in CHARACTER) WORD = CH; end FirstChar;

Protseduur NextChar lisab loetud tähe muutuja WORD lõppu; ühtlasi saab siin ka kontrollida lisatingimusi, näiteks paljudes programmeerimiskeeltes on identifikaatorite (ka võtmesõnade) pikkus piiratud:

procedure NextChar
(WORD: in out STRING; CH: in CHARACTER) if length(WORD) = 8 then RECORD_ERROR(3, POSITION) else WORD = WORD & CH; end if; end NextChar;

Protseduur OutWord käivitatakse pärast programmitekstis leitud sõna lugemist. See kontrollib algul funktsiooni LookUpKeyWord abil, kas leitud sõna on võtmesõna. Kui on, käivitatakse protseduur OutKeyword, mis lisab vastava lekseemi väljundfaili OUTFILE. Kui loetud sõna ei ole võtmesõna, peab see olema identifikaator. Identikaatori puhul kontrollib funktsioon LookUpNameTab, kas see identifikaator on juba kantud nimede tabelisse; kui ei ole, kannab identifikaatori nimede tabelisse. Funktsioon väljastab viida nimede tabeli reale, kus identifikaator on kirjeldatud; selle viida abil moodustab protseduur OutIdent identifikaatori jaoks lekseemi ja lisab selle väljundfaili.

procedure OutWord
(Word: in STRING; Pos: in TEXTPOSITION) is begin if LookUpKeyWord(Word) then OutKeyWord(Word); else OutIdent(LookUpNameTab(Word)); end if; end OutWord;

Funktsioon LookUpKeyWord kontrollib, kas sisendtekstis leitud sõna kuulub lähtekeele reserveeritud sõnade hulka:

function LookUpKeyWord
(Word: in STRING) return BOOLEAN is begin if Word in KEYWORD return TRUE else return FALSE end if; end LookUpKeyWord;

Protseduur OutKeyWord(Word, Pos) kirjutab väljundfaili LEXOUT lekseemi, mille klass on Word (võtmesõnade klass on see võtmesõna ise), spetsifikatsioon puudub ja positsioon võetakse globaalmuutujast POSITION.

Funktsioon LookUpSymTab kontrollib, kas sisendtekstis leitud identifikaator on juba kantud nimede tabelisse ja kui on, annab vastava reanumbri; kui ei ole, lisab tabelisse uue rea; maxc on praegune ridade arv tabelis; tabeli lubatud (maksimaalne) ridade arv on maxsymbtab, kui maxc > maxsymbtab, tuleb veateade.

function LookUpSymTab
(Word: in STRING; maxc: in out INTEGER) return ADR: INTEGER is begin step i from 1 to maxc if SYMTAB[i].alpha = Word -- s.t. see identifikaator on juba esinenud ja tabelis!
then return i;exit; end if; end step; if maxc + 1 > maxsymtab then ERRORSYMTAB; return 0; exit; end if maxc = maxc+1; SYMTAB[maxc].alpha= Word; -- tabelisse lisatakse uus identifikaator!
return maxc; end LOOKUPSYMTAB

Protseduur OutIdent kirjutab väljundfaili LEXOUT lekseemi, mille klass on IDENT, spetsifikatsioon on funktsiooni LookUpSymTab poolt antud viit nimede tabelisse ja positsioon saadakse globaalmuutujast POSITION.

Ka sisendtekstis esinevate arvude käsitlemine on samasugune kui varem:

procedure ARV is
begin FirstDigit(CH,CONST); while CH in DIGIT loop READ; NextDigit(CH,CONST); end loop; end while; OutConst(CONST); end NUMBER;

Protseduur FirstDigit initsialiseerib täisarvu-tüüpi muutuja CONST, kuhu salvestatakse kogu loetud arvu väärtus; ORD on tüübiteisendus, mis teisendab märgi (character) vastavaks numbriks :

procedure FirstDigit
(CH: in CHARACTER CONST: in out INTEGER) is DIGIT=ORD(CH); CONST = DIGIT; end FirstDigit

Protseduur NextDigit lisab loetud numbri muutuja CONST väärtusele:

procedure NextDigit
(CH: in CHARACTER CONST: in out INTEGER) is DIGIT=ORD(CH); CONST = 10 * CONST + DIGIT; end FirstDigit

Protseduur OutConst(CONST) kirjutab väljundisse lekseemi, mille klass on ARV, spetsifikatsioon on CONST (arvu väärtus) ja positsioon saadakse globaalmuutujast POSITION.

Protseduuris ERALDAJA kasutatav alamprotseduur OutSeparator kirjutab iga eraldaja jaoks väljudfaili lekseemi, mille klass on see eraldaja, spetsifikatsioon on tühi ja positsioon saadakse globaalmuutujast POSITION.

procedure ERALDAJA is
begin case CH is when '+'|'-'|'*'|'/'|'='|'>'|'<'|';'|'('|')' OutSeparator(CH); READ; when ':' READ; if CH = '=' then OutSeparator(':='); READ; else RECORD_ERROR(1,POSITION); -- veakood 1: oodati sümbolit '='
end if; end case; end ERALDAJA;


Ülesandeid:
1. Mis muutub ülalesitatud skanneri skeemis, kui skaneeritavat keelt täiendada tsüklikonstruktsioonidega FOR omistamine UNTIL avaldis DO programm ENDFOR ja DO programm UNTIL tingimus ENDDO ?

Küsimused, probleemid: ©2004-2013 Jaak Henno