Ma voi simti mult mai putin vinovat daca va voi anunta ca testul 6 a fost
    ratat fara exceptie din cauza conditiei gresite ca un numar sa fie
    int/Integer. Conditia corecta era -32768<=x<=32767, nu -32767<=x<=32767.
    
    Lasand rautatile, probabil ca ar fi frumos sa povestesc exact cum trebuia
    abordata problema pentru a reduce la minim numarul de cazuri particulare.
    Deocamdata stau groaznic cu timpul si va raman dator. Trimit totusi sursa
    mea bine comentata, impreuna cu o prezentare a algoritmului:
    
    1) Citim programul. Nu efectuam nici un fel de prelucrari asupra numelor de
    variabile si a expresiilor, ci le lasam asa cum sunt (stringuri). Exceptie
    fac etichetele, pe care le convertim la integer. Instructiunile IF vor fi
    sparte in trei instructiuni, si anume:
    
      a) IF-ul in sine, care contine numai conditia logica si adresele de salt
    in cazurile in care conditia este sau nu adevarata;
      b) Instructiunea de pe ramura THEN, care va fi memorata imediat dupa
    instructiunea IF;
      c) Instructiunea de pe ramura ELSE, care in general este memorata imediat
    dupa THEN, in afara cazului cand instructiunea de pe ramura THEN este un
    if imbricat.
    
    2) Interpretam programul. Partea de evaluare a expresiilor aritmetice se
    face cu gramatica:
    
    Expr -> Term [+-] Term [+-] ... [+-] Term
    Term -> Fact [*/] Fact [*/] ... [*/] Fact
    Fact -> -Fact | (Expr) | Numar | Identificator
    
    La nivelul Fact-orilor "inghitim" tokenii (numar, identificator) si atunci
    convertim stringurile la numere, sau cautam variabilele in tabela de
    variabile. Constructia arborilor sintactici pentru fiecare expresie era
    calea cea mai rapida pentru interpretarea care urma, dar insemna o munca de
    cateva zile.
    
    Pana la urma, totul era o chestie de gramatici. In afara de gramatica
    expresiilor aritmetice prezentata mai sus, gramatica limbajului era:
    
    Program            -> Lista_instructiuni
    Lista_instructiuni -> Instructiune Lista_instructiuni |
                          Lambda (adica nimic)
    Instructiune       -> Eticheta Instr2 |
                          Instr2
     ( adica o eticheta urmata de o instructiune propriu-zisa, sau direct o
       instructiune propriu-zisa )
    
    Eticheta           -> Numar
    Instr2             -> "PRINT" Valoare |
                          "INPUT" Valoare |
                          "LET" Variabila "=" Valoare |
                          "IF" Conditie "THEN" Instr2 "ELSE" Instr2 |
                          "GO" "TO" Valoare |
                          "STOP"
      (Mentionez ca programul meu lucreaza de fapt cu productia
         "IF" Conditie "THEN" Instructiune "ELSE" Instructiune |
       adica nu are functii separate pentru Instrunctiune si Instr2. Totusi,
       asta nu poate genera ambiguitati)
    
    Variabila          -> Litera Rest_variabila
    Rest_variabila     -> Litera Rest_variabila |
                          Cifra Rest_variabila |
                          Lambda
      (puteati face trunchierea la 8 caractere fie la citire, fie la
       interpretare)
    
    Valoare            -> Expr              (expresie aritmetica)
    Numar              -> Cifra Rest_numar
    Rest_numar         -> Cifra Rest_numar |
                          Lambda
    Conditie           -> Valoare Op_logic Valoare
    Op_logic           -> "<" | ">" | "=" | "<=" | ">=" | "<>"
    
    Mai trebuie specificat faptul ca in C exista functia ungetc(), care permite
    punerea inapoi in fisier a unui caracter citit anterior (din pacate numai a
    unuia :) Aceasta ne permite sa citim un caracter, sa-l examinam si daca el
    nu ne este de folos pentru cuvantul pe care il citim, sa-l bagam inapoi.
    Acesta este un avantaj considerabil al C-ului pentru problema in discutie.
    Iata de exemplu cum citim un numar de pe banda, lasand restul benzii
    neatinse:
    
    C=getc(FIn);
    if isdigit(C)                 // Incepe un numar
      { do { yyText[yyLen++]=C;
             C=getc(FIn);
        } while (isdigit(C));     // Papam toate cifrele
        ungetc(C, FIn);           // Hopa, tu nu! Avantaj C fata de Pascal!
      }
    
    Se vede ca numarul se termina cand apare un caracter care nu e cifra. In
    Pascal ramanem cu acest caracter in brate, solutia fiind sa cream o
    variabila globala unde sa stocam caracterul pe care l-am citit, dar cu care
    inca n-am avut ce face.
    
    Un tip de date foarte convenabil pentru aceasta problema este "union". El
    are corespondentul Pascal "record cu case" (vom trata totusi implementarea
    in C). Tipul struct permite crearea de inregistrari formate din mai multe
    campuri. Daca definesc de exemplu:
    
    struct { int Tz;
             char Sh;
           } Idina;
    
    atunci am acces la campurile Idina.Tz si Idina.Sh, care sunt reprezentate
    unul dupa altul in memorie. Alterarea unuia nu il altereaza pe celalalt.
    
    In schimb, daca zic:
    
    union { int Tz;
            char Sh;
          } Idina;
    
    atunci am acces la aceleasi doua campuri, care insa sunt SUPRAPUSE in
    memorie (adica incep toate la aceeasi adresa). Suprascrierea unuia il va
    modifica si pe celalalt. Daca de exemplu zic:
    
    Idina.Tz=0xABCD;
    Idina.Sh=0xEF;
    
    atunci Idina.Tz va fi 0xABEF, pentru ca Sh are aceeasi adresa cu octetul
    cel mai nesemnificativ din Tz.
    
    Este treaba mea sa nu dau valori unui camp, iar pe urma sa ma refer la
    altul, daca nu vreau sa am neplaceri. Acest tip de date ajuta la
    omogenizarea unor date mai putin eterogene cu un consum minim de memorie.
    De exemplu, putem tine o instructiune prin:
    
    x Tipul ei;
    x un union care sa contina datele utile pentru toate tipurile de
    instructiune (valoarea pentru PRINT, numele variabilei pentru INPUT etc).