|
Derleyici Tasarımı
|
librdesc dokümantasyon linkini referans için tekrardan veriyorum: https://metwse.github.io/rdesc/
En basit anlamıyla rdesc ve rdesc_grammar initialize etmek için
ve temizlemek için
kullanıyoruz.
Hatırlarsanız, librdesc'te gramer kurallarımızı, top-down parser'ı yazarkenkinden farklı olarak
ekstra nonterminaller tanımlayarak
yapmıştık. Detayları önemli değil, ancak ikinci yöntemin daha optimize olduğunu (ya da daha kolay optimize edilebildiğini) bilseniz iyi olur. İkinci yöntemin kullanımını standardize etmek için librdesc, rrr makrosunu sunar:
(librdesc dokümantasyonundan)
Right-Recursive liste kuralı tanımlar. İki nonterminal oluşturur: liste head'i (base'i içerir) ve onun devamı (tekrarlı suffix). rrr(A, (β), (α)) şuna eşittir:
rdesc_flip_left(), bu iki production rule ile oluşturulmuş bir subtree'yi, aşağıdaki kurala çevirir:
rrr() makrosunu hem toplama hem de çıkarma işlemi için kullanırken rrr(EXPR, (NT(TERM)), (NT(EXPR_OP), NT(TERM))) kullanacağız. Sonuç olarak elde edeceğimiz kurallar:
Eş işlem önceliğine sahip operatörleri tek bir nonterminal içerisinde gruplayarak rrr() ile kullanabiliriz. Fark ettiyseniz rrr()'de tekrarlı suffix'ler arasına birden fazla çeşit tokeni direkt koymanın yöntemi yok, yardımcı bir nonterminal tanımlamak gerekti.
Opsiyonel production rule tanımlamak için. Nonterminal içinde iki alternatif oluşturur. ropt(α) şuna eşittir:
Bu nispeten çok daha basit bir makro. Aşağıdaki production rules'ı tekrar tekrar yazmak yerine ropt() kullanacağız.
yazmak yerine ropt(NT(EXPR_OP)) yazacağız.
Node'ların tuttuğu veriye erişmeyi aşağıdaki tablo net bir şekilde özetliyor:
| Dönüt Tipi | Macro | Açıklama |
|---|---|---|
| node | rparent(n) | Ağaçtaki bir üst node'u döner. Root için kullanmayın. |
RDESC_TOKEN/RDESC_NONTERMINAL | rtype(n) | Nonterminal-token düzeyinde node tipi. |
| int | rid(n) | Match edilmiş token ya da nonterminal'in ID'si. Örneğin TK_INT ya da NT_EXPR. |
| int | ralt_idx(n) | Nonterminal'in kaçıncı alternatifinin match edildiğini verir. |
void * | rseminfo(n) | Token node'un seminfo'suna işaret eden pointer döner. |
| int | rchild_count(n) | Nonterminal node'un kaç child'ı oluğunu döner. |
| node | rchild(n, i) | Nonterminal node'un i. çocuğunu döner. |
Şimdi node_printer() daha anlamlı gelmiştir.
Şimdiye kadar gramerlerde alternatifleri | operatörüyle gösterdik. librdesc, alternatifleri sırayla denenir ve ilk eşleşen seçilir. Buna ordered-choice denir ve / operatörüyle gösterilir.
Bu fark kritik, ancak burada detaylarına girmeyeceğiz. Kısaca, başta konuştuğumuz CFG'de | belirsizliğe (ambiguity) yol açabilir. Detaylarını merak edenler, Dangling Else Problem'a göze atabilir.
Buradan itibaren gramerlerimizi librdesc'in notasyonuyla yazacağız:
| Eski (CFG) | Yeni (librdesc) |
|---|---|
⟨Expr⟩ → ... | <expr> ::= ... |
\| (alternatif) | / (ordered-choice) |
⟨Term⟩ | <term> |
+, -, int | "+", "-", int |
Artık Tree-Walk Interpreter yazmaya başlayabiliriz!