Sözdizimi: BNF, türetme, parse tree ve belirsizlik
BNF ve EBNF ile dil kuralı yazmak, leftmost derivation çıkarmak, parse tree çizmek, belirsizliği ispatlamak ve öncelik/birleşme yönünü gramerden okumak. Vizenin en çok puan getiren bölümü.
Bu konu vizede en çok puan getiren bölüm. Soru tipleri belli: verilen gramerle leftmost derivation yaz, parse tree çiz, bir gramerin belirsiz olduğunu ispatla, gramerden öncelik ve birleşme yönünü oku, EBNF’yi BNF’ye çevir. Hepsi mekanik. Tarifi bilen kaybetmez.
Altın kurallar:
- Leftmost derivation: her satırda sadece en soldaki tek nonterminal’i değiştir.
- Ağaçta aşağıdaki önce hesaplanır. Yüksek öncelikli operatör ağaçta derindedir.
- Sol özyineleme sola birleşme verir, sağ özyineleme sağa birleşme.
- Belirsizlik ispatı = tek cümle + iki farklı ağaç. “Kuralda iki alternatif var” ispat değildir.
- EBNF ifade gücü eklemez. Her EBNF kuralı BNF ile yazılabilir.
Sözdizimi ve anlam
Bir dilin tanımı iki parçadan oluşur. Sözdizimi (syntax) ifadelerin biçimidir: hangi karakter dizileri geçerli program? Anlam (semantics) bu ifadelerin ne yaptığıdır. while (x < 5) x++; cümlesinin doğru yazılmış olması syntax, döngünün x 5 olana kadar artırması semantics.
Programın en küçük anlamlı birimi lexeme’dir: sum, =, 17, ;. Her lexeme bir kategoriye ait, bu kategoriye token denir. sum ve total farklı lexeme ama ikisi de aynı token: IDENT.
| Lexeme | Token |
|---|---|
count | IDENT |
= | ASSIGN_OP |
count | IDENT |
+ | ADD_OP |
1 | INT_LIT |
; | SEMICOLON |
Dili tanımlamanın iki yolu var. Recognizer (tanıyıcı) bir cümleyi alır, “dile ait mi?” diye karar verir; derleyicinin syntax analyzer’ı budur. Generator (üretici) dilin cümlelerini üretir; gramer budur.
BNF
BNF (Backus-Naur Form), 1950’lerin sonunda ALGOL 58/60 için kullanıldı ve Chomsky’nin context-free grammar (bağlamdan bağımsız gramer) sınıfıyla aynı güçtedir. Bir kural (rule, production) şu biçimdedir:
<if_stmt> -> if <logic_expr> then <stmt>
| if <logic_expr> then <stmt> else <stmt>
- Sol taraf (LHS) tek bir nonterminal: köşeli parantezli soyutlama,
<if_stmt>gibi. - Sağ taraf (RHS) terminal ve nonterminal dizisi. Terminal dilin gerçek sembolleri:
if,then,+. |aynı LHS için alternatif demek.- Başlangıç sembolü (start symbol) genelde ilk kuralın LHS’si:
<program>,<assign>.
BNF’de tekrar için döngü yok. Listeler özyineleme (recursion) ile yazılır:
<ident_list> -> ident | ident , <ident_list>
Bu kural x, x , y, x , y , z … üretir.
Türetme (derivation)
Türetme, başlangıç sembolünden başlayıp kuralları uygulayarak cümleye ulaşmaktır. Her ara satır bir sentential form, sadece terminallerden oluşan son satır bir sentence. Standart gramerimiz:
<assign> -> <id> = <expr>
<id> -> A | B | C
<expr> -> <expr> + <term> | <term>
<term> -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>
A = B + C * A için leftmost derivation:
<assign> => <id> = <expr>
=> A = <expr>
=> A = <expr> + <term>
=> A = <term> + <term>
=> A = <factor> + <term>
=> A = <id> + <term>
=> A = B + <term>
=> A = B + <term> * <factor>
=> A = B + <factor> * <factor>
=> A = B + <id> * <factor>
=> A = B + C * <factor>
=> A = B + C * <id>
=> A = B + C * A
13 adım. Hangi kuralı seçeceğine nasıl karar verdin? Cümlenin en üst seviyedeki operatörüne bak. Burada + en üstte (çarpma ondan önce hesaplanacak), o yüzden <expr> önce <expr> + <term> olarak açıldı. B = ( A + C ) * B cümlesinde parantez dışındaki tek operatör *, bu yüzden <expr> => <term> => <term> * <factor> yoluna girersin.
Rightmost derivation aynı işi en sağdaki nonterminal’den başlayarak yapar. Aynı ağaca karşılık gelir, sadece açma sırası farklı:
<assign> => <id> = <expr>
=> <id> = <expr> + <term>
=> <id> = <expr> + <factor>
=> <id> = <expr> + <id>
=> <id> = <expr> + C
...
=> A = B + C
TUZAK · tek satırda tek değişiklik. Bir adımda iki nonterminal’i birden açmak ya da kuralda olmayan kestirme yapmak (
<expr> => Bgibi; arada<term>,<factor>,<id>var) puan kaybettirir. Her satırda tam bir kural uygula.
Parse tree
Türetmenin hiyerarşik hali. Kök başlangıç sembolü, her iç düğüm bir nonterminal ve çocukları uygulanan kuralın RHS’si (soldan sağa), yapraklar terminaller. A = B + C * A için:
<assign>
├── <id> ── A
├── =
└── <expr>
├── <expr> ── <term> ── <factor> ── <id> ── B
├── +
└── <term>
├── <term> ── <factor> ── <id> ── C
├── *
└── <factor> ── <id> ── A
Yaprakları soldan sağa oku: A = B + C * A. Tutuyorsa ağaç doğru. Ağacın bir faydası daha var: anlamı gösterir. C * A alt ağaçta, yani önce hesaplanır, sonucu B’ye eklenir.
Belirsizlik (ambiguity)
Bir gramer, en az bir cümle için iki farklı parse tree üretebiliyorsa belirsizdir. Eşdeğer tanım: o cümlenin iki farklı leftmost derivation’ı vardır.
<E> -> <E> + <E> | <E> * <E> | x
x + x * x için iki ağaç var: kökte + (x + (x * x)) ya da kökte * ((x + x) * x). Değer koyunca fark ortaya çıkar: 2 + 3 * 4 birinci ağaçta 14, ikincide 20. Derleyici hangi kodu üreteceğini bilemez; belirsizliğin neden kötü olduğu bu.
İspat tarifi:
- Aynı operatörden iki tane (
a - b - a) ya da iki farklı operatör (a + b * c) olan kısa bir cümle seç. - İki ağacı çiz, kökteki operatör farklı olsun.
- İki ağacın da yapraklarının aynı cümleyi verdiğini göster.
Belirsizliği gidermek: öncelik ve birleşme yönü
Her öncelik seviyesi için ayrı bir nonterminal kullan. Düşük öncelikli operatör başlangıç sembolüne yakın (ağaçta yukarıda), yüksek öncelikli olan aşağıda.
| Seviye | Nonterminal | Operatör | Kural |
|---|---|---|---|
| en düşük | <expr> | + - | <expr> -> <expr> + <term> |
| orta | <term> | * / | <term> -> <term> * <factor> |
| en yüksek | <factor> | parantez, id | <factor> -> ( <expr> ) |
Birleşme yönü özyinelemenin hangi tarafta olduğundan gelir:
- Sol özyineleme (
<expr> -> <expr> + <term>): LHS, RHS’nin solunda tekrar ediyor. Soldaki işlem derinde kalır. Sola birleşir:9 - 4 - 2 = (9 - 4) - 2 = 3. - Sağ özyineleme (
<t> -> <f> ^ <t>): sağa birleşir:2 ^ 3 ^ 2 = 2 ^ (3 ^ 2) = 512. Üs alma için doğru seçim budur.
<e> -> <e> - <t> | <t> - sola birleşir, düşük öncelik
<t> -> <f> ^ <t> | <f> ^ sağa birleşir, yüksek öncelik
<f> -> NUM
Bu gramerle 10 - 2 ^ 3 - 1 = (10 - (2 ^ 3)) - 1 = 1.
TUZAK · gramer kuralı dayatır. Sınavda bazen “ters” gramer verilir:
<e> -> <e> * <t>,<t> -> <t> + <f>. Burada+daha aşağıda, yani önce hesaplanır:2 * 3 + 4 = 2 * (3 + 4) = 14. Matematikten bildiğin kuralı değil, ağacı uygula. Aynı şekilde<e> -> <t> - <e>sağ özyinelemeli olduğu için10 - 4 - 3 = 10 - (4 - 3) = 9olur.
Dangling else
<stmt> -> if c then <stmt> | if c then <stmt> else <stmt> | s
if c then if c then s else s iki ağaca sahip: else içteki ya da dıştaki if’e ait olabilir. Java, C, C++ kuralı: else en yakın eşleşmemiş if’e bağlanır. Gramerle zorlamak için ifadeler <matched> (her if’in else’i var) ve <unmatched> diye ayrılır; o zaman aynı cümlenin tek ağacı kalır. Python girintiyle, Ruby ve Ada end / end if ile, Perl her dalda süslü parantez zorunluluğuyla sorunu sözdiziminde çözer.
EBNF
EBNF (Extended BNF) gücü artırmaz, kuralları kısaltır ve okunur yapar.
| EBNF | Anlamı | BNF karşılığı |
|---|---|---|
[ X ] | isteğe bağlı (0 ya da 1) | X’li ve X’siz iki alternatif |
{ X } | 0 veya daha fazla tekrar | özyinelemeli yardımcı kural |
( a | b ) | RHS içinde seçim | ayrı alternatifler |
EBNF: <expr> -> <term> { (+ | -) <term> }
BNF: <expr> -> <expr> + <term> | <expr> - <term> | <term>
EBNF: <args> -> ( [ id { , id } ] )
BNF: <args> -> ( ) | ( <list> )
<list> -> id | id , <list>
{ } ile yazılan tekrarı BNF’ye çevirirken sol özyineleme seçmek + ve - için olağan sola birleşmeyi korur. { } “en az bir” demek değildir; sıfır tekrar da dahildir. “En az bir” için X { X } yazılır.
Gramer yazma
Sık çıkan iki kalıp:
bir veya daha fazla a, sonra bir veya daha fazla b:
<S> -> <A> <B>
<A> -> a <A> | a
<B> -> b <B> | b
a^n b^2n, n >= 1 (sayılar bağlı):
<S> -> a <S> b b | a b b
Sayılar bağlıysa (“eşit sayıda”, “iki katı”) iki uç aynı kuralda birlikte üretilir. Bağımsızsa ayrı nonterminal’ler yan yana konur. Yazdıktan sonra iki-üç cümleyle kontrol et ve dile ait olmayan bir cümlenin üretilmediğini de düşün.
Hangi cümleler üretilir?
<S> -> a <S> | <B>
<B> -> b <B> c | b c
Önce dili tarif et: başta sıfır veya daha fazla a, sonra eşit sayıda (en az bir) b ve c. aabc ✔, abbcc ✔, bc ✔, abcc ✘ (b ve c sayısı farklı), aab ✘ (c yok).
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| Leftmost’ta sağdaki nonterminal’i açmak | Her satırda en soldaki tek nonterminal |
<expr> => B gibi kestirme | <expr> => <term> => <factor> => <id> => B |
| “İki alternatif var, o yüzden belirsiz” | Somut bir cümle ve iki farklı ağaç göster |
| Aynı yapıda iki ağaç çizmek | Kökteki operatör ya da gruplama farklı olmalı |
Ağaçta terminalleri unutmak (=, +, parantez) | Her RHS sembolü bir çocuk olur |
{ X } = en az bir | Sıfır veya daha fazla |
| Ters gramerde matematik önceliğini uygulamak | Ağaçtaki derinlik belirler |
| Sağ özyineleme ile çıkarma yazmak | 10 - 4 - 3 = 9 olur; sol özyineleme kullan |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Using the grammar below, how many derivation steps (⇒) does the leftmost derivation of A = B + C * A take?
<assign> -> <id> = <expr>
<id> -> A | B | C
<expr> -> <expr> + <term> | <term>
<term> -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>Cevabı göster
Ağaçtaki her iç düğüm (nonterminal) bir ⇒ adımıdır. Leftmost türetme:
<assign> => <id> = <expr> => A = <expr>
=> A = <expr> + <term> => A = <term> + <term>
=> A = <factor> + <term> => A = <id> + <term> => A = B + <term>
=> A = B + <term> * <factor> => A = B + <factor> * <factor>
=> A = B + <id> * <factor> => A = B + C * <factor>
=> A = B + C * <id> => A = B + C * AToplam 13 ok. En üstte + var çünkü * daha derinde (<term> seviyesinde) üretilir.Give the first four sentential forms of the leftmost derivation of B = ( A + C ) * B with the same grammar.
<assign> -> <id> = <expr>
<id> -> A | B | C
<expr> -> <expr> + <term> | <term>
<term> -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>Cevabı göster
En üstteki işlem * (parantez dışındaki tek operatör). Bu yüzden <expr> önce tek bir <term> olmalı, + kuralı burada seçilmez. Devamı: B = <term> * <factor>, B = <factor> * <factor>, B = ( <expr> ) * <factor> ... Toplam 16 adım sürer.
Which of the following sentences are generated by the grammar? (a) aabc (b) abbcc (c) abcc (d) bc (e) aab
<S> -> a <S> | <B>
<B> -> b <B> c | b cCevabı göster
<S> başa istediği kadar a koyar (sıfır dahil). <B> eşit sayıda b ve c üretir, en az bir çift. Dil: , , .
(a) aa + bc ✔. (b) a + bbcc ✔. (c) 1 b, 2 c: eşit değil ✘. (d) bc ✔. (e) c yok ✘.
How many distinct parse trees does the sentence x + x * x have in this grammar? Is the grammar ambiguous?
<E> -> <E> + <E> | <E> * <E> | xCevabı göster
Ağaç 1: kökte +, sağ çocuk x * x (yani x + (x * x)). Ağaç 2: kökte *, sol çocuk x + x (yani (x + x) * x). Tek bir cümlenin iki farklı ağacı olduğu için gramer belirsizdir (ambiguous). Sayılarla: 2 + 3 * 4 birinci ağaçta 14, ikincide 20 verir.
In the same ambiguous grammar <E> -> <E> + <E> | <E> * <E> | x, how many parse trees does x + x + x + x have?
Cevabı göster
Üç + operatöründen hangisinin köke çıkacağı serbest. Kökte 1. +: sağdaki x + x + x için 2 ağaç. Kökte 2. +: solda 1, sağda 1 ağaç → 1. Kökte 3. +: solda 2 ağaç. Toplam 2 + 1 + 2 = 5 (Catalan sayısı ).
Using the grammar below, evaluate 2 ^ 3 ^ 2 and 9 - 4 - 2 according to their parse trees. What is the associativity of ^ and of -?
<e> -> <e> - <t> | <t>
<t> -> <f> ^ <t> | <f>
<f> -> NUMCevabı göster
<t> -> <f> ^ <t> sağ özyinelemeli (right recursive): sağdaki ^ ağaçta daha derinde kalır, önce o hesaplanır: 2 ^ (3 ^ 2) = 2 ^ 9 = 512.<e> -> <e> - <t> sol özyinelemeli (left recursive): soldaki çıkarma derinde: (9 - 4) - 2 = 3.
Ayrıca ^, <t> seviyesinde yani -'den aşağıda olduğu için daha yüksek önceliklidir.
In the grammar below, what value does the parse tree give for 2 * 3 + 4?
<e> -> <e> * <t> | <t>
<t> -> <t> + <f> | <f>
<f> -> NUMCevabı göster
Bu gramer alışılmışın tersi: +, <t> seviyesinde yani ağaçta daha aşağıda, bu yüzden önce hesaplanır. Ağaç 2 * (3 + 4) yapısını kurar: 2 * 7 = 14. Gramer, matematikteki kuralı değil kendi yapısını dayatır.
With the right-recursive grammar below, what is the value of 10 - 4 - 3?
<e> -> <t> - <e> | <t>
<t> -> NUMCevabı göster
Sağ özyineleme sağa birleşme verir: 10 - (4 - 3) = 10 - 1 = 9. Matematikteki (10 - 4) - 3 = 3 değil. Çıkarmayı doğru modellemek için <e> -> <e> - <t> | <t> (sol özyineleme) yazılmalı.
Convert the EBNF rule to BNF: <args> -> ( [ id { , id } ] )
Cevabı göster
[ ... ] isteğe bağlı: parantezin içi boş ya da dolu, iki alternatif. { , id } sıfır veya daha fazla tekrar: özyinelemeli bir yardımcı nonterminal <list> ile yazılır. Üretilen cümleler: ( ), ( id ), ( id , id ), ...
Write a BNF grammar for the language , (e.g. abb, aabbbb).
Cevabı göster
Sayılar bağlı olduğu için a ve b'ler aynı kuralda birlikte eklenmeli: her uygulama sola bir a, sağa iki b koyar. Taban a b b, böylece n = 1'den başlar. Kontrol: aabbbb: S ⇒ aSbb ⇒ aabbbb ✔. <A> <B> gibi bağımsız iki parça yazmak sayıları eşitlemez.
How many parse trees does if c then if c then s else s have in this grammar, and which language rule resolves the problem in Java?
<stmt> -> if c then <stmt> | if c then <stmt> else <stmt> | sCevabı göster
Ağaç 1: else içteki if'e ait. Ağaç 2: else dıştaki if'e ait. İki ağaç = belirsiz (dangling else). Java, C, C++: else en yakın eşleşmemiş if'e bağlanır. Gramerde bunu matched/unmatched ayrımıyla zorlayınca aynı cümlenin tek ağacı kalır.
Give the second and third sentential forms of the rightmost derivation of A = B + C in the assignment grammar (expr/term/factor).
Cevabı göster
Rightmost'ta her adımda en sağdaki nonterminal açılır. <assign> ⇒ <id> = <expr> ⇒ <id> = <expr> + <term> ⇒ <id> = <expr> + <factor> ⇒ <id> = <expr> + <id> ⇒ <id> = <expr> + C ⇒ ... En soldaki <id> en son, son adımda A olur.
Doğrulama: Tüm türetmeler, ağaç sayıları ve gramerden hesaplanan değerler node ile kontrol edildi: verify/SEN2001/lib/grammar.mjs bütün parse tree'leri sayar, leftmost/rightmost türetmeyi yazar ve ağaçtan değer hesaplar (verify/SEN2001/02.mjs). EBNF/BNF eşdeğerliği uzunluk 7'ye kadar bütün dizilerle kaba kuvvetle denendi.