Bu büyük bir konudur, ancak sizi görkemli bir “kitap oku, evlat” ile fırçalamak yerine, kafanızı sarmanıza yardımcı olmak için memnuniyetle işaretçiler vereceğim.
Çoğu derleyici ve / veya tercüman şöyle çalışır:
Tokenize : Kod metnini tarayın ve bir belirteç listesine bölün.
Bu adım zor olabilir çünkü dizeyi yalnızca boşluklara bölemezsiniz, bunun if (bar) foo += "a string";
8 simgeli bir liste olduğunu kabul etmeniz gerekir : WORD, OPEN_PAREN, WORD, CLOSE_PAREN, WORD, ASIGNMENT_ADD, STRING_LITERAL, TERMINATOR. Gördüğünüz gibi, kaynak kodunu boşluklara bölmek işe yaramaz, her karakteri bir sıra olarak okumak zorundasınız, bu nedenle alfanümerik bir karakterle karşılaşırsanız, alfanum olmayan bir karaktere ve o dizgene isabet edene kadar karakterleri okumaya devam edersiniz. sadece okumak, daha sonra sınıflandırılacak bir WORD. Simgenizin ne kadar granül olduğuna kendiniz karar verebilirsiniz: "a string"
STRING_LITERAL adlı bir simge olarak daha sonra ayrıştırılmak üzere mi yutulup yutulmadığı veya görüp görmediği konusunda karar verebilirsiniz."a string"
OPEN_QUOTE, UNPARSED_TEXT, CLOSE_QUOTE veya her neyse, kodlama yaparken kendiniz için karar vermeniz gereken seçeneklerden sadece biri.
Lex : Şimdi artık bir jeton listeniz var. Muhtemelen bazı jetonları WORD gibi belirsiz bir sınıflandırma ile etiketlediniz, çünkü ilk seferde her karakter dizisinin içeriğini anlamaya çalışmak için fazla çaba harcamak zorunda değilsiniz. Bu yüzden şimdi kaynak belirteçlerin listesini tekrar okuyunuz ve belirsiz belirteçlerin her birini, dilinizdeki anahtar kelimelere göre daha belirgin bir belirteç türü ile yeniden sınıflandırınız. Bu nedenle, "if" ve "if" gibi bir WORD'iniz, IF sembolüne sahip özel anahtar kelimeler listenizdeyse, bu belirtecin sembol tipini WORD'den IF'ye ve özel anahtar kelimeler listenizde olmayan bir WORD'ye değiştirirsiniz. WORD foo gibi, bir tanımlayıcıdır.
Ayrıştırma : Şimdi if (bar) foo += "a string";
, şuna benzeyen bir belirteç belirteçleri listesi açtınız: IF OPEN_PAREN IDENTIFER CLOSE_PAREN IDENTIFIER ASIGN_ADD STRING_LITERAL TERMINATOR. Adım, tokenlerin dizilerini ifadeler olarak tanımaktır. Bu ayrıştırıyor. Böyle bir gramer kullanarak bunu yaparsınız:
BİLDİRİM: = ASIGN_EXPRESSION | IF_STATEMENT
IF_STATEMENT: = IF, PAREN_EXPRESSION, STATEMENT
ASIGN_EXPRESSION: = TANIMCI, ASIGN_OP, DEĞER
PAREN_EXPRESSSION: = OPEN_PAREN, DEĞER, CLOSE_PAREN
DEĞER: = TANIMCI | STRING_LITREAL | PAREN_EXPRESSION
ASIGN_OP: = EQUAL | ASIGN_ADD | ASIGN_SUBTRACT | ASIGN_MULT
"|" Kullanan yapımlar terimler arasında "bunlardan herhangi biriyle eşleşin" anlamına gelir, terimler arasında virgül varsa, "terimler dizisinin bu eşleşmesi" anlamına gelir
Bunu nasıl kullanıyorsunuz? İlk belirteçten başlayarak, belirteç dizinizi bu üretimlerle eşleştirmeye çalışın. İlk önce token listenizi STATEMENT ile eşleştirmeye çalışıyorsunuz, bu yüzden STATEMENT kuralını okuyorsunuz ve "bir STATEMENT bir ASIGN_EXPRESSION ya da bir IF_STATEMENT" diyor, bu yüzden önce ASIGN_EXPRESSION ile eşleşmeye çalışıyorsunuz, böylece ASIGN_EXPRESSION için dilbilgisi kuralına bakıyorsunuz ve "ASIGN_EXPRESSION, ardından bir ASIGN_OP ve ardından bir VENTUE izleyen bir IDENTIFIER olduğunu söylüyor, bu yüzden IDENTIFIER için dilbilgisi kuralını araştırıyorsunuz ve IDENTIFIER için dilbilgisi çekirdeği olmadığını görüyorsunuz, bu da IDENTIFIER için daha fazla gerektirmeyen bir" terminal "anlamına geliyor. eşleşecek şekilde ayrıştırma, böylece doğrudan belirtecinizle eşleştirmeye çalışabilirsiniz, ancak ilk kaynak belirteciniz bir IF'dir ve IF bir IDENTIFIER ile aynı değildir, bu nedenle eşleşme başarısız olur. Şimdi ne var? STATEMENT kuralına geri dönüp sonraki terimi eşleştirmeyi deneyin: IF_STATEMENT. IF_STATEMENT'i ararsınız, IF ile başlar, IF'yi arar, IF bir terminaldir, terminali ilk jetonunuzla karşılaştırın, IF jetonu eşleşmeleri, harika devam edin, gelecek dönem PAREN_EXPRESSION, PAREN_EXPRESSION'a bakın, terminal değil, ilk terimdir, PAREN_EXPRESSION OPEN_PAREN ile başlar, OPEN_PAREN'i arar, bu bir terminaldir, OPEN_PAREN ile bir sonraki belirtecinizle eşleşir, eşleşir, vb.
Bu adıma yaklaşmanın en kolay yolu, eşleştirmeye çalıştığınız kaynak kod belirtecini ve eşleştirmeye çalıştığınız dilbilgisi terimini ilettiğinizde parse () adında bir işleve sahip olmanızdır. Dilbilgisi terimi bir terminal değilse, yinelenirsiniz: yine aynı kaynak belirteci ve bu dilbilgisi kuralının ilk terimini geçen parse () işlevini çağırırsınız. Bu nedenle “özyinelemeli iniş ayrıştırıcı” olarak adlandırılır. Parse () işlevi kaynak belirteçleri okurken geçerli konumunuzu döndürür (veya değiştirir), esasen eşleşen sıradaki son belirteci geri döndürür ve bir sonraki çağrıya devam edersiniz. ayrıştır () oradan.
Her seferinde parse (), ASIGN_EXPRESSION gibi bir üretimle eşleşir ve bu kod parçasını temsil eden bir yapı yaratırsınız. Bu yapı orijinal kaynak belirteçlerine referanslar içerir. Bu yapıların bir listesini oluşturmaya başlarsınız. Bu yapının tamamına Özet Sözdizimi Ağacı (AST) diyoruz.
Derleme ve / veya Yürütme : Dilbilginizdeki bazı üretimler için, bir AST yapısı verilirse, AST'nin bu yığınını derleyeceği veya yürüteceği işleyici işlevleri oluşturdunuz.
Öyleyse, AST'nizin ASIGN_ADD tipindeki parçasına bakalım. Yani tercüman olarak bir ASIGN_ADD_execute () işlevine sahipsiniz. Bu işlev, ayrıştırma ağacına karşılık gelen AST'nin bir parçası olarak geçirilir foo += "a string"
, bu nedenle bu işlev bu yapıya bakar ve yapıdaki ilk terimin bir TANIMCI olması gerektiğini ve ikinci terimin DEĞER olduğunu bilir, yani ASIGN_ADD_execute () VALUE terimini, bellekte değerlendirilen değeri temsil eden bir nesneyi döndüren bir VALUE_eval () işlevine geçirir, ardından ASIGN_ADD_execute (), değişkenler tablonuzda "foo" ifadesini arar ve eval_value () öğesinin ne döndürdüğüne bir başvuru saklar. işlevi.
Bu bir tercüman. Bir derleyici bunun yerine, işleyici işlevlerinin AST'yi yürütmek yerine bayt koduna veya makine koduna çevirmesini sağlar.
Adım 1 ila 3 ve bazı 4, Flex ve Bison gibi araçlar kullanılarak daha kolay hale getirilebilir. (aka. Lex ve Yacc) ancak sıfırdan kendinize bir tercüman yazmak muhtemelen herhangi bir programcının başarabileceği en güçlendirici egzersizdir. Diğer tüm programlama zorlukları, bunu yaptıktan sonra önemsiz görünüyor.
Tavsiyem küçük başlıyor: küçük bir dilbilgisi içeren küçük bir dil ve birkaç basit ifadeyi ayrıştırıp yürütmeyi deneyin, sonra oradan büyütün.
Bunları oku ve iyi şanslar!
http://www.iro.umontreal.ca/~felipe/IFT2030-Automne2002/Complements/tinyc.c
http://en.wikipedia.org/wiki/Recursive_descent_parser
lex
,yacc
vebison
.