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.
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.