Sageli on soovitav/loomulik, et lekseemil oleks peale tavaliste, "klassikaliste" attribuutide (koht lähtetekstis, tüüp, võibolla viit nimede tabelisse) veel mingeid attribuute. Selline lekseem oleks näiteks - HTML-i <img> märgend - loomulik oleks, et kõik märgendi img attribuudid (src, width, height jne) tuleks ka skaneerimisel loodava lekseemi attribuutideks; sellised on ka XML-keele märgendite attribuudid.
Veel rohkem on attribuute SMIL-keele märgenditel; järgnevas vaatlemegi Flex-i abil attribuutidega lekseemide genereerimist SMIL-keele näite abil.
SMIL on keel WWW abil levitatavate multimeediaesituste kirjeldamiseks. Kui HTML on orienteeritud staatilise meedia (tekst, pildid) esitamiseks ja ajas toimuvaid meediaklippe (heli, video) saab vaid võõrkehadena sisestada, kuid peaaegu üldse mitte kontrollida, siis SMIL on loodud dünaamiliste, video- ja heliklippe sisaldavate esituste kirjeldamiseks, kusjuures korraga võib olla käivitatud mitu erinevat meediaklippi. Kogu ekraan on jagatud regioonideks ja esituse toimumist kirjeldatakse paralleelsete <par>...</par> ja järjestikuste <seq>...</seq> tegevuste abil. Iga meediaklipi jaoks kirjeldatakse nii selle käivitamise/lõpetamise aeg, koht(regioon) ekraanil, kus see käivitatakse jne, s.t. meediaklippide esitust kontrollivatel märgenditel <img>, <audio>, <text> on mitmesuguseid attribuute.
SMIL on HTML-i multimeedia-laiendus ja SMIL-dokument sarnaneb struktuurilt HTML-dokumendiga. SMIL- dokument on märgendite <smil> , </smil> vahel ja koosneb päisest (märgendite <head></headl> vahel) ja kehast (märgendite <body>...</body> vahel); need on kohustuslikud. Päises on märgendite <layout>...</layout> vahel kirjeldatud ekraani multimeediaregioonid (kohustulik osa). Regioonide kirjeldus algab kogu dokumendi regiooni kirjeldusega ( root-layout; see võib ka puududa, kuid kui on, siis ainult üks) millele järgnevad (alam)regioonid. Regiooni kirjelduse märgend algab <region ja lõpeb />; regiooni kirjelduse kohustuslik osa on regiooni nimi: id = string; regiooni kirjelduses määratakse ka selle asukoht ekraanil, kuid lihtsustamiseks on paljud SMIL-keele detailid siin ära jäetud.
Keha koosneb paralleelselt ja järjestikku täidetavate/esitetavate osade kirjeldustest. Paralleelste osade kirjeldus algab <par id= string >, lõpeb </par>; järjestikku esitatavate osade kirjeldus algab <seq id= string > ja lõpeb </seq> . Igas osas määratakse meediategevused, mis on kas heli: <audio id = string /> või pilt : <img id = string /> (stringid on failinimed). Osasid võib olla kuitahes palju (0,1,...). On lubatud osade sisestamine, st paralleelse osa sees võib olla üks või enam järjestikku täidetavatest osadest; lihtsustatud SMIL-is osasid ei sisestata, nad järgnevad üksteisele. Järgnevas on väike näide SMIL-dokumendist:
<smil>
<head>
<layout>
<root-layout id = "The Great Wall of China"/>
<region id = "image1" />
<region id = "image2" />
<region id = "text"/>
<region id = "music"/>
</layout>
</head>
<body>
<par id="Presentation">
<audio id ="audio" region="music" />
<img id = "fortress" region = "image1" />
<img id = "first_view" region = "image2" />
</par>
</body>
</smil>
Märgendite par, audio, img attribuutide id, region väärtused on loomulik kanda (laiendatud) nimede tabelisse, mis sisaldab kõiki töödeldava (skaneeritava) SMIL-esituse kirjelduse (SMIL-programmi) identifikaatoreid; skaneerimisel asendatakse identifikaatorid viitega sellesse tabelisse (sellesse tuleb ka identifikaatori tüüp ja viit mäluosale, kus salvestatakse identifikaatori väärtus).
Lekseemile attribuutide lisamiseks peab skanneril olema mälu. Kui näiteks HTML-i <img> -märgendil on attribuudid width, height (need esitatakse <img>-märgendis kujul "width=integer", "height=integer"), peab skanner pärast täisarvu (integer) lugemist mäletama, kas eespool oli "width=" või "height=", et kanda integer õige attribuudi (width, height) väärtuseks. Täpselt samuti peab SMIL-i <img>, <audio> attribuutide "regioon=string", "id=string" töötlemisel pärast stringi lugemist mäletama, kas eespool viidati regioonile või id-le, s.t. skanner töötab siin nagu lõplik automaat (tegelikult on kõik Flex-i abil genereeritud skannerid lõplikud automaadid).
"Mälu" saab skannerile anda mitmel viisil - võib
kasutada kas Flex-i olekuid või C-keele muutujaid. Skanneri olekute
kasutamine on väga loomulik ka nn sisestatud programmitekstide
skaneerimisel - kui näiteks HTML-keelses tekstis algab PHP-keeles
kirjutatud osa, on loomulik viia skanner PHP-analüüsiks teise olekusse.
Järgnevas on Flex-i olekuid kasutatud SMIL-märgendite tüübi
eristamiseks.
SMIL-keele audio- ja img-märgenditesl on mitu ühesugust attribuuti, kuid nende väärtused oleks loomulik salvestada erinevates tabelites. Skaneerimisel loetakse algul märgendi tüüp: audio või img ja skanner peab seda attribuudi väärtuseni jõudes "mäletama". Varem loetud märgijada meelespidamiseks võib kasutada Flex-i alustamistingimusi (start-conditions).
Järgnevas on lihtne näide, kus SMIL-keele audio- ja img-märgenditest moodustatakse järgneva struktuuriga lekseemid:
struct lexemtype
{
char type[6];
char id[10];
char region[10];
};
struct lexemtype lexem;
Kontrolliks trükitakse see struktuur alamprotseduuri tag_out abil märgendi lõppedes välja:
void tag_out(struct lexemtype lexem)
{
printf("< %s, %s, %s
>\n",lexem.type,lexem.id,lexem.region);
return;
}
Lekseemi moodustamisel saab lekseemi tüübi määrata juba lekseemi alguse <audio või <img lugemisel, seega kui Flex-programmi definitsioonide (esimeses) osas on kirjeldus:
START_AUDIO_TAG "<audio"{WHITE_CHAR}+
siis programmi teises (reeglite) osas võib määrata vastava semantilise tegevuse:{START_AUDIO_TAG} {
strcpy(lexem.type,"audio");
}
Kuid id- või region-attribuudi väärtuse (string) lugemise järel peab teadma, kas loetud string tuleb kirjutada lekseemi id- või region-välja, ja selle info meelespidamiseks (kas enne loetud stringi oli id = või region = ) saab kasutada Flex-i alustamistingimusi (olekuid). Kui skanner loeb sisendist mitteterminaliga ATTR_ID kirjeldatud märgijada id=, täidetakse alustamistingimus BEGIN(READ_ID), mis määrab, et skanner läheb olekusse READ_ID. Alustamistingimus määrab skanneri sees n.ö. "alamskanneri" - reeglid, mida täidetakse vaid selles olekus; analoogiliselt märgijada region= viib skanneri olekusse READ_REG; nendes olekutes stringi lugemisel teab skanner, millisesse lekseemi välja loetud string tuleb kirjutada. Alustamistingimusele vastavate reeglite ette tuleb kirjutada see tingimus nurksulgude < > vahel; kui tingimusele vastavaid reegleid on rohkem, võib nad figuursulgude { } abil kokku võtta:
ATTR_ID
"id"{WHITE_CHAR}*"="{WHITE_CHAR}*
ATTR_REG "region"{WHITE_CHAR}*"="{WHITE_CHAR}*
...
%%
...
{ATTR_ID} BEGIN(READ_ID);
<READ_ID>{STRING} strcpy(lexem.id,yytext);
{REG_ID} BEGIN(READ_REG);
<READ_REG>{STRING} strcpy(lexem.region,yytext);
Alustamistingimusi on kahesuguseid - välistavad ja lubavad. Välistavad tingimused ei luba tingimuse kehtimise ajal kasutada tavalisi (ilma tingimusteta defineeritud) reegleid; lubavate tingimuste korral võib (kui tingimusega määratud reeglite seas sobivat pole) kasutada ka ilma tingimusteta reegelid (tingimusega reegleid võib alati kasutada vaid vastava tingimuse kehtides).
SMIL-i märgendites võib üks (või mõlemad) attribuutidest id, region ka puududa, s.t. pole teada, millal leitakse märgendi lõpp. Sellepärast on siin kasutatud lubavaid alustamistingimusi, mille korral märgendi lõpu reegli võib täita niipea kui sisendis leitakse alamjada \>:
TAG_STOP "/>"
...
%%
...
{TAG_STOP} {
tag_out(lexem);
strcpy(lexem.id,""); strcpy(lexem.region,"");
BEGIN(INITIAL);
}
Tühja stringi omistamised pärast lekseemi väljastamist on vajalikud - kuna id ja/või region-attribuudid võivad järgmisel lekseemil puududa, tuleb vastavad väljad struktuuris lexem tühjaks teha!
Alustamistingimused peab loetlema programmi esimese (definitsioonide) osa algul; välistavate tingimuste ette (esimesest positsioonist) tuleb kirjutada %x, lubavate tingimuste ette %s. Et alustamistingimusi saaks kasutada, peab Flex-programmi algusesse lisama võtme %option stack.
Järgneb kogu Flex-programm SMIL-i audio ja img-märgenditest struktuursete lekseemide moodustamiseks:
%option noyywrap
/* grammatika Smil-margenditest parametriseeritud lekseemide tegemiseks
*/
%option stack
/* vajalik alustamistingimuste kasutamiseks */
%{
#include <string.h> /* vajalik strcpy saamiseks */
struct lexemtype
{
char type[6];
char id[10];
char region[10];
};
struct lexemtype lexem;
void tag_out(struct lexemtype lexem)
{
printf("< %s, %s, %s >",lexem.type,lexem.id,lexem.region);
return;
}
%}
%s READ_ID READ_REG
NUMBER [0-9]
TAHT [a-zA-Z]
TAHTNUM {TAHT}|{NUMBER}|[ \t"_""-"]
WHITE_CHAR [ \n\t]
TAG_STOP "/>"
ATTR_ID "id"{WHITE_CHAR}*"="{WHITE_CHAR}*
ATTR_REG "region"{WHITE_CHAR}*"="{WHITE_CHAR}*
STRING \"{TAHTNUM}*\"{WHITE_CHAR}*
START_AUDIO_TAG "<audio"{WHITE_CHAR}*
START_IMG_TAG "<img"{WHITE_CHAR}*
%%
{START_AUDIO_TAG} {
/* printf("\n audio, state %d: ",YYSTATE); */
strcpy(lexem.type, "audio"); }
{START_IMG_TAG} {
/*printf("\n image, state %d: ",YYSTATE);*/
strcpy(lexem.type, "img"); }
{ATTR_ID} BEGIN(READ_ID);/*printf("READ_ID, state: %d\n",YYSTATE);*/
<READ_ID>{STRING} strcpy(lexem.id, yytext);
{ATTR_REG} BEGIN(READ_REG);/* printf("READ_REG, state=%d\n", YYSTATE);
*/
<READ_REG>{STRING} strcpy(lexem.region, yytext);
{TAG_STOP} {
/*printf("tag_end, state:%d \n",YYSTATE); */
tag_out(lexem);
strcpy(lexem.id, "");
strcpy(lexem.region, ""); BEGIN(INITIAL); }
%%
main()
{
yylex();
}
Testime skannerit sisendiga, kus on kasutatud kõiki võimalusi attribuutide järjekorra ja esinemise jaoks:
<img id ="image1" region ="pilt" />
<audio id="audio1" />
<img region = "ekraaninurk" id = "helikopter"/>
<audio region="left_channel"/>
<audio/>
Väljund tuleb korrektne:
< img, "image1" , "pilt" >
< audio, "audio1" , >
< img, "helikopter", "ekraaninurk" >
< audio, , "left_channel" >
< audio, , >
Et näha, milliseid reegleid ja millises järjekorras skanner kasutas,
tuleb Flex-i sisendi alguses %options-osas
lisada võti
%options debug
(või käivitada võtmega -d) - siis väljastab Flex ka kasutatud reeglid (reanumbrid on Flex-i sisenfailist): --(end of buffer or a NUL)
--accepting rule at line 44 ("<img ")
--accepting rule at line 57 ("id =")
--accepting rule at line 58 (""image1" ")
--accepting rule at line 62 ("region =")
--accepting rule at line 63 (""pilt" ")
--accepting rule at line 49 ("/>")
--accepting default rule ("
")
--accepting rule at line 40 ("<audio ")
--accepting rule at line 57 ("id=")
--accepting rule at line 58 (""audio1" ")
--accepting rule at line 49 ("/>")
--accepting default rule ("
")
--accepting rule at line 44 ("<img ")
--accepting rule at line 62 ("region = ")
--accepting rule at line 63 (""ekraaninurk" ")
--accepting rule at line 57 ("id = ")
--accepting rule at line 58 (""helikopter"")
--accepting rule at line 49 ("/>")
--accepting default rule ("
")
--accepting rule at line 40 ("<audio ")
--accepting rule at line 62 ("region=")
--accepting rule at line 63 (""left_channel"")
--accepting rule at line 49 ("/>")
--accepting default rule ("
")
--accepting rule at line 40 ("<audio")
--accepting rule at line 49 ("/>")
--accepting default rule ("
")
--(end of buffer or a NUL)
--EOF (start condition 1)
Oletusreegel default rule on sisendi kopeerimine väljundisse - Flex-i sisendfailis polnud midagi reavahetuste kohta ja Flex kopeerib need selle reegli järgi otse väljundisse. Kui skanner on koostatud nii, et kõigi sisendis esineda võivate märkide/märgijadade jaoks on reegel, võib oletusreegli ära keelata, käivitades Flex-i võtmega -s. Selle kasutamine on sellise "täieliku" skanneri jaoks hea test - kui sisendis on midagi, mille jaoks reeglit pole, teatab skanner kohe veast; kui oletusreeglit võib kasutada, kopeerib skanner sellise koha otse väljundisse ja viga võib jääda märkamata.
Struktuurseid lekseeme võib moodustada ka otse C-keele muutujaid kasutades; sel juhul defineeritakse mõned abimuutujad, mis sisuliselt salvestavad lisainfot läbivaadatud osa kohta, näiteks allpooltoodud Flex-programmis muutujad image_start, audio_start on töesed (1), kui on alanud <img> või <audio> märgend, id_attr ja reg_attr taas "mäletavad", et enne praegu loetud stringi (Id või regiooniattribuudi väärtus) oli tekstis vastavalt "Id=" või "regioon=" jne
Järgnevas on näide C-keele muutujate abil kogu SMIL-programmi keha (body) analüüsiks.
%option noyywrap
/* scanner for a toy Smil-like language body part*/
%{
int num=0;
int id_rida=0;
int reg_rida=0;
int image_start=0;
int audio_start=0;
int region_attr=0;
int id_attr=0;
int reg_recorded=0;
int id_recorded=0;
int tabelisse=0;
int rida=1,veerg=1;
char c, nimed[50][10];
/* nametable; < 50 names are allowed in a program */
int i,j,k,n=0;
struct STag
{
int Tyyp; int iRida;
int iVeerg; int
ID_index; int Reg_index; };
struct STag slxResult;
void tag_out(struct STag slxTag)
{
printf("[ %d, %d, %d, %d, %d ]\n", slxTag.Tyyp, slxTag.iRida,
slxTag.iVeerg,
slxTag.ID_index, slxTag.Reg_index);
return; }
%}
TYHIK [ \t]
TYHIZ {TYHIK}*
TYHIKUD {TYHIK}+
ID "id"{TYHIZ}"="{TYHIZ}
REG "region"{TYHIZ}"="{TYHIZ}
NUMBER [0-9]
TAHT [a-zA-Z]
TAHTNUM {TAHT}|{NUMBER}|"_"|"-"
ID_STRING \"{TAHTNUM}+\"
IMG_start "<img"
AUD_start "<audio"
TAG_end "/>"
%%
{IMG_start} {image_start=1;
printf("Image tag start, rida %d veerg %d:\n",rida,veerg);
slxResult.iRida=rida;slxResult.iVeerg=veerg;
slxResult.Tyyp = 1;
/*slxResult.Tyyp="Img"*/
veerg=veerg+yyleng;}
{AUD_start} {audio_start=1;
printf("Audio tag start, rida %d veerg %d:\n",rida,veerg);
slxResult.iRida=rida;slxResult.iVeerg=veerg;
slxResult.Tyyp=2; /*"AUD" */
veerg=veerg+yyleng;}
{REG} {region_attr=1;
printf("region (rida %d veerg %d):",rida,veerg);
veerg=veerg+yyleng;}
{ID} {id_attr=1;
printf("tag id (rida %d veerg %d):",rida,veerg);
veerg=veerg+yyleng;}
{ID_STRING} {/*printf("%s\n",yytext);*/
for (i=0; iyyleng-2; i++)
nimed[n][i]=yytext[i+1];
nimed[n][yyleng-2]=0x00;
if ((region_attr==1)&&(reg_recorded==0))
{slxResult.Reg_index=n;
reg_recorded=1; };
if ((id_attr==1)&&(id_recorded==0))
{slxResult.ID_index=n; id_recorded=1; };
n++;
veerg=veerg+yyleng;}
{TAG_end} {if (audio_start==1)
{printf("Audio tag end, rida %d veerg %d\n",rida,veerg);
veerg=veerg+yyleng;
audio_start=0;};
if (image_start==1)
{printf("Image tag end, rida %d veerg
%d\n",rida,veerg); veerg=veerg+yyleng;
image_start=0;};
if (region_attr==1)
{region_attr=0;reg_recorded=0;}; if
(id_attr==1)
{id_attr=0;id_recorded=0;};
tag_out(slxResult);
slxResult.ID_index = -1;
slxResult.Reg_index = -1; /* kui järgmisel puudub */
veerg=veerg+yyleng;
};
{TYHIKUD} veerg=veerg+yyleng;
[\n]* {rida=rida+yyleng;veerg=1;}
.
%%
main( argc, argv )
int argc;
char **argv;
{
++argv, --argc; /* skip over program name */
if ( argc > 0 )
yyin = fopen( argv[0], "r" );
else
yyin = stdin;
slxResult.ID_index = -1; /* kui järgmisel
puudub */
slxResult.Reg_index = -1; /* kui järgmisel
puudub */
for(i = 0; i < 50; i++) nimed[i][0] = 0x00;
yylex();
printf("Identifikaatoreid: %d\n", n);
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");
} }
- esimese sulgeva figuursuluga }, mida saab paari panna esimese avava
figuursuluga { või
- reavahetusega,
{eesel }\n
- kommentaar
{ee{sel} }\n - kommentaar (kuni
reavahetuseni \n)
{ee{ el}\n - kommentaar
(reavahetus \n lõpetab)
{ee}sel } - kommentaar on vaid
lõik {ee}.
<smil>
<head>
<layout>
<root-layout id = "The Great Wall of China"/>
<region id = "image1" />
<region id = "image2" />
<region id = "text"/>
<region id = "music"/>
<transition id="fade_1" dur="2s" type="fade" />
</layout>
</head>
<body>
<par id="Presentation">
<audio id ="audio" region="music" dur = "2s"/>
<img id = "fortress" region = "image1" transln="fade_1" dur = "5.0s"/>
<img id = "first_view" region = "image2" dur "3.5s" />
</par>
</body>
</smil>
Skanner peab kõik päises deklareeritud regioonid ja üleminekud kandma nimede tabelisse ja nende esinemisel img, audio märgendites kontrollima, et vastavate attribuutide väärtused on nimede tablelis olemas; kui pole, peab tulema veateade (kus real, millise sümboli juures oli viga); attribuudi dur argumendi formaati peab samuti kontrollima ja lekseemis peaks attribuudi väärtus olema arv (s.t. reaalarv 5.2 , mitte string "5.2s").
StringBegChar 'ja koosta Flex-i programm, mis peale lekseemide Identifier NumConst EscapeChar KeyWords1 KeyWords2 KeyWords3 leiab ka rea- ja blokikommentaarid ja stringid; kommentaaride tekst asendatakse sõnaga "comment", string - sõnaga "string" (kommentaaride ja stringide teksti leidmisel peaks kasutama alustamistingimusi).
StringEndChar '
int a = 1;töötlemisel on pärast yylex()-i täitmist võimalik saada (printf-käskude abil) väljatrükk
int b=2;
float pii = 3.14;
taisarv a = 1Raskem versioon - skanner avastab ka mõned vead deklaratsiooni süntaksis (kuid jätkab tööd!), näiteks sisendi
taisarv b = 2
rarv pii = 3.14
int a = 1;järel (vigase sisendi võib trükkida juba analüüsi ajal) võib saada väljundi
int b = ;
float pii 3.14;
int b viga10. Täienda nimede tabelit nii, et sinna kantakse ka arvkonstandid; need asendatakse kõik lekseemiga ARV ja nende tegelik väärtus on nimede tabelis. Konstandi deklaratsiooni süntaks on nagu C-keeles, s.t. lubatud on näiteks järgnevad deklaratsioonid:
float pii viga
int a = 1
int const a = 1,b = 2;Arvkonstantide tüübid võivad olla (vaid) int, float, lekseem const võib esineda nii enne kui ka pärast tüübikirjeldust, deklaratsioonis võib olla üks või rohkem konstantide identifikaatoreid.
const int a = 2, c = 3;
float const pii = 3.14;