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).
|
|