Reálnočíselná kalkulačka

Ciele
  1. Pochopiť princíp zotavenia analyzatora pri syntaktickej chybe a spracovania chýb v nástrojoch Lex a Yacc.
  2. Naučiť sa špecifikovať typy hodnôt prenášaných v atribútoch symbolov jazyka.
Úvod

    Na tomto cvičení sa pozrieme na spracovanie chýb. Ak v analyzovanom vstupnom zdrojovom texte je chyba, teda tento vstup nezodpovedá gramatike definovanej vo vstupoch pre generátory lex a yacc, je možné jednoducho nahlásiť túto chybu a ukončiť analýzu. Je však užitočnejšie, ak analýza bude pokračovať a nájdu sa aj prípadné ďalšie chyby. Všimnite si, ako prekladač jazyka C vie nahlásiť viac existujúcich rozpoznaných syntaktických chýb, ktoré sa nachádzajú vo vstupnom zdrojovom súbore. Preto syntaktické analyzátory a prekladače implementujú zotavenie sa z chýb — pokračovanie v procese analýzy aj po výskyte chyby. Dôležité je, aby analyzátor nehlásil tzv. zavlečené chyby, ktoré v analyzovanom texte niesú, ale analyzátor ich nesprávne detekuje v dôsledku zlého zotavenia po predošlých chybách.

    Ďalšou témou cvičenia je to, ako je možné prenášať v atribútoch symbolov hodnoty rôznych typov.

Postup
  1. Spracovanie chýb pre generátory Lex-Yacc

    Implicitnou reakciou na chybu v analyzátore generovanom nástrojom Yacc je volanie funkcie yyerror("syntax error") a ukončenie práce analyzátora, ak nie je možné zotavenie. Stručné zhrnutie implementácie zotavenia v lexikálnom a syntaktickom analyzátore je uvedené nižšie.

    Princíp zotavenia pri lexikálnej chybe:

    • CHYBA = vstup nezodpovedá žiadnemu symbolu,
    • použiť vypúšťacie pravidlo:
      . {yyerror("nedovoleny znak");}

    Princíp zotavenia pri syntaktickej chybe:

    • CHYBA = vstup nezodpovedá pravidlám gramatiky.
    • Použiť v pravidlách, kde možno očakávať chyby, preddefinovaný terminálny symbol error.
    • Pri zistení chyby je testovaná možnosť pre doteraz spracované pravidlá (neterminálne symboly) využitia symbolu error (či existuje možnosť jeho presunu do zásobníka)
    • Ak je to možné — zotavenie, inak ukončenie práce analyzátora
    • Po zotavení zostáva analyzátor v stave nehlásenia chýb, kým správne nespracuje tri vstupne symboly

    Príklad: Majme gramatiku:

    kalkul: e
          | kalkul vypocet CR
          | kalkul CR
          | kalkul error CR
          ;
    vypocet : vyraz
    vyraz : vyraz PLUS vyraz
          | vyraz DELENE vyraz
          | CISLO
          ;

    Pre vstup "2++3 CR 4/5 CR" obsahujúci chybu bude priebeh syntaktickej analýzy nasledovný:

    Obr.: Stav zásobníka pri syntaktickej analýze výrazu s chybou
  2. Špecifikácia typu prenášaných hodnôt

    Pri analýze textu sú symbolom gramatiky priradené hodnoty - atribúty symbolov, ktoré sú prenášané medzi akciami priradenými k jednotlivým pravidlám. Atribúty symbolov sú realizované v generátoroch lex a yacc pomocou tzv. pozičných premenných $1,$2,..$$. V príklade z minulého cvičenia sa prenášané hodnoty využívali na uchovanie medzivýsledku výpočtu výrazu.

    Hodnota priradená do špeciálnej premennej $$ sa priraďuje symbolu na ľavej strane pravidla gramatiky. Následne, ak sa tento symbol vyskytne na pravej strane niektorého iného pravidla, daná hodnota bude prístupná v špeciálnej premennej $i, pričom i je číslo dané poradím daného symbolu na pravej strane pravidla. Lexikálnym symbolom sú hodnoty priraďované v lexikálnom analyzátore tak, že sa zapíšu do premennej yylval.

    Obr.: Ilustrácia prenosu hodnôt medzi pravidlami

    Pri implementácii prekladača môže byť potrebné prenášať so symbolmi hodnoty rôznych typov. Definícia typu prenášaných hodnôt sa vo vstupnom súbore Yacc zadáva pomocou direktívy %union, ktorá má syntax obdobnú deklarácií union v jazyku C. Vymenujú sa tam všetky typy hodnôt ktoré sa budú používať v programe a sú im priradené mená.

    %union{
        TYP1 MENO1;
        ...
        TYPn MENOn;
    }
    Obr.: Deklarácia typov prenášaných hodnôt

    Následne je potrebné priradiť typ terminálnym symbolom tak, že sa meno typu uvedie v direktíve %token. Na priradenie typu neterminálnym symbolom sa používa direktíva %type.

    %token <meno> term1 term2 ...
    %type <meno> net1 net2 ...
    Obr.: Priradenie typu terminálnym a neterminálnym symbolom.

    V lexikálnom analyzátore sa potom s premennou yylval, ktorá slúži na prenos hodnôt lexikálnych symbolov, pracuje ako s hodnotou typu union. To znamená, že je potrebné pristupovať k jej príslušnej zložke, ktorá zodpovedá potrebnému typu.

    %{
        extern YYSTYPE yylval;
    %}
    %%
    symbol  { yylval.meno = hodnota;
              return(KOD); }
    Obr.: Priradenie hodnoty daného typu v lex. analyzátore (vstupný súbor pre Lex)
  3. V pripravenom zdrojovom kóde kalkulačky je interpretér z minulého cvičenia upravený tak, aby dokázal pracovať nie len s celočíselnými ale aj s reálnymi hodnotami (typu double).
    %union {
        int ihod;
        double rhod;
        struct { int jeint; double hod; } vhod;
    };
    Obr.: Deklarácia typov reálnočíselnej kalkulačky
    Zatiaľ čo hodnoty pre lexikálne symboly zodpovedajúce celému alebo reálnemu číslu sú jednoduché typy (int ihod resp. double rhod), hodnoty výrazov sú štruktúrou. Jednou zo zložiek štruktúry je premenná jeint indikujúca typ hodnoty výrazu. Samotná hodnota je pre jednoduchosť uložená vždy ako typ double.
    Úloha: Preštudujte si implementáciu reálnočíselnej kalkulačky. Ako sa v akciách vo vstupnom súbore pre Yacc pristupuje k prenášaným hodnotám? Používa sa identifikátor typu (napríklad $1.ihod)?
    Úloha: Upravte calc2.l a calc2.y, aby kalkulačka umožňovala:
    1. int(vyraz), kde výsledkom bude celočíselná hodnota výrazu
    2. x@y, kde výsledkom bude y-ta (y — celé číslo) mocnina x (celé alebo reálne číslo)
    3. Relačné operátory <, <=, >, >=, ==, !=, kde výsledkom bude 0 alebo 1
    4. x ? y : z, kde výsledkom bude y, ak hodnota x bude nenulová a výsledkom bude z, ak hodnota x bude nulová
Zdroje
  1. Zdrojové kódy pre cvičenie 2
comments powered by Disqus