Lexical Analiz

lexical analiz karakter dizisini, token dizisine çevirme işlemine deniyor. Token dediğimiz şey ise, bir veya daha fazla karakterden oluşan ve grup olarak önem taşıyan karakter dizisi demek. Bu analizi yapan kodlara lexer deniyor. Çoğu zaman, lexer'ların oluşturduğu token dizisi, parser tarafından işlenir. Bunlar bir dil oluşturmak ve bu dili analiz etmek için kullanılır.

cevre = 2 * pi * r

Yukarıdaki koddaki tokenler şu şekilde düşünülebilir:

Tip Deger
degisken cevre
esittir =
sayı 2
islec *
degisken pi
islec *
degisken r

Lexical analiz için, çeşitli yöntemler kullanılabilir.

  • Düzenli İfadeler: Düzenli ifadeler, bu tip işler için kullanılabilir. Düzenli ifadeler çok gelişmiş araçlardır. Ancak, bunlar lexer için biraz fazla kaçabilir. Ayrıca, yazılan kurallara bağlı olarak, çok yavaş çalışabilirler.
  • Lexer ve Parser oluşturucu araçlar: Örneğin, lex/yacc, flex/bison, ply gibi araçlar, lexer ve parser oluşturmak için kullanılabilir. Ancak duyduğuma göre, bunlar çok genel amaçlı araçlar olduğu için, en ufak iş için bile, gereğinden fazla kod oluşturuyorlarmış. Ayrıca, yavaş çalıştıklarını da duymuştum.
  • lexer yazmak: lexer yazmak çok zor iş değil. Kendi kullanım alanınızla tam entegre olur, ve gereğinden fazla kod üretmez. Ayrıca, lexer'ın çalışma yapısı üzerinde tam hakimiyet sahibi olursunuz.

Python'la basitçe lexer'ın nasıl yapılabileceğini, birkaç yazılık bir seri halinde anlatmak istedim. Eğer vakit bulursam, bundan sonra da basit bir parser yapmaya değinebilirim. Lexer sınıfımız şu şekilde başlıyor:

class Lexer(Thread):
    def __init__(self,inp, que, name="unnamed lexer"):
        super(Lexer, self).__init__()
        self._inp = inp
        self._que = que
        self._start = 0
        self._pos = 0
        self.name = name

Bu sınıf bir thread olarak çalışacak. Böylece, ileride parser ilave edersek, parser ve lexer aynı anda paralel olarak çalışabilir. Argüman olarak, okuyacağı girdiyi ve Queue objesini alıyor. _start geçerli tokenin başladığı index'i gösteriyor. _pos ise, lexer'in o anda hangi pozisyonda işlem yaptığını gösteriyor. _start ve _pos state fonksiyonları tarafından o anki token'i belirlemek için kullanılacak. Detaylarına daha sonra değineceğiz.

Lexer sınıfımız state machine gibi çalışacak. Her state sonraki state'i döndürecek, ve lexer'ımız bu şekilde çalışmaya devam edecek.

class Lexer(Thread):
    def run(self):
        state = self._lexInitial
        while True:
            try:
                state = state()
            except Consumed:
                self._emit("END")
                break

Başlangıçta _lexInitial state'i çalışıyor. Daha sonra, sonsuz döngü şeklinde, state'ler ardarda çalışıyor. Analiz işleminin bittiğini belirtmek için exception kullandım. Bu durumda, "END" tokeni verip, döngüden çıkıyoruz.

class Consumed(Exception):
    pass

class Lexer(Thread):
    def _emit(self, token_type):
        self._que.put((token_type, self._inp[self._start:self._pos]))
        self._start = self._pos

state fonksiyonları bir token vermek istediklerinde _emit fonksiyonunu çağırıyorlar. Bu fonksiyon, Lexer sınıfına verilmiş olan Queue'ye tokenin tipi ve değerinden oluşan bir tuple koyuyor. Daha sonra, _start değerini ileri sarıyor. Böylece, bir sonraki token, bu tokenin bittiği yerden başlayacak.

Bir sonraki yazıda state fonksiyonlarını gösterdiğimde, _start ve _pos değişkenlerinin nasıl kullanıldığı ve state'ler arasındaki geçişin nasıl sağlandığı biraz daha netleşecek. Ama bu yazılık bu kadar.