Pisi-Algoli leksikaanalüsaator

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

Leksikaanalüsaatori (skanneri) töö tulemuseka on kaks andmestruktuuri: lähtetekstile vastav lekseemide jada ja nn nimede tabel, kus säilitatakse info programmi muutujate (identifikaatorite) kohta.

Kõik lekseemid on 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 enam oluline); arvkonstantide (arvude) klass on ARV (ka kõigi arvude klass on sama), operatsioonimärkide ja keele võtmesõnade klass - 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 selle 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 eesesitatud 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 )*

Programmi jaoks tuleb defineerida lisaks eespoolvaadeldud 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, UNTIL, DO, ENDLOOP, WRITE, READ);

Skanneri loogika (struktuuri) määrab eesesitatud 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- initsialiseerib string-tüüpi muutuja WORD:

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 eespool juba käsitletud:

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. Ylesande tekst

K simused, probleemid: ©2004 Jaak Henno