C ++ neden bir LR (1) ayrıştırıcısıyla ayrıştırılamıyor?


153

Ayrıştırıcılar ve ayrıştırıcı jeneratörleri hakkında okuyordum ve bu ifadeyi wikipedia'nın LR ayrıştırma sayfasında buldum:

Birçok programlama dili, bir LR ayrıştırıcısının bazı varyasyonları kullanılarak ayrıştırılabilir. Dikkate değer bir istisna C ++.

Neden böyle? C ++ 'ın hangi özel özelliği LR ayrıştırıcılarıyla ayrıştırmanın imkansız olmasına neden olur?

Google'ı kullanarak, yalnızca C'nin LR (1) ile mükemmel bir şekilde ayrıştırılabileceğini buldum, ancak C ++ LR (∞) gerektiriyor.


7
Tıpkı: özyineleme ;-) öğrenmek için özyinelemeyi anlamanız gerekir.
Toon Krijthe

5
"Bu ifadeyi ayrıştırdığınızda ayrıştırıcıları anlayacaksınız."
ilya n.

Yanıtlar:


92

Lambda Ultimate'da C ++ için LALR dilbilgisini tartışan ilginç bir konu var .

C ++ ayrıştırma tartışmasını içeren bir doktora tezine bir bağlantı içerir, bu şunları belirtir:

"C ++ dilbilgisi belirsiz, bağlama bağlı ve potansiyel olarak bazı belirsizlikleri çözmek için sonsuz bir ileriye ihtiyaç duyuyor".

Birkaç örnek vermeye devam etmektedir (pdf'in 147. sayfasına bakınız).

Örnek:

int(x), y, *const z;

anlamı

int x;
int y;
int *const z;

Karşılaştırmak:

int(x), y, new int;

anlamı

(int(x)), (y), (new int));

(virgülle ayrılmış bir ifade).

İki belirteç dizisi aynı ilk alt diziye, ancak son öğeye bağlı olan farklı ayrıştırma ağaçlarına sahiptir. Belirsiz olandan önce keyfi olarak birçok jeton olabilir.


29
Bu sayfada 147. sayfa hakkında bir özet almak güzel olurdu. Yine de o sayfayı okuyacağım. (+1)
Neşeli

11
Örnek şudur: int (x), y, * const z; // anlamı: int x; int y; int * const z; (bir dizi bildirim) int (x), y, yeni int; // anlamı: (int (x)), (y), (yeni int)); (virgülle ayrılmış bir ifade) İki belirteç dizisi aynı ilk diziye ancak son öğeye bağlı olan farklı ayrıştırma ağaçlarına sahiptir. Belirsiz olandan önce keyfi olarak birçok jeton olabilir.
Blaisorblade

6
Bu bağlamda ∞, "keyfi olarak çok sayıda" anlamına gelir, çünkü ileriye her zaman giriş uzunluğu ile sınırlanacaktır.
MauganRa

1
Bir doktora tezinden alınan alıntıdan oldukça şaşkınım. Bir belirsizlik varsa, tanım gereği, HİÇBİR gözetleme belirsizliği "çözemez" (yani hangi ayrışmanın doğru cevher olduğuna karar verin, çünkü en az 2 bölüm dilbilgisi tarafından doğru kabul edilir). Ayrıca, atıf C'nin belirsizliğinden bahsetmektedir ancak açıklama, bir belirsizlik göstermez, ancak ayrıştırma kararının ancak keyfi olarak uzun bir ileriye baktıktan sonra alınabileceği belirsiz olmayan bir örnektir.
Dodecaplex

231

