Nimede tabel

Lekseemide leidmise kõrval peab skanner moodustama ka nn nimede tabeli (nametable), mis kirjeldab kõiki transleeritava (skaneeritava) programmi identifikaatoreid: milline on muutuja tüüp (range tüübikontrolliga keeltes), milline on muutuja praegune väärtus (või viit mäluosale, kus muutuja on salvestatud); kui transleeritavas keeles saab deklareerida konstante ja kasutatada funktsioone, siis kantakse ka need tavaliselt tabelisse, sest nende käsitlemine (näiteks) aritmeetilises avaldises sarnaneb identifikaatorite käsitlemisega; tavaliselt kasutatakse nende kõigi jaoks terminit (mitteterminali) nimi (name). Skaneerimisel genereeritakse kõigi identifikaatorite jaoks ühesugune lekseem (IDENT), vaid selle lekseemi spetsifikatsioonis olev viit nimede tabelisse võimaldab hiljem leida identifikaatori tüübi ja viida mäluosale, kus salvestatakse identifikaatori väärtus.

Nimede tabeli andmestruktuuri võib olla fikseeritud pikkusega tabel või viitadega nimistu. Tabeli kasutamisel on programmis esineda võivate muutujate arv määratud tabeli pikkusega. Viitadega nimistu kasutamisel seda kitsendust pole, kuid tabeli kasutamisega tutvumiseks on tabel (array) lihtsam, samuti on kiirem tablisse identifikaatorite lisamine ja otsimine (viitadega nimede tabeli realiseerimist on kirjeldatud peatükis "Mäluga kalkulaator").

Järgnevas on Flex-i sisendfail, mis kirjeldab ka Flex-i abil programmis esinevate identifikaatorite kogumist vektoriks (char array); siin ei genereerita veel mingit väljundit (skaneerimise tulemusena saadavat lekseemide jada), kuid arvud (täis- ja reaalarvud) muudetakse juba arvudeks (lähtetekstis on nad sümbolite jadad) ja kahesümboliline omistusmärk := asendatakse ühe lekseemiga, kusjuures ':', '=' võivad esineda ka tavaliste eraldajatena.

Identifikaatori leidmisel peab skanner kõigepealt kontrollima, kas see identifikaator juba esineb nimede tabelis. Kui esineb, peab kontroll väljastama viida tabeli reale, kus identifikaator on kirjeldatud (siin kirjeldatakse tabelis vaid identifikaatori nimi); kui ei, lisama identifikaatori tabelisse. Allpoolesitatud tekstis näitab mõlemal juhul muutuja Index, millisel tabeli real muutuja on kirjeldatud. Kuna identifikaatori esinemise kontrollis kasutatakse C-keele stringifunktsioni strcomp, tuleb programmi algul include-lausega lisada ka teek string.h, kus see funktsioon on kirjeldatud. Funktsiooni strcomp kasutamisel peab meeles pidama, et see kontrollib mitte-võrdumist (tulemus on tõene, kui võrreldavad stringid on erinevad), seega stringid on võrdsused, kuid eitus !strcomp on tõene.

%option noyywrap
%{
#include <math.h>
#include <string.h>
#include <stdio.h>
int num;
int i,j,k,Index,on,n=0;
float realnum;
char c, nimed[50][10];
/* nametable; < 50 names are allowed in a program */
%}

DIGIT [0-9]
ID [A-Za-z][A-Za-z0-9]*
OMISTUS :=
ERALDAJA ["=" ";" "<" ">" ":" "(" ")" "{" "}"]
%%
{DIGIT}+ { num=atoi(yytext); printf( "Täisarv: %s (%d)\n", yytext, num ); }
{DIGIT}+"."{DIGIT}* { realnum=atof(yytext); printf( "Reaalarv: %s (%g)\n", yytext, realnum ); }
"."{DIGIT}+ { realnum=atof(yytext); printf( "Reaalarv: %s (%g)\n", yytext, realnum ); }
if|then|begin|end|procedure|function|main|else { printf( "Võtmesõna: %s\n", yytext ); }
{ID} {
/* kontrollime, kas on juba tabelis? */
on=0;
for (j=0;j<n;j++)
if (!(strcmp(nimed[j],yytext))) {on=1;Index = j;} if (on==0) /* ei leitud */
{Index = n; for (i=0;i < yyleng;i++ ) /* lisame tabelisse */ nimed[n][i] = yytext[i]; nimed[n][yyleng]=0x00; /* 0x00 - stringi terminaator! */
n++; }
printf( "Identifikaator: %s, tabeli indeks: %d\n", yytext,Index); };
{OMISTUS} printf("Omistamine: %s\n",yytext);
"+"|"-"|"*"|"/"|">"|"<"|"=" printf( "Operaator: %s\n", yytext );
"{"[^}\n]*"}" /* kustuta üherealised kommentaarid */
[ \t\n]+ /* kustuta tühikud */
{ERALDAJA} printf("Eraldaja: %s\n", yytext);
. printf( "Viga: %s\n", yytext );

