Python ile Interpreter Yapımı - Tokenizer - ihan3t

ihan3t

Kadim Üye
7 Şub 2012
5,018
21
Merhabalar, http://www.turkhackteam.org/off-top...cilar-belli-olacak-teknik-yarisma-ihan3t.html konusunda sorduğum sorunun cevabının bir kısmını yazmaya karar verdim.

Sorduğum soruda istediğim şey bir tokenizer (lexer) ve bir adet parser yazılmasıydı, fakat birçoğu soruyu anlamadı/çarpıttı ve istenilen cevap ortaya maalesef çıkmadı.

Bir programlama dilini kullanarak yazdığımız kod aslında belirli kurallara uyarak bir araya getirdiğimiz karakterler kümesidir.

Tokenizer, bu karakterler kümesini dilin kurallarını kullanarak interpreterın anlayacağı hale getirir.

Şimdi basit matematiksel ifadelerimizi tokenize etme işlemini yapacağız.

Yapacağımız iş,

(9*3)-1+8*5+(1-3)

şeklinde verilen bir ifadeyi tokenlara ayırmak olacak.

Tokenizer adında bir classımız olsun. Tokenlara ayırmak için verilen expression ımızı teker teker okuyup belirli bir kurala göre tokenize edeceğiz. Kuralımız ise şu, eğer matematiksel bir ifadeye rastlarsa (dört işlem sembolü veya parantez) token olarak ekle, eğer bir sayıya rastlarsa sayı devam ettiği sürece alıp bunu token olarak ekle.

Classımızın 3 adet fieldı var. Birincisi tokenize edeceğimiz expression, diğeri şuanda okuduğumuz char diğeri expressiondaki mevcut pozisyonumuz (kaçıncı karakteri okuduğumuz) sonuncusu ise operation tiplerimizi tutan dictionary tipindeki değişkenimiz. (bu değişken yerine TokenTypes gibi bir enum da tutulabilirdi, farklı şekillerde yapılabilirdi, token tiplerimiz az olduğu için ve kod yazarken kolaylık olsun diye ben dictionary seçtim)

Tokenlarımızı temsil eden classımız :

Kod:
class Token():
    type = ''
    text = ''

    def __init__(self, text, type):
        self.type = type
        self.text = text

Kod:
class Tokenizer():
    expression = ''
    currentChar = ''
    currentCharPosition = 0

Tokenize etme işlemi sırasında bir char ın operation olup olmadığına karar vereceğimiz ve eğer operation ise hangi operation olduğunu söyleyen metodu ekleyelim :

Kod:
class Tokenizer():
    expression = ''
    currentChar = ''
    currentCharPosition = 0

    opTypes = {
        '+' : 'ADD',
        '-' : 'SUBTRACT',
        '*' : 'MULTIPLY',
        '/' : 'DIVIDE',
        '(' : 'LEFT_PAREN',
        ')' : 'RIGHT_PAREN'
    }

    def isOp(self, chr):
        opList = ['+', '-', '*', '/', '(', ')']
        return True if chr in opList else False

    def findOpType(self, chr):
        return self.opTypes[chr]

Şimdi gelelim tokenize metodumuza. Asıl işi burada yapıyor olacağız.

Kod:
    def tokenize(self, resource):
        tokens = []
        token = ''
        state = 'DEFAULT'

        index = 0
        while index < len(resource):
            chr = resource[index]
            index += 1

            if state == 'DEFAULT':
                if self.isOp(chr):
                    opType = self.findOpType(chr)
                    tokens.append(Token(chr, opType))
                elif chr.isdigit():
                    token += chr
                    state = 'NUMBER'
                continue

            if state == 'NUMBER':
                if chr.isdigit():
                    token += chr
                else:
                    tokens.append(Token(token, 'NUMBER'))
                    token = ''
                    state = 'DEFAULT'
                    index -= 1
                continue

        return tokens

- Burada expressionımızdan bir karakter okuyoruz
- Başlangıçta "default" adında bir durumdayız
- Durumumuz default ise okuduğumuz karakterin operation mı yoksa sayı tipinde mi olduğuna bakıyoruz
- Eğer operation tipindeyse, hangi operation olduğunu bulup token listemize ekliyoruz
- Eğer sayı tipindeyse sayıyı token değişkenimize ekleyip, state değişkenini "number" yapıyoruz ve sayının sonuna kadar okumaya devam ediyoruz
- Sayı bitip tekrar bir operation geldiğinde state imizi tekrar değiştiriyoruz ve token oluşturma işlemine devam ediyoruz

Programı bir bütün haline getirelim :

Kod:
class Tokenizer():
    expression = ''
    currentChar = ''
    currentCharPosition = 0

    opTypes = {
        '+' : 'ADD',
        '-' : 'SUBTRACT',
        '*' : 'MULTIPLY',
        '/' : 'DIVIDE',
        '(' : 'LEFT_PAREN',
        ')' : 'RIGHT_PAREN'
    }

    def isOp(self, chr):
        opList = ['+', '-', '*', '/', '(', ')']
        return True if chr in opList else False

    def findOpType(self, chr):
        return self.opTypes[chr]

    def tokenize(self, resource):
        tokens = []
        token = ''
        state = 'DEFAULT'

        index = 0
        while index < len(resource):
            chr = resource[index]
            index += 1

            if state == 'DEFAULT':
                if self.isOp(chr):
                    opType = self.findOpType(chr)
                    tokens.append(Token(chr, opType))
                elif chr.isdigit():
                    token += chr
                    state = 'NUMBER'
                continue

            if state == 'NUMBER':
                if chr.isdigit():
                    token += chr
                else:
                    tokens.append(Token(token, 'NUMBER'))
                    token = ''
                    state = 'DEFAULT'
                    index -= 1
                continue

        return tokens

def main():
    tokenizer = Tokenizer()
    expression = "( 9 *3 )-1+8*5+(1-3)"

    tokens = tokenizer.tokenize(expression)

    for token in tokens:
        if token.type == 'NUMBER':
            print '{} ({})'.format(token.type, token.text)
        else:
            print token.type

Kodumuzu çalıştırdığımızda bize şöyle bir çıktı veriyor :

Kod:
LEFT_PAREN
NUMBER (9)
MULTIPLY
NUMBER (3)
RIGHT_PAREN
SUBTRACT
NUMBER (1)
ADD
NUMBER (8)
MULTIPLY
NUMBER (5)
ADD
LEFT_PAREN
NUMBER (1)
SUBTRACT
NUMBER (3)
RIGHT_PAREN

Tokenize edilmiş sonucumuz. Bu çok simple bir tokenizer. Bir expressionı tokenize ettikten sonra, mantığı anlayıp daha derin bilgiler edindiğinizde aslında bir programlama dili syntaxı oluşturmaya başlıyorsunuz.

Parse etme sürecini de öğrenince aslında çalışan bir programlama diliniz oluyor.

Tabii ki bu tasarlamayı yapmak için context free grammer kavramını, terminal ve non terminal değişkenleri bilmek gerekiyor.

Verdiğim örneklerden yola çıkıp kendinizi geliştirerek programlama dili keywordlerini tokenize eden bir tokenizer yazabilirsiniz artık.

Tokenize etme aşamasında çok farklı teknikler kullanılabilir, matematik dilini baz aldığımız için bu şekilde karakter bazında okuma işlemi bizim için yeterli.

Yeterli ilgi olursa parse etme işlemini ileriki zamanlarda fırsat bulursam anlatabilirim.

Herkese iyi forumlar, ihan3t.

 

ihan3t

Kadim Üye
7 Şub 2012
5,018
21
Teşekkürler. Böyle konular kimse bakmadan aşağılarda kaybolurken top 15 teki troll konuların yukarda olması çok ilginç.
 

Phoique 7

Katılımcı Üye
14 Mar 2017
505
1
Manisa
Elinize sağlık merak ettiğim ve çözümünü bir zamanlar aradığım konuydu. Çok karışık görünüyor. Aslında kodlar çok düzenli yazılmış ama yeni başlayan biri için şahsen benim için anlamakta biraz zorluk çekmedim değil :)
 

TurkishAttacker

Katılımcı Üye
13 Tem 2015
723
0
Bu forumdaki uzun zamandır gördüğüm en yararlı (kişiden kişiye göre değişir kendi fikrimce) ve en orijinal konu.
Teşekkürler.
 

nS4f3

Üye
3 Haz 2017
92
0
Hocam Gerçekten sizin sayenizde bilmediğim bir konuyu öğrendim
Yalnız ilk kodlarda oluşturmuş olduğunuz Token sınıfını programın bütün hali kısmına eklememişsiniz
Unuttunuz mu yoksa herhangi bir sebebi var mı?
 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.