İçeriğe geç
academia.sh

Ders 14 / 16

Derleme Aşamaları

Sözcüksel çözümleme, ayrıştırma, anlam denetimi, ara gösterim ve kod üretimi adımları.

İçindekiler

Önceki ders, çevirinin ne zaman yapıldığına göre üç model tanımladı. Bu ders, çevirinin kendisini açar: kaynak metin hangi adımlardan geçerek komutlara dönüşür?

Adımlar hemen her gerçekleştirimde aynıdır. Bir yorumlayıcı ile bir derleyici, ilk aşamalarda aynı işi yapar; yollar sondaki üretim adımında ayrılır.

Sözcüksel Çözümleme

İlk aşama, karakter dizisini anlamlı en küçük birimlere ayırır. Bu birimlere sözcük birimi (token) denir.

toplam = 3 + 4 * 2 satırı şu birimlere ayrılır:

Birim Tür
toplam ad
= atama işleci
3 sayı sabiti
+ toplama işleci
4 sayı sabiti
* çarpma işleci
2 sayı sabiti

Bu aşamada boşluklar ve yorum satırları atılır; sayı sabitleri, karakter dizisinden sayısal değere çevrilir. Sözcüksel çözümleyici yapıyla ilgilenmez: 3 + * 4 dizisi de sorunsuz biçimde birimlere ayrılır, çünkü her birim tek başına geçerlidir.

Bu aşamada üretilen hatalar, dilin alfabesine ait olmayan karakterler veya kapatılmamış dizgi sabitleri gibi durumlardır.

Ayrıştırma

İkinci aşama, sözcük birimlerinin dilin dilbilgisine uyup uymadığını denetler ve uyuyorsa yapıyı ağaç biçiminde kurar. Bu ağaca soyut sözdizim ağacı (abstract syntax tree) denir.

3 + 4 * 2 ifadesinin ağacı, işleç önceliğini yapısına gömer:

      +
     / \
    3   *
       / \
      4   2

Çarpmanın toplamadan önce yapılması, ağaçta çarpma düğümünün daha derinde olmasıyla kodlanmıştır: bir düğümün değeri hesaplanmadan üstündeki düğüm hesaplanamaz. Öncelik kuralları, dilbilgisinin yazılış biçimiyle sağlanır; ayrı bir “öncelik denetimi” adımı yoktur.

Ayrıştırma aşamasının hataları, birimlerin sırasına ilişkindir: kapatılmamış parantez, eksik işlenen, beklenmeyen anahtar sözcük.

Anlam Denetimi

Sözdizimi doğru olan her program anlamlı değildir. toplam = tanimsiz_ad + 1 satırı dilbilgisine uyar, ancak adın neye karşılık geldiği bilinmiyorsa yürütülemez.

Bu aşamada derleyici bir simge tablosu kurar: her adın nerede tanımlandığını, türünü ve kapsamını tutar. Ağaç bu tabloya karşı denetlenir:

  • Kullanılan adlar tanımlı mı?
  • İşleçlerin işlenen türleri uyumlu mu?
  • Atamalarda tür dönüşümü geçerli mi?
  • Fonksiyon çağrılarında argüman sayısı ve türleri doğru mu?

Statik tipli dillerde bu denetimlerin çoğu derleme sırasında yapılır. Dinamik tipli dillerde tür bilgisi çalışma anında değerlerin üzerinde taşınır; aynı hatalar, ilgili satır yürütüldüğünde ortaya çıkar. İki yaklaşım arasındaki fark, hatanın ne zaman görüldüğüdür.

Ara Gösterim ve Eniyileme

Ağaç doğrudan makine koduna çevrilebilir, ancak çoğu derleyici araya bir katman koyar: belirli bir işlemciye bağlı olmayan ara gösterim (intermediate representation).

Ara gösterimin iki yararı vardır. Eniyilemeler burada, hedef makineden bağımsız olarak yazılır; ve mm dil ile nn hedef mimari için m×nm \times n değil, m+nm + n bileşen yazmak yeterli olur.

Bu düzeydeki tipik eniyilemeler:

  • Sabit katlama: 3 + 4 * 2 ifadesi çalışma anında değil, derleme sırasında hesaplanır ve yerine 11 konur.
  • Ölü kod eleme: Sonucu hiçbir yerde kullanılmayan hesaplar çıkarılır.
  • Ortak alt ifade eleme: Aynı hesabın tekrarı bir kez yapılıp saklanır.
  • Döngü değişmezini dışarı alma: Döngü boyunca değişmeyen hesap, döngü öncesine taşınır.

Eniyilemenin sınırı, programın gözlenebilir davranışını değiştirmemektir. Bu kısıt, kayan noktalı aritmetiğin neden serbestçe yeniden sıralanamayacağını açıklar: toplama birleşme özelliği taşımadığından, sıra değişimi sonucu değiştirebilir.

Kod Üretimi

Son aşama, ara gösterimi hedef komut kümesine çevirir. Burada mimariye özgü kararlar verilir:

  • Komut seçimi: Aynı işlem birden çok komut dizisiyle yapılabilir; en ucuzu seçilir.
  • Yazmaç ayırma: Değerlerin hangisi yazmaçta tutulacak, hangisi belleğe taşınacak. Yazmaç sayısı sınırlı olduğundan bu, derleyicinin en zorlu görevlerinden biridir.
  • Komut sıralama: Boru hattının boşta kalmaması için komutlar yeniden sıralanabilir.

Bu aşamada uyulması gereken bir dış kural daha vardır: çağrı sözleşmesi (calling convention). Sözleşme, argümanların hangi yazmaçlara veya yığıt konumlarına yerleştirileceğini, dönüş değerinin nerede taşınacağını ve hangi yazmaçları çağrılan tarafın koruyacağını belirler. Ayrı ayrı derlenmiş dosyaların birbirini çağırabilmesi, hepsinin aynı sözleşmeye uymasına bağlıdır; sözleşme derleyicinin değil, hedef platformun tanımıdır.

Yorumlayıcılar bu aşamayı atlar: ağacı veya bayt kodu doğrudan yürütür. Sanal makine modelinde ise hedef, gerçek bir işlemci değil sanal makinenin komut kümesidir.

Hata Bildirimi ve Kurtarma

Bir derleyici, ilk hatada durup çıkabilir. Bu davranış geliştirici için verimsizdir: her hata ayrı bir derleme turu gerektirir. Bu nedenle ayrıştırıcılar hata kurtarma uygular — hatayı bildirir, tutarlı bir noktaya kadar birimleri atar ve çözümlemeyi sürdürür.

Kurtarmanın bedeli, ardışık hatalardır. Kapatılmamış tek bir parantez, sonraki yapıların yanlış konumda görünmesine ve onlarca ek hata iletisine yol açabilir. Pratik kural şudur: iletilerin ilki, en güvenilir olanıdır; sonrakiler ilkinin türevi olabilir.

Hata iletisinin niteliği de bir tasarım işidir. İyi bir ileti üç şeyi söyler: sorunun ne olduğunu, nerede oluştuğunu ve ne beklendiğini. Bunun için ayrıştırıcı, ürettiği her düğümde kaynak konumunu (satır ve sütun) taşır; bu bilgi ağaç boyunca korunur ve anlam denetimi aşamasında da kullanılır.

Aşamaları Programla Görmek

Aşağıdaki program, ilk iki aşamayı küçük bir ifade dili üzerinde gerçekler ve üçüncü aşamayı standart kütüphaneyle gösterir:

import re, ast

DESEN = re.compile(r"\s*(?:(\d+)|([A-Za-z_]\w*)|(.))")

def sozcuklere_ayir(metin: str) -> list[tuple[str, str]]:
    """Kaynak metni (tür, değer) çiftlerine ayırır."""
    birimler = []
    for sayi, ad, simge in DESEN.findall(metin):
        if sayi:
            birimler.append(("sayi", sayi))
        elif ad:
            birimler.append(("ad", ad))
        elif simge.strip():
            birimler.append(("simge", simge))
    return birimler


print(sozcuklere_ayir("toplam = 3 + 4 * 2"))
# [('ad', 'toplam'), ('simge', '='), ('sayi', '3'), ('simge', '+'),
#  ('sayi', '4'), ('simge', '*'), ('sayi', '2')]

agac = ast.parse("3 + 4 * 2", mode="eval")
print(ast.dump(agac.body, annotate_fields=False))
# BinOp(Constant(3), Add(), BinOp(Constant(4), Mult(), Constant(2)))

ast.dump çıktısı, ayrıştırma bölümündeki ağacın metin biçimidir: dıştaki düğüm toplama, sağ işleneni ise çarpma düğümüdür. Öncelik, ağacın biçimine yerleşmiştir.

Aynı programda sözcüksel çözümleyicinin 3 + * 4 girdisini sorunsuz ayırdığı, ancak ast.parse çağrısının aynı girdide sözdizim hatası verdiği denenebilir; bu, iki aşamanın farklı sorular sorduğunun doğrudan gösterimidir.

Özet

  • Sözcüksel çözümleme, karakter dizisini sözcük birimlerine ayırır ve boşlukları atar.
  • Ayrıştırma, birimlerin dilbilgisine uygunluğunu denetler ve soyut sözdizim ağacını kurar; işleç önceliği ağacın biçiminde kodlanır.
  • Anlam denetimi, simge tablosuna karşı ad ve tür uyumunu sınar; statik ve dinamik tipli diller bu denetimi farklı zamanlarda yapar.
  • Ara gösterim, eniyilemeleri hedef mimariden bağımsız kılar ve dil–mimari bileşimlerinin sayısını azaltır.
  • Kod üretimi komut seçimi, yazmaç ayırma ve komut sıralama kararlarını verir.
  • Bir derleme hatası, üretildiği aşama bilinerek daha çabuk okunur.

Sonraki Adım

Kod üretimi, her kaynak dosya için ayrı bir çıktı verir. Ancak bir program tek dosyadan oluşmaz ve kullandığı kütüphaneler başka yerlerde derlenmiştir. Sonraki ders, bu parçaların tek bir çalıştırılabilir bütüne nasıl bağlandığını ve çalışma anında belleğe nasıl yüklendiğini ele alacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat