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.

1. Nagy házi feladat

Ha még nincs kész a nagy házi feladatod, dolgozhatsz azon is.

2. A parser framework használata

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ésEBNF operátor Syntx (C++) operátor
definíció::=<<=
egymásra következés␣ (szóköz)<<
≥1 ismétléspostfix +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?