Programlama dilleri fonksiyonları nasıl tanımlar?


28

Programlama dilleri fonksiyonları / yöntemleri nasıl tanımlar ve kaydeder? Ruby'de tercüme edilmiş bir programlama dili oluşturuyorum ve işlev bildiriminin nasıl uygulanacağını bulmaya çalışıyorum.

İlk fikrim, bildirimin içeriğini bir haritaya kaydetmek. Mesela ben böyle bir şey yaptıysam

def a() {
    callSomething();
    x += 5;
}

Sonra haritama bir giriş eklerdim:

{
    'a' => 'callSomething(); x += 5;'
}

Bununla ilgili sorun yinelemeli hale gelmesidir, çünkü ipte parseyöntemimi çağırmam gerekecek, sonra parsekarşılaştığında tekrar arayacaktım.doSomething ve sonunda yığın alanım bitecek.

Peki, yorumlanmış diller bunu nasıl ele alıyor?


Oh, ve bu benim Programcılarımdaki ilk yazım. Bu yüzden, yanlış bir şey yaptığımı veya konu dışı olduğunu lütfen bana bildirin. :)
Doorknob

Geçmişte hepsini belirteçlerimin içinde satır içi olarak sakladım ve işlev çağrıları sadece belirli bir ofsetle (montajdaki etiketlere benzer) atlar. Senaryoyu belirliyor musun? Veya her seferinde dizeleri ayrıştırmak?
Simon Whitehead

@SimonWhitehead Dizgiyi belirteçlere bölüştürür ve ardından her belirteci ayrı ayrı ayrıştırırım.
Doorknob

3
Dil tasarımını ve uygulamasını programlama konusunda yeniyseniz, konuyla ilgili literatürden bazılarını kontrol etmek isteyebilirsiniz. En popüler olanı "Ejderha Kitabı" dır: en.wikipedia.org/wiki/… , ancak çok iyi olan başka, daha özlü metinler de vardır. Örneğin, Aarne Ranta tarafından Programlama Dillerinin Uygulanması buradan ücretsiz olarak temin edilebilir: bit.ly/15CF6gC .
evilcandybag

1
@ddyer Teşekkürler! Farklı dillerde lisp tercüman için googled ve bu gerçekten yardımcı oldu. :)
Doorknob

Yanıtlar:


31

"Ayrıştırma" işlevinizin yalnızca kodu ayrıştırmakla kalmayıp aynı zamanda aynı zamanda yürütmesini de kabul etmek doğru olur mu? Bu şekilde yapmak istiyorsanız, bir fonksiyonun içeriğini haritanıza kaydetmek yerine, konumu saklayın. .

Ama daha iyi bir yol var. Önceden biraz daha fazla çaba gerektirir, ancak karmaşıklık arttıkça daha iyi sonuçlar verir: bir Özet Sözdizimi Ağacı kullanın.

Temel fikir, kodu yalnızca bir kez, ancak bir kez ayrıştırabilmenizdir. Ardından, işlemleri ve değerleri temsil eden bir dizi veri türünüz vardır ve bunlardan bir ağacı oluşturursunuz;

def a() {
    callSomething();
    x += 5;
}

dönüşür:

Function Definition: [
   Name: a
   ParamList: []
   Code:[
      Call Operation: [
         Routine: callSomething
         ParamList: []
      ]
      Increment Operation: [
         Operand: x
         Value: 5
      ]
   ]
]

