A nyelvi elemző használata
Czirkos Zoltán · 2022.06.21.
Néhány levezető feladat.
Válassz az alábbi feladatok közül!
A laborokhoz
A laborok mellé minden héten lesz kiírva egy beadandó az admin portálon. Ide óra végén töltsd fel a forráskódokat (*.cpp, *.h)! A feladatokat ezért külön projektben oldd majd meg, ne írd felül a megoldásokat.
A C nyelv hexadecimális számai (pl. 0x12, 0xdeafbeef, 0x00124) az alábbi szintaktikai szabályokkal adhatóak meg:
hex_num ::= hex_prefix hex_digit+ hex_prefix ::= '0' 'x' hex_digit ::= '0' | '1' | '2' ... 'd' | 'e' | 'f'
Ez azt jelenti, hogy egy hexadecimális szám (hex_num) elején egy prefix van (hex_prefix), utána pedig legalább egy, de tetszőlegesen sok (+) hexadecimális számjegy (hex_digit) lehet. A prefix alakja: egy 0 és egy x karakter (szimbólum) egymás után. A hexadecimális számjegy pedig a 0, 1, 2, stb. karakterek valamelyike (|).
Ezt a keretrendszerben az alábbi kódrészlettel lehet megadni:
rule hex_num, hex_prefix, hex_digit;
hex_num <<= hex_prefix << +hex_digit;
hex_prefix <<= character("0") << character("x");
hex_digit <<= character("0123456789abcdef");
Jelentés | EBNF operátor | Syntx (C++) operátor |
---|---|---|
definíció | ::= | <<= |
egymásra következés | ␣ (szóköz) | << |
≥1 ismétlés | postfix + | prefix + |
literális | 'x' | character("x") |
A letölthető kódban (parser.cpp) arra is látsz példát, hogy egy szabály illeszkedéséhez (sztringrészlet felismeréséhez)
hogyan lehet tevékenységet társítani, az indexelő operátor segítségével. A társított függvény std::string const &
típusú paraméterben megkapja a megtalált szövegrészletet.
A hexadecimális szám kiírása
Írd át úgy a programot, hogy ne a hexa prefixet írja ki a találat esetén a képernyőre (0x), hanem a megtalált hexa számot! Pl. 0xfce2 esetén ez fce2. Hova kell tenni a semantic_action-t?
Megoldás
Figyelni kell arra, hogy a kiíró függvényt nem a hex_digit
-hez, hanem a +hex_digit
-hez
kell társítani. Különben nem a teljes számot kapja meg, hanem egyesével megkapja az összes hexa számjegyet. A
társításnál ehhez kell egy zárójel, mivel a C++ ()
függvényhívó operátora magasabb precedenciájú, mint
a prefix +
operátora.
auto print_hex_num = [] (std::string const & s) {
std::cout << "Found hex num: " << s << std::endl;
};
hex_num <<= hex_prefix << (+hex_digit)(print_hex_num);
A hexadecimális szám konvertálása
Írd át a függvényedet úgy, hogy a megtalált hexa számot ne kiírja a képernyőre, hanem tegye azt be egy int
típusú változóba! Ezt az int
változót a lambda függvénynek látnia kell majd.
Megoldás
rule hex_num, hex_prefix, hex_digit;
int i;
auto convert_hex = [&i] (std::string const & s) {
i = 0;
for (char c : s) {
i = i * 16 + (isdigit(c) ? c-'0' : c-'a'+10);
}
};
hex_num <<= hex_prefix << (+hex_digit)(convert_hex);
hex_prefix <<= character("0") << character("x");
hex_digit <<= character("0123456789abcdef");
match_range context(text.begin(), text.end()), result;
if (hex_num.match(context, result)) {
std::cout << "The number is: " << i << std::endl;
}
else std::cout << "Didn't match" << std::endl;
Decimális számok
Írj nyelvtani szabályt decimális számok feldolgozására! A decimális számok legalább egy, de amúgy akárhány olyan számjegyből állnak, amelyek a 0, 1, ... 9 halmazból kerülnek ki. Írd meg ehhez is a konverziót!
Megoldás
/* DECIMÁLIS SZÁM */
rule dec_num, dec_digit;
int i;
auto convert_dec = [&i] (std::string const & s) {
i = 0;
for (char c : s) {
i = i * 10 + c-'0';
}
};
dec_num <<= (+dec_digit)(convert_dec);
dec_digit <<= character("0123456789");
match_range context(text.begin(), text.end()), result;
if (dec_num.match(context, result)) {
std::cout << "The number is: " << i << std::endl;
}
else std::cout << "Didn't match" << std::endl;
Hexadecimális szám vagy decimális szám?
A parser a nyelvtani szabályok alapján fel tudja ismerni az is, hogy hexadecimális vagy decimális
számot kapott. Egy szám az vagy hexadecimális szám (prefixszel), vagy decimális szám (prefix nélkül). Mindkettőhöz
megvan már a szabály, csak választani kell közülük. Ezt az EBNF |
operátora teszi, amelyet
a C++-ban szintén |
operátorként lehetett megvalósítani.
Írj egy új szabályt, amely így definiál egy számot! A program így képes lesz arra, hogy automatikusan válasszon a kettő közül, sőt a meglévő hex→int és dec→int függvények segítségével át is alakítja azt.
Megoldás
int i;
/* DECIMÁLIS SZÁM */
rule dec_num, dec_digit;
auto convert_dec = [&i] (std::string const & s) {
i = 0;
for (char c : s) {
i = i * 10 + c-'0';
}
};
dec_num <<= (+dec_digit)(convert_dec);
dec_digit <<= character("0123456789");
/* HEXADECIMÁLIS SZÁM */
rule hex_num, hex_prefix, hex_digit;
auto convert_hex = [&i] (std::string const & s) {
i = 0;
for (char c : s) {
i = i * 16 + (isdigit(c) ? c-'0' : c-'a'+10);
}
};
hex_num <<= hex_prefix << (+hex_digit)(convert_hex);
hex_prefix <<= character("0") << character("x");
hex_digit <<= character("0123456789abcdef");
/* TETSZŐLEGES SZÁM, HEXA VAGY DECIMÁLIS */
rule number;
number <<= hex_num | dec_num;
match_range context(text.begin(), text.end()), result;
if (number.match(context, result)) {
std::cout << "The value is: " << i << std::endl;
}
else std::cout << "Didn't match" << std::endl;
Sztring vége szabály
Észreveheted, hogy a mostani program nem zavartatja magát, ha érvénytelen karaktereket talál egy sztring
végén. Pl. a "123"
sztringet ugyanúgy elfogadja százhuszonháromnak, mint a "123x"
sztringet. Ez azért van, mert legfölső szabálynak a számot értelmező szabályt vettük; az eljutott a hármas
számjegy utánig; és az x
-et nem vette a megtalált szövegrész részének, de igazzal tért vissza,
mert az előtte lévő számjegyeket tudta értelmezni. Azt kellene tehát mondanunk, hogy a szám karakterei
után már más nem szabad szerepeljen. Ha csak a decimális számokra gondolunk, valahogy így:
dec_num <<= +dec_digit << end_of_string();
Ehhez egy új szabályt, egy új base_rule
leszármazottat kell bevezetni, amely az üres
sztringre, és csak az üres sztringre illeszkedik. Írd meg ezt az osztályt, figyelve arra, hogy
mi a test()
virtuális függvény feladata! (Illeszkedés esetén igazzal tér vissza, és beállítja
mind a context
, mind a matching_range
paramétereket). Egészítsd ki a számokat
leíró nyelvtani szabályt úgy, hogy visszadobják a sztringet, ha szemét van a végén!
Megoldás
class end_of_string : public base_rule {
private:
virtual bool test(match_range &context, match_range &matching_range) override {
if (context.first == context.second) {
matching_range = context;
return true;
}
return false;
}
public:
virtual std::shared_ptr<base_rule> clone() const override {
return std::shared_ptr<base_rule>(new end_of_string(*this));
}
};
Vessző operátor?
Az EBNF-ben az egymásra következést általában nem jelölik sehogy, vagyis szóközzel jelölik.
Néha jelölik vesszővel is, pl. hex_prefix ::= '0', 'x'
. Lehetne ezt C++-ban
a <<
operátor helyett ,
operátorral jelölni? Ha igen, kényelmesebb
lenne vagy kényelmetlenebb lenne a keretrendszer használata? Miért?