%%

main( argc, argv )
int argc; char **argv; { ++argv, --argc; /* skip over program name */ if ( argc > 0 ) yyin = fopen( argv[0], "r" ); else yyin = stdin; yylex(); printf("\nIdentifikaatorieid: %d\n", n);
/* trükime tabeli */
for (i=0;i < n;i++)
{
printf("%d: ",i); k = 0;
c = nimed[i][k];
while (c != 0x00)
{printf ("%c", c); k++; c = nimed[i][k]; }; printf("\n");
}
}

Selle programmi testimisel failiga

if A1>B1 then C1:=0;
C2 := C1+1;
A2 := 2.4*A1;
B1 := .1;
A2 := B2-B1;

saame tulemuseks:

Võtmesõna: if
Identifikaator: A1, tabeli indeks: 0
Operaator: >
Identifikaator: B1, tabeli indeks: 1
Võtmesõna: then
Identifikaator: C1, tabeli indeks: 2
Omistamine: :=
Täisarv: 0 (0)
Eraldaja: ;
Identifikaator: C2, tabeli indeks: 3
Omistamine: :=
Identifikaator: C1, tabeli indeks: 2
Operaator: +
Täisarv: 1 (1)
Eraldaja: ;
Identifikaator: A2, tabeli indeks: 4
Omistamine: :=
Reaalarv: 2.4 (2.4)
Operaator: *
Identifikaator: A1, tabeli indeks: 0
Eraldaja: ;
Identifikaator: B1, tabeli indeks: 1
Omistamine: :=
Reaalarv: .1 (0.1)
Eraldaja: ;
Identifikaator: A2, tabeli indeks: 4
Omistamine: :=
Identifikaator: B2, tabeli indeks: 5
Operaator: -
Identifikaator: B1, tabeli indeks: 1
Eraldaja: ;
Identifikaatorieid: 6
0: A1
1: B1
2: C1
3: C2
4: A2
5: B2


Ülesandeid:
1. Täienda nimede tabelit nii, et iga identifikaatori jaoks seal oleks salvestatud ka selle esmakordse esinemise koht (rida, veerg) - range tüübikontrolliga keeltes määratakse seal identifikaatori tüüp.
2. Täienda lekseemi moodustamist nii, et lekseemid oleks kõik kolmeväljalised kirjed (vt "Pisi-Algoli leksikaanalüsaator"):
- esimene väli klass kirjeldab, millisesse lekseemiklassi vastav lekseem kuulub; kõigi identifikaatorite klass on IDENT, 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;
- teine väli, spetsifikatsioon, on identifikaatorite korral viit nimede tablisse SYMTAB, kus säilitatakse identifikaatori tüüpi, nimekuju 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;
- kolmas väli POS määrab lekseemi asukoha lähtetekstis, see on täisarvude paar RIDA, POSITSIOON
3. Programmeerimiskeele RPC (Remote Procedure Call) süntaks (lihtsustatud, vt http://publib.boulder.ibm.com/infocenter/systems/index.jsp?topic=/com.ibm.aix.progcomm/doc/progcomc/rpc_lang.htm) on kirjeldatud produktsioonidega:
program-definition:
"program" program-ident "{"
version-list
"}" "=" value

version-list:
version ";"
version ";" version-list

version:
"version" version-ident "{"
procedure-list
"}" "=" value

procedure-list:
procedure ";"
procedure ";" procedure-list

procedure:
type-ident procedure-ident "(" type-ident ")" "=" value

typedef-definition:
"typedef" declaration

const-definition:
"const" ident "=" integer
int-definition:
["unsigned"] "int" ident "=" integer

Tee skanner, mis teisendaks näidisprogrammi
program TIMEPROG {
version TIMEVERS {
unsigned int TIMEGET (void) = 1;
void TIMESET (unsigned) = 2;
} = 1;
} = 44;
väljundiks (define-lauseteks header-failis):
#define TIMEPROG 44

#define TIMEVERS 1

#define TIMEGET 1

#define TIMESET 2

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