LR ayrıştırıcıları tasarım gereği belirsiz dilbilgisi kurallarını kullanamazlar. (Fikirlerin çalışıldığı 1970'lerde teoriyi daha kolay hale getirdi).

C ve C ++ aşağıdaki ifadelere izin verir:

x * y ;

İki farklı ayrımı vardır:

  1. X türüne işaretçi olarak y'nin bildirimi olabilir.
  2. Cevabı atmak, x ve y çarpımı olabilir.

Şimdi, ikincisinin aptal olduğunu ve göz ardı edilmesi gerektiğini düşünebilirsiniz. Çoğu sizinle aynı fikirde; ancak, bunun bir yan etkisi olabileceği durumlar vardır (örneğin, çarpma aşırı yüklenmişse). ama mesele bu değil. Nokta var olan iki farklı ayrıştırır ve bu nedenle bir program bu şekline bağlı olarak farklı anlamlara gelebilir gerektiğini çözümlenir edilmiştir.

Derleyici uygun koşullar altında uygun olanı kabul etmelidir ve başka herhangi bir bilginin yokluğunda (örneğin, x türü bilgisi) daha sonra ne yapılacağına karar vermek için her ikisini de toplamalıdır. Dolayısıyla bir dilbilgisi buna izin vermelidir. Bu da dilbilgisini belirsiz kılar.

Bu nedenle saf LR ayrıştırma bunu başaramaz. Antlr, JavaCC, YACC veya geleneksel Bison gibi yaygın olarak bulunan diğer ayrıştırıcı jeneratörler ve hatta "saf" bir şekilde kullanılan PEG tarzı ayrıştırıcılar da olamaz.

Çok daha karmaşık durumlar vardır (şablonun sözdizimi ayrıştırılması keyfi bir önden okuma gerektirirken, LALR (k) çoğu k jetonunu ileriye bakabilir), ancak saf LR (veya diğerleri) ayrıştırmasını vurmak için yalnızca bir karşı örnek alır .

Çoğu gerçek C / C ++ ayrıştırıcısı bu örneği, fazladan bir saldırı ile bir tür deterministik ayrıştırıcı kullanarak işler: sembol tablosu koleksiyonu ile ayrıştırmayı iç içe geçirirler ... böylece "x" ile karşılaşıldığında, ayrıştırıcı x'in bir tür ya da değil ve böylece iki potansiyel çözüm arasında seçim yapabilir. Ancak bunu yapan bir ayrıştırıcı bağlamdan bağımsız değildir ve LR ayrıştırıcıları (saf olanlar, vs.) bağlamdan bağımsızdır.

Bu dezavantajı yapmak için LR ayrıştırıcılarına hile ve kural başına azaltma süresi semantik kontrolleri eklenebilir. (Bu kod genellikle basit değildir). Diğer ayrıştırıcı türlerinin çoğunda, ayrıştırma işleminin çeşitli noktalarına anlamsal denetimler eklemek için bazı araçlar vardır, bunlar bunu yapmak için kullanılabilir.

Yeterince hile yaparsanız, LR ayrıştırıcılarının C ve C ++ için çalışmasını sağlayabilirsiniz. GCC adamları bir süre yaptılar, ancak elle kodlanmış ayrıştırma için vazgeçtiler, bence daha iyi hata teşhisi istediler.

Yine de, güzel ve temiz olan ve C ve C ++ 'ı herhangi bir sembol tablosu korsanlığı olmadan gayet iyi ayrıştıran başka bir yaklaşım var: GLR ayrıştırıcılar . Bunlar tam bağlamsız ayrıştırıcılardır (etkili bir şekilde sonsuz görünüme sahip). GLR ayrıştırıcıları, her iki ayrışmayı da kabul ederek belirsiz ayrışmayı temsil eden bir "ağaç" (aslında çoğunlukla ağaç gibi yönlendirilmiş asiklik bir grafik) üretir. Ayrıştırma sonrası geçiş belirsizlikleri çözebilir.

Bu tekniği DMS Software Reengineering Tookit için C ve C ++ ön uçlarında kullanıyoruz (Haziran 2017 itibariyle bunlar MS ve GNU lehçelerinde tam C ++ 17'yi ele alıyor). Milyonlarca satır büyük C ve C ++ sistemlerini işlemek için kullanılmıştır, tam, kesin ayrıştırmalar, kaynak kodunun tüm ayrıntılarını içeren AST'ler üretmektedir. ( C ++ 'ın en sinir bozucu ayrışması için AST'ye bakınız. )


11
'X * y' örneği ilginç olsa da, C'de de aynı şey olabilir ('y' bir typedef veya değişken olabilir). Ancak C, bir LR (1) ayrıştırıcısı tarafından ayrıştırılabilir, bu nedenle C ++ ile farkı nedir?
Martin Cote

12
Cevabım C'nin aynı problemi olduğunu zaten gözlemlemişti, sanırım bunu kaçırdınız. Hayır, aynı nedenden ötürü LR (1) tarafından ayrıştırılamaz. Ne demek 'y' bir typedef olabilir? Belki de 'x' demek istediniz? Bu hiçbir şeyi değiştirmez.
Ira Baxter

6
Ayrıştırma 2'nin C ++ 'da aptal olması gerekmez, çünkü * yan etkilere sahip olması için geçersiz kılınabilir.
Dour High Arch

8
Baktım x * yve kıkırdadı - birinin böyle küçük belirsiz belirsizlikleri ne kadar az düşündüğü şaşırtıcı.
yeni123456

51
@altie Hiç kimse bir akışa en değişken türlerini yazmasını sağlamak için bir bit kaydırma operatörünü aşırı yüklemez, değil mi?
Troy Daniels

16

Sorun asla böyle tanımlanmadı, oysa ilginç olmalı:

Bu yeni dilbilgisinin "bağlamsız" bir yacc ayrıştırıcısı tarafından mükemmel şekilde ayrıştırılabilmesi için gerekli olabilecek en küçük C ++ dilbilgisi değişiklikleri grubu nedir? (sadece bir 'hack' kullanmaktan geçer: typename / tanımlayıcı anlam ayrımı, ayrıştırıcı, her typedef / class / struct için lexer'ı bilgilendirir)

Birkaç tane görüyorum:

  1. Type Type;yasak. Tür adı olarak bildirilen bir tanımlayıcı, tür adı olmayan bir tanımlayıcı struct Type Typeolamaz (belirsiz olmayan ve yine de izin verilebilir not ).

    3 tür vardır names tokens:

    • types : yerleşik türü veya typedef / class / struct nedeniyle
    • Şablon fonksiyonlar
    • tanımlayıcılar: fonksiyonlar / yöntemler ve değişkenler / nesneler

    Şablon işlevlerini farklı jetonlar olarak değerlendirmek func<belirsizliği çözer . Eğer funcbir şablon işlevi adıdır, o zaman <başka türlü, bir şablon parametresi listesinin başında olmalıdır funcbir işlev işaretçisi ve <karşılaştırma operatörüdür.

  2. Type a(2);bir nesne örneğidir. Type a();ve Type a(int)fonksiyon prototipleridir.

  3. int (k); tamamen yasak, yazılmalı int k;

  4. typedef int func_type(); ve typedef int (func_type)();yasaktır.

    Typedef işlevinin, typedef işlev işaretçisi olması gerekir: typedef int (*func_ptr_type)();

  5. şablon özyineleme 1024 ile sınırlıdır, aksi takdirde derleyiciye seçenek olarak artırılmış bir maksimum değer aktarılabilir.

  6. int a,b,c[9],*d,(*f)(), (*g)()[9], h(char); ile yasaklanmış olabilir, int a,b,c[9],*d; int (*f)();

    int (*g)()[9];

    int h(char);

    işlev prototipi veya işlev işaretçisi bildirimi başına bir satır.

    Çok tercih edilen bir alternatif, korkunç işlev işaretçisi sözdizimini değiştirmek,

    int (MyClass::*MethodPtr)(char*);

    şu şekilde yeniden senkronize ediliyor:

    int (MyClass::*)(char*) MethodPtr;

    bu, döküm operatörü ile uyumlu (int (MyClass::*)(char*))

  7. typedef int type, *type_ptr; de yasaklanmış olabilir: typedef başına bir satır. Böylece

    typedef int type;

    typedef int *type_ptr;

  8. sizeof int, sizeof char, sizeof long longVe co. her kaynak dosyada bildirilebilir. Bu nedenle, türü kullanan her kaynak dosya intile başlamalıdır.

    #type int : signed_integer(4)

    ve unsigned_integer(4)bu #type direktifin dışında yasaklanacaktı sizeof int, bu birçok C ++ başlığında mevcut olan aptal belirsizlik için büyük bir adım olurdu.

Yeniden eşitlenmiş C ++ 'ı uygulayan derleyici, belirsiz sözdizimini kullanan bir C ++ kaynağı ile karşılaşırsa, source.cppçok fazla ambiguous_syntaxklasör taşıyacak ve source.cppderlemeden önce otomatik olarak net bir çeviri oluşturacaktır .

Biraz biliyorsanız lütfen belirsiz C ++ sözdiziminizi ekleyin!


3
C ++ çok iyi yerleşmiş. Kimse bunu pratikte yapmayacak. Ön uçlar inşa eden bu millet (bizim gibi) basitçe mermiyi ısırır ve ayrıştırıcıları çalıştırmak için mühendislik yapar. Ve, şablonlar dilde mevcut olduğu sürece, saf bağlamsız bir ayrıştırıcı elde edemezsiniz.
Ira Baxter

9

Benim de görebileceğiniz gibi burada cevap , C ++ deterministically nedeniyle tip çözünürlük aşamasında (tipik sonrası ayrıştırma) bir LL veya LR parser tarafından ayrıştırılamaz sözdizimi değişen içeren işlemlerin sırasını ve AST dolayısıyla temel şekli ( tipik olarak bir birinci-aşama ayrışması ile sağlanması beklenmektedir).


3
Belirsizliği işleyen ayrıştırma teknolojisi, ayrıştırıldıkça her iki AST değişkenini de üretir ve tür bilgisine bağlı olarak yanlış olanı ortadan kaldırır.
Ira Baxter

@Ira: Evet, bu doğru. Bunun özel avantajı, ilk aşama ayrıştırmasının ayrılmasını sağlamanıza izin vermesidir. GLR ayrıştırıcısında en yaygın olarak bilinmesine rağmen, C ++ 'a "GLL?" İle vuramamanızın özel bir nedeni yoktur. ayrıştırıcı.
Sam Harwell

"GLL"? Elbette, ama teoriyi bulmalı ve geri kalanı için bir kağıt yazmalısınız. Büyük olasılıkla, yukarıdan aşağıya elle kodlanmış bir ayrıştırıcı veya bir geri izleme LALR () ayrıştırıcısı kullanabilir (ancak "reddedildi") ayrıştırıcılarını kullanabilir veya bir Earley ayrıştırıcısı çalıştırabilirsiniz. GLR, oldukça iyi bir çözüm olma avantajına sahiptir, iyi belgelenmiştir ve şimdiye kadar iyi kanıtlanmıştır. Bir GLL teknolojisinin GLR'yi sergilemek için bazı önemli avantajları olması gerekir.
Ira Baxter

Rascal projesi (Hollanda) tarayıcısız bir GLL ayrıştırıcısı oluşturduklarını iddia ediyor. Devam eden çalışmalar, çevrimiçi bilgi bulmak zor olabilir. tr.wikipedia.org/wiki/RascalMPL
Ira Baxter

@IraBaxter GLL'de yeni gelişmeler var gibi görünüyor: GLL ile ilgili bu 2010 makalesine bakın dotat.at/tmp/gll.pdf
Sjoerd

6

Bence cevaba oldukça yakınsın.

LR (1), soldan sağa ayrıştırma işleminin içeriğe bakmak için yalnızca bir jetona ihtiyaç duyduğu anlamına gelirken, LR (∞) sonsuz bir ileriye bakış anlamına gelir. Yani, ayrıştırıcı şimdi nerede olduğunu bulmak için gelen her şeyi bilmek zorunda kalacaktı.


4
Derleyici sınıfımdan n> 0 için LR (n) 'nin matematiksel olarak LR (1)' e indirgenebileceğini hatırlıyorum. Bu n = sonsuzluk için doğru değil mi?
rmeador

14
Hayır, n ile sonsuzluk arasındaki farkın aşılamaz bir dağı var.
ephemient

4
Cevap değil mi: Evet, sonsuz zaman verildiğinde? :)
Steve Fallows

7
Aslında, LR (n) -> LR (1) 'in nasıl gerçekleştiğine dair belirsiz hatırlamamla, yeni ara durumlar yaratmayı içerir, bu nedenle çalışma zamanı' n 'nin sabit olmayan bir işlevidir. LR (inf) -> LR (1) tercüme edilmesi sonsuz zaman alacaktır.
Aaron

5
"Cevap değil mi: Evet, sonsuz miktarda zaman verildi mi?" - Hayır: 'sonsuz miktarda zaman verilen' ifadesi, "herhangi bir süre sınırlı yapılamaz" demenin mantıklı olmayan, kısa elli bir yoludur. "Sonsuz" gördüğünüzde, düşünün: "sonlu değil".
ChrisW

4

C ++ 'daki "typedef" sorunu ayrıştırılırken bir sembol tablosu oluşturan bir LALR (1) ayrıştırıcısı ile ayrıştırılabilir (saf bir LALR ayrıştırıcısı değil). "Şablon" sorunu muhtemelen bu yöntemle çözülemez. Bu tür LALR (1) ayrıştırıcısının avantajı, dilbilgisinin (aşağıda gösterilmiştir) bir LALR (1) dilbilgisi (belirsizlik olmaması) olmasıdır.

/* C Typedef Solution. */

/* Terminal Declarations. */

   <identifier> => lookup();  /* Symbol table lookup. */

/* Rules. */

   Goal        -> [Declaration]... <eof>               +> goal_

   Declaration -> Type... VarList ';'                  +> decl_
               -> typedef Type... TypeVarList ';'      +> typedecl_

   VarList     -> Var /','...     
   TypeVarList -> TypeVar /','...

   Var         -> [Ptr]... Identifier 
   TypeVar     -> [Ptr]... TypeIdentifier                               

   Identifier     -> <identifier>       +> identifier_(1)      
   TypeIdentifier -> <identifier>      =+> typedefidentifier_(1,{typedef})

// The above line will assign {typedef} to the <identifier>,  
// because {typedef} is the second argument of the action typeidentifier_(). 
// This handles the context-sensitive feature of the C++ language.

   Ptr          -> '*'                  +> ptr_

   Type         -> char                 +> type_(1)
                -> int                  +> type_(1)
                -> short                +> type_(1)
                -> unsigned             +> type_(1)
                -> {typedef}            +> type_(1)

/* End Of Grammar. */

Aşağıdaki giriş sorunsuz bir şekilde çözümlenebilir:

 typedef int x;
 x * y;

 typedef unsigned int uint, *uintptr;
 uint    a, b, c;
 uintptr p, q, r;

LRSTAR ayrıştırıcı jeneratör yukarıda dilbilgisi notasyonu okur ve bir ayrıştırıcı oluşturduğu kolları ayrıştırma ağacında veya AST Belirsizlik olmadan "typedef" sorunu. (Açıklama: Ben LRSTAR'ı yaratan adam benim.)


Bu, GCC tarafından eski LR ayrıştırıcısı ile "x * y" gibi şeylerin belirsizliğini ele almak için kullanılan standart hack'tir. Ne yazık ki, diğer yapıları ayrıştırmak için keyfi olarak büyük bir ileri okuma gereksinimi var, bu yüzden LR (k) herhangi bir sabit k için bir çözüm olamıyor. (GCC, daha fazla reklam hockery ile özyinelemeli inişe geçti).
Ira Baxter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.