SEN2001 · 2. hafta · Describing Syntax and Semantics I

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ü.

syntaxsemanticslexemetokenBNFEBNFnonterminalterminalderivationleftmost derivationsentential formparse treeambiguous grammaroperator precedenceassociativitydangling else

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:

  1. Leftmost derivation: her satırda sadece en soldaki tek nonterminal’i değiştir.
  2. Ağaçta aşağıdaki önce hesaplanır. Yüksek öncelikli operatör ağaçta derindedir.
  3. Sol özyineleme sola birleşme verir, sağ özyineleme sağa birleşme.
  4. Belirsizlik ispatı = tek cümle + iki farklı ağaç. “Kuralda iki alternatif var” ispat değildir.
  5. 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.

LexemeToken
countIDENT
=ASSIGN_OP
countIDENT
+ADD_OP
1INT_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> => B gibi; 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:

  1. Aynı operatörden iki tane (a - b - a) ya da iki farklı operatör (a + b * c) olan kısa bir cümle seç.
  2. İki ağacı çiz, kökteki operatör farklı olsun.
  3. İ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.

SeviyeNonterminalOperatörKural
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çin 10 - 4 - 3 = 10 - (4 - 3) = 9 olur.

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.

EBNFAnlamı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çimayrı 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çmakHer 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ç çizmekKökteki operatör ya da gruplama farklı olmalı
Ağaçta terminalleri unutmak (=, +, parantez)Her RHS sembolü bir çocuk olur
{ X } = en az birSıfır veya daha fazla
Ters gramerde matematik önceliğini uygulamakAğaçtaki derinlik belirler
Sağ özyineleme ile çıkarma yazmak10 - 4 - 3 = 9 olur; sol özyineleme kullan

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1orta

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
13

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 * A
Toplam 13 ok. En üstte + var çünkü * daha derinde (<term> seviyesinde) üretilir.

Soru 2orta

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
<assign> ⇒ <id> = <expr> ⇒ B = <expr> ⇒ B = <term>

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.

Soru 3kolay

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 c
Cevabı göster
(a), (b), (d)

<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: ambncna^m b^n c^n, m≥0m \ge 0, n≥1n \ge 1.
(a) aa + bc ✔. (b) a + bbcc ✔. (c) 1 b, 2 c: eşit değil ✘. (d) bc ✔. (e) c yok ✘.

Soru 4orta

How many distinct parse trees does the sentence x + x * x have in this grammar? Is the grammar ambiguous?

<E> -> <E> + <E> | <E> * <E> | x
Cevabı göster
2; yes, ambiguous

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.

Soru 5zor

In the same ambiguous grammar <E> -> <E> + <E> | <E> * <E> | x, how many parse trees does x + x + x + x have?

Cevabı göster
5

Üç + 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ı C3C_3).

Soru 6orta

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> -> NUM
Cevabı göster
512 and 3; ^ is right associative, - is left associative

<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.

Soru 7zor

In the grammar below, what value does the parse tree give for 2 * 3 + 4?

<e> -> <e> * <t> | <t>
<t> -> <t> + <f> | <f>
<f> -> NUM
Cevabı göster
14

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.

Soru 8orta

With the right-recursive grammar below, what is the value of 10 - 4 - 3?

<e> -> <t> - <e> | <t>
<t> -> NUM
Cevabı göster
9

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ı.

Soru 9kolay

Convert the EBNF rule to BNF: <args> -> ( [ id { , id } ] )

Cevabı göster
<args> -> ( ) | ( <list> ) ; <list> -> id | id , <list>

[ ... ] 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 ), ...

Soru 10orta

Write a BNF grammar for the language anb2na^n b^{2n}, n≥1n \ge 1 (e.g. abb, aabbbb).

Cevabı göster
<S> -> a <S> b b | a b b

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.

Soru 11orta

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> | s
Cevabı göster
2; else matches the nearest unmatched if

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.

Soru 12zor

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
<id> = <expr> + <term> ; <id> = <expr> + <factor>

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.

Deftere not kartı

syntax = biçim, semantics = anlam. lexeme: sum, + token: IDENT, ADD_OP BNF: LHS -> RHS, | alternatif, liste = özyineleme: <L> -> id | id , <L> leftmost: her adımda EN SOLDAKİ TEK nonterminal. adım sayısı = iç düğüm sayısı ara satır = sentential form, son satır (sadece terminal) = sentence parse tree: kök = başlangıç sembolü, yapraklar soldan sağa = cümle ambiguous = TEK cümle + İKİ farklı ağaç (veya iki farklı leftmost türetme) aday cümleler: a - b - a , a + b * c öncelik: aşağıdaki önce hesaplanır. düşük öncelik başlangıca yakın (<expr>) E -> E + T | T ; T -> T * F | F ; F -> ( E ) | id sol özyineleme = sola birleşme ; sağ özyineleme = sağa birleşme (**, ^) EBNF: [X] = 0/1 (iki alternatif) {X} = 0+ (özyineleme) (a|b) = seçim EBNF güç eklemez, sadece kısaltır dangling else: Java/C'de en yakın eşleşmemiş if