(Bu sadece varsayımsal bir AST'nin yapısının bir metin temsilidir. Gerçek ağaç muhtemelen metin biçiminde olmaz.) Her neyse, kodunuzu bir AST'ye bölüştürür ve sonra tercümanınızı doğrudan AST üzerinden çalıştırırsınız, veya AST'yi bir çıktı formuna dönüştürmek için ikinci bir kod ("kod oluşturma") kullanın.

Dilinizde muhtemelen yapacağınız şey, işlev isimlerini işlev dizeleri yerine işlev isimlerini AST işlevine eşleyen bir haritaya sahip olmaktır.


Tamam, ama sorun hala orada: özyinelemeyi kullanıyor. Bunu yaparsam sonunda yığın alanı biter.
Doorknob

3
@Doorknob: Özyinelemeyi, özellikle neler kullanır? Herhangi bir blok yapılı programlama dili ( ASM'den daha yüksek bir seviyedeki her modern dil) doğal olarak ağaç tabanlıdır ve bu nedenle doğada özyinelemelidir. Yığın taşması konusunda hangi özel yönlerden endişeleniyorsunuz?
Mason Wheeler

1
@Doorknob: Evet, makine koduna göre derlenmiş olsa bile, herhangi bir dilin doğal özelliği. (Çağrı yığını bu davranışın tezahürüdür.) Aslında tarif ettiğim gibi çalışan bir betik sistemine katkıda bulunuyorum. Bana chat.stackexchange.com/rooms/10470/… adresinde sohbete katılın ve verimli yorumlama ve istifleme boyutu üzerindeki etkiyi en aza indirgeme konusunda bazı teknikleri tartışacağım. :)
Mason Wheeler

2
@Doorknob: Burada tekrarlama sorunu yoktur, çünkü AST'deki işlev çağrısı işlevin adıyla başvuruda bulunur, asıl işleve bir başvuru gerekmez . Makine kodunu derliyorsanız, sonunda işlev adresine ihtiyacınız olacaktır, bu nedenle çoğu derleyici birden çok geçiş yapar. Tek geçişli bir derleyiciye sahip olmak istiyorsanız , derleyicinin önceden adres atayabilmesi için tüm işlevlerin "ileri bildirimlerini" yapmanız gerekir. Bytecode derleyicileri bununla bile uğraşmaz, jitter ad araması yapar.
Aaron,

5
@Doorknob: Gerçekten özyinelemeli. Ve evet, yığında yalnızca 16 giriş varsa, ayrıştırmazsınız (((((((((((((((( x ))))))))))))))))). Gerçekte, yığınlar çok daha büyük olabilir ve gerçek kodun gramer karmaşıklığı oldukça sınırlıdır. Elbette bu kodun insan tarafından okunabilir olması gerekiyorsa.
MSalters

4

Sen görünce ayrıştırma çağıran edilmemelidir callSomething()(Ne demek tahmin callSomethingziyade doSomething). Arasındaki fark avecallSomething diğer bir yöntem çağrısı ise bu bir olan bir yöntem tanımıdır.

Yeni bir tanım gördüğünüzde, bu tanımı ekleyebilmenizi sağlamak için gerekli kontrolleri yapmak istersiniz.

  • Fonksiyonun zaten aynı imzayla bulunup bulunmadığını kontrol edin
  • Metot bildiriminin uygun kapsamda yapıldığından emin olun (örn. Diğer metot bildirimleri içerisinde metotlar ilan edilebilir mi?)

Bu kontrollerin başarılı olduğunu varsayarak, haritanıza ekleyebilir ve bu yöntemin içeriğini kontrol etmeye başlayabilirsiniz.

Gibi bir yöntem çağrısı bulduğunuzda callSomething(), aşağıdaki kontrolleri yapmanız gerekir:

  • Mu callSomethingharitanızda var?
  • Düzgün bir şekilde aranıyor mu (argüman sayısı bulduğunuz imza ile eşleşiyor)?
  • Bağımsız değişkenler geçerli mi (değişken adları kullanılıyorsa, bildirildiler mi? Bu kapsamda erişilebilirler mi?)?
  • Çağrınız Bulunduğunuz yerden bir şey çağrılabilir mi (özel, herkese açık, korunuyor mu?)?

Eğer onu bulursan callSomething()Tamam , o zaman bu noktada ne yapmak istediğinizi gerçekten nasıl yaklaşmak istediğinize bağlı. Açıkçası, böyle bir aramanın bu noktada tamam olduğunu öğrendikten sonra, daha fazla ayrıntıya girmeden yalnızca yöntemin adını ve argümanları kaydedebilirsiniz. Programınızı çalıştırdığınızda, yöntemi çalışma zamanında sahip olmanız gereken değişkenlerle çağırırsınız.

Daha ileri gitmek istiyorsanız, yalnızca dizgiyi değil, gerçek yöntemin bağlantısını da kaydedebilirsiniz. Bu daha verimli olurdu, ancak belleği yönetmeniz gerekiyorsa, kafa karıştırıcı olabilir. İlk başta ipi tutmanı tavsiye ederim. Daha sonra optimize etmeye çalışabilirsiniz.

Not Bu tüm size programında tüm belirteçleri tanınan ve ne biliyorum vasıta programınızı, lexxed ettik varsayarak olduğunu vardır . Bu, henüz birlikte bir anlam ifade edip etmediklerini, yani ayrıştırma aşaması olduğunu bildiğinizi söylemek değildir. Belirteçlerin ne olduğunu henüz bilmiyorsanız, önce bu bilgileri edinmeye odaklanmanızı öneririm.

Umarım bu yardımcı olur! SE Programcılarına Hoşgeldiniz!


2

Mesajınızı okurken, sorunuzda iki soru fark ettim. En önemlisi nasıl ayrıştırılacağıdır. (Açık veya kapalı) bir dilbilgisi verildiğinde "özyinelemeli" bir metin programında geçiş yapmak için kullanabileceğiniz birçok ayrıştırıcı türü (örneğin Recursive iniş ayrıştırıcısı , LR Ayrıştırıcısı , Packrat Ayrıştırıcısı ) ve ayrıştırıcı üreteci (örneğin GNU bizonu , ANTLR ) vardır.

İkinci soru, fonksiyonlar için depolama formatı ile ilgilidir. Yapmadığınız zaman sözdizimi yönelimli çeviri , bir olabilir programınızın bir ara temsilini oluşturmak soyut sözdizimi ağacı , veya bazı özel ara dil üzerinde, yazma, (onunla derleme ileri işlem yapmak dönüşümü, yürütmek amacıyla bir dosya, vb.)


1

Genel bir bakış açısından, bir işlevin tanımı koddaki bir etiketten veya yer iminden çok azdır. Diğer çoğu döngü, kapsam ve koşullu işleçler benzerdir; Onlar düşük soyutlama seviyelerinde temel bir "atlama" veya "goto" komutu için hazırlar. Bir işlev çağrısı temel olarak aşağıdaki düşük seviyeli bilgisayar komutlarına göre kısılır:

  • Tüm parametrelerin verilerini, ayrıca mevcut işlevin bir sonraki komutuna bir göstericiyi "çağrı yığını çerçevesi" olarak bilinen bir yapıya birleştirin.
  • Bu çerçeveyi çağrı yığınının üzerine itin.
  • Fonksiyon kodunun ilk satırının hafıza ofsetine atlayın.

Bir "return" ifadesi veya benzeri, aşağıdakileri yapacaktır:

  • Bir sicile döndürülecek değeri yükleyin.
  • İşaretçiyi arayana bir kayıt defterine yerleştirin.
  • Geçerli yığın çerçevesini açın.
  • Arayanın işaretçisine atla.

Bu nedenle, işlevler, insanların kodları daha sürdürülebilir ve sezgisel bir şekilde düzenlemelerine izin veren, daha yüksek düzeyde bir dil belirtimindeki soyutlamalardır. Bir derleme veya ara dilde (JIL, MSIL, ILX) derlendiğinde ve kesinlikle makine kodu olarak yapıldığında neredeyse tüm bu soyutlamalar kaybolur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.