Layman'ın şartlarına uydurmaktan bir bıçak alacağım.
Ayrıştırma ağacı açısından düşünürseniz (AST değil, ayrıştırıcının ziyareti ve girdinin genişlemesi), sol özyineleme sola ve aşağı doğru büyüyen bir ağaç ile sonuçlanır. Doğru özyineleme tam tersidir.
Örnek olarak, bir derleyicideki ortak dilbilgisi bir öğeler listesidir. Dizelerin bir listesini ("kırmızı", "yeşil", "mavi") alıp ayrıştıralım. Dilbilgisini birkaç yolla yazabilirim. Aşağıdaki örnekler sırasıyla doğrudan sol veya sağ özyinelemelidir:
arg_list: arg_list:
STRING STRING
| arg_list ',' STRING | STRING ',' arg_list
Bu ayrıştırma için ağaçlar:
(arg_list) (arg_list)
/ \ / \
(arg_list) BLUE RED (arg_list)
/ \ / \
(arg_list) GREEN GREEN (arg_list)
/ /
RED BLUE
Özyineleme yönünde nasıl büyüdüğüne dikkat edin.
Bu gerçekten bir sorun değil, ayrıştırıcı aracınız bu sorunu çözebiliyorsa, bir özyinelemeli dilbilgisi yazmak iyi olur. Aşağıdan yukarıya ayrıştırıcılar iyi işliyor. Böylece daha modern LL ayrıştırıcılar olabilir. Özyinelemeli dilbilgisi ile ilgili sorun özyineleme değildir, ayrıştırıcıyı ilerletmeden özyineleme veya bir jeton tüketmeden özyineleme. Dinlediğimizde daima en az 1 jeton tüketirsek, sonunda ayrışmanın sonuna ulaşırız. Sol özyineleme, sonsuz bir döngü olan tüketmeden özyineleme olarak tanımlanır.
Bu sınırlama, saf bir yukarıdan aşağı LL ayrıştırıcısı (özyinelemeli iniş ayrıştırıcısı) ile bir dilbilgisi uygulanmasının bir uygulama detayıdır. Sol özyinelemeli dilbilgileriyle uğraşmak istiyorsanız, tekrarlamadan önce en az 1 jeton tüketmek için üretimi yeniden yazarak bununla başa çıkabilirsiniz, böylece bu, üretken olmayan döngüye asla sıkışmamamızı sağlar. Sol yinelemeli olan herhangi bir dilbilgisi kuralı için, yinelemeli prodüksiyonlar arasında bir jeton tüketerek dilbilgisini yalnızca bir ileriye doğru düzleştiren bir ara kural ekleyerek yeniden yazabiliriz. (NOT: Dilbilgisini yeniden yazmanın tek yolu veya tercih edilen yol olduğunu söylemiyorum, sadece genelleştirilmiş kuralı göstererek. Bu basit örnekte, en iyi seçenek sağ özyinelemeli formu kullanmaktır). Bu yaklaşım genelleştirildiğinden, bir ayrıştırıcı jeneratör (teorik olarak) programlayıcıyı dahil etmeden uygulayabilir. Uygulamada, ANTLR 4'ün şimdi tam da bunu yaptığını düşünüyorum.
Yukarıdaki dilbilgisi için, sol özyineleme gösteren LL uygulaması şöyle görünecektir. Ayrıştırıcı, bir listeyi tahmin etmekle başlar ...
bool match_list()
{
if(lookahead-predicts-something-besides-comma) {
match_STRING();
} else if(lookahead-is-comma) {
match_list(); // left-recursion, infinite loop/stack overflow
match(',');
match_STRING();
} else {
throw new ParseException();
}
}
Gerçekte, gerçekten uğraştığımız şey "saf uygulama" dır. başlangıçta belirli bir cümleyi belirledik, sonra tekrarlayarak bu tahminin işlevini çağırdık ve bu işlev naif olarak aynı öngörüyü tekrar çağırıyor.
Aşağıdan yukarıya ayrıştırıcıların her iki yönde de yinelenen kurallar sorunu yoktur, çünkü bir cümlenin başlangıcını yeniden çözmezler, cümleyi tekrar bir araya getirerek çalışırlar.
Bir dilbilgisinde özyineleme yalnızca yukarıdan aşağıya ürettiğimizde bir sorundur, yani. ayrıştırıcımız, jetonları tüketirken tahminlerimizi "genişleterek" çalışır. Genişletmek yerine, bir LALR (Yacc / Bison) aşağıdan yukarıya ayrıştırıcıda olduğu gibi daraltırız (üretimler "azalır"), o zaman her iki tarafın tekrarlaması bir sorun değildir.
::=
gelenExpression
etmekTerm
ve ilk sonra aynı yaptıysa||
, artık sol özyinelemeli olurdu? Yalnızca yaptıysa Ama bundan sonra::=
, ancak||
, yine de sol özyinelemeli olurdu?