Parsery LR z założenia nie radzą sobie z niejednoznacznymi regułami gramatycznymi. (Ułatwili teorię w latach siedemdziesiątych, kiedy opracowywano pomysły).
C i C ++ zezwalają na następującą instrukcję:
x * y ;
Ma dwie różne analizy:
- Może to być deklaracja y, jako wskaźnik do typu x
- Może to być wielokrotność x i y, odrzucając odpowiedź.
Teraz możesz pomyśleć, że to drugie jest głupie i należy je zignorować. Większość by się z tobą zgodziła; jednak są przypadki, w których może to mieć efekt uboczny (np. przeciążenie funkcji multiply). ale nie o to chodzi. Chodzi o to, że istnieją dwie różne analizy, dlatego program może oznaczać różne rzeczy w zależności od tego, jak powinien zostać przeanalizowany.
Kompilator musi zaakceptować odpowiednią w odpowiednich okolicznościach, aw przypadku braku innych informacji (np. Znajomość typu x) musi zebrać oba, aby później zdecydować, co robić. Zatem gramatyka musi na to pozwolić. A to sprawia, że gramatyka jest niejednoznaczna.
Dlatego czyste parsowanie LR nie może sobie z tym poradzić. Również wiele innych powszechnie dostępnych generatorów parserów, takich jak Antlr, JavaCC, YACC czy tradycyjny Bison, czy nawet parsery w stylu PEG, nie może być używanych w „czysty” sposób.
Istnieje wiele bardziej skomplikowanych przypadków (parsowanie składni szablonu wymaga arbitralnego spojrzenia w przód, podczas gdy LALR (k) może patrzeć w przód na większości k tokenów), ale wystarczy tylko jeden kontrprzykład, aby zestrzelić czysty parsowanie LR (lub innych).
Większość prawdziwych parserów C / C ++ radzi sobie z tym przykładem używając pewnego rodzaju deterministycznego parsera z dodatkowym hackem: przeplatają parsowanie z kolekcją tablic symboli ... tak aby do czasu napotkania "x" parser wiedział, czy x jest typem lub nie, a zatem może wybierać między dwoma potencjalnymi analizami. Ale parser, który to robi, nie jest bezkontekstowy, a parsery LR (czyste itp.) Są (w najlepszym przypadku) bezkontekstowe.
Można oszukiwać i dodawać semantyczne kontrole czasu redukcji dla poszczególnych reguł w parserach LR, aby dokonać tego ujednoznacznienia. (Ten kod często nie jest prosty). Większość innych typów parserów ma pewne sposoby dodawania kontroli semantycznej w różnych punktach analizy, które mogą być do tego użyte.
A jeśli wystarczająco oszukasz, możesz sprawić, by parsery LR działały w C i C ++. Faceci z GCC zrobili to przez jakiś czas, ale zrezygnowali z ręcznego kodowania parsowania, myślę, że chcieli lepszej diagnostyki błędów.
Istnieje jednak inne podejście, które jest ładne i przejrzyste oraz dobrze analizuje C i C ++ bez żadnego hakera w tablicy symboli: parsery GLR . Są to parsery w pełni bezkontekstowe (mające efektywnie nieskończone antycypowanie). Parsery GLR po prostu akceptują obie analizy, tworząc „drzewo” (właściwie skierowany acykliczny graf, który jest głównie podobny do drzewa), które reprezentuje niejednoznaczną analizę. Przebieg po analizie może rozwiązać niejednoznaczności.
Używamy tej techniki w interfejsach C i C ++ dla naszego oprogramowania DMS Reengineering Tookit (od czerwca 2017 obsługują one pełne C ++ 17 w dialektach MS i GNU). Były używane do przetwarzania milionów wierszy dużych systemów C i C ++, z kompletnymi, precyzyjnymi analizami tworzącymi AST z pełnymi szczegółami kodu źródłowego. (Zobacz AST dla najbardziej irytującej analizy języka C ++ ).