"Gereksiz detaylardan" kurtulmayı bu aşamada yapacağız. if, int gibi keyword'leri ya da punctuation'ları string formatında saklamaktansa onlara sayısal ID'ler atayacağız.
TK_IF,
TK_ELSE,
TK_TY_INT,
TK_PLUS,
TK_MINUS,
};
Keyword ve punctuation'lara karşılık gelen token ID'lerimizi, tasarlayacağımız dile göre bu enum içinde tanımlayacağız. Diğer tokenlerin ID'lerini de tokenizer kendisi tanımlamıştır:
Benzer şekilde identifier'lara da sayısal ID'ler atayacağız. Tokenizer, daha önce karşılaşmadığı bir identifier gelince ona sıradaki ID'yi atayacak.
- Ayrıca Bakınız
- Veri Yapıları
| identifier | ID |
| degisken1 | 0 |
| baska_bir_degisken | 1 |
| ... | |
Bunları yapınca elde edeceğimiz, tamamen soyut bir token yapısı:
};
};
size_t id
Token tipini ifade eden özgün ID.
size_t ident_id
TK_IDENT tipi için seminfo.
intmax_t num_int
TK_INT tipi için seminfo.
double num_float
TK_FLOAT tipi için seminfo.
Tokenizer Tasarımı
Önce elde etmek istediğimiz işlevi ele alarak başlayalım. Lexerin ürettiği lexemelerdeki tokenleri çıkaracak bir yapı istiyoruz, yani:
void tokenizer_feed(struct tokenizer *tokenizer, struct lexeme lexeme)
Lexemeyi tokenizere gönderir.
struct token tokenizer_next(struct tokenizer *tokenzier)
Sıradaki tokeni çek.
Bir lexeme'de birden fazla token olabileceği için bu yapıyı kurduk. Punctuation'ları tokenize ederken bu yapı daha da mantıklı gelecek.
};
struct lexeme current_lexeme
İşlenmekte olan lexeme.
size_t last_id
Identifier'lara eşsiz ID'ler ataybilmek için yardımcı sayaç.
struct map punctuations
Yazılım dilindeki punctuation'lar (sembol ve operatörler).
struct map ident_map
Identifier'a karşılık atanan sayısal ID.
struct map keywords
Yazılım dilindeki keyword'ler.
Basma kalıp init ve destroy fonksiyonlarımızı yazalaım:
{
clear_lexeme(t);
}
{
}
void map_destroy(struct map *map)
map tarafından ayrılmış belleği temizler.
void map_init(struct map *map, size_t value_size)
Yeni bir map oluşturur.
void tokenizer_destroy(struct tokenizer *tokenizer)
Tokenizerın tahsis ettiği belleği temizler.
void tokenizer_init(struct tokenizer *tokenizer)
[Token tanımı]
Keyword ve punctuation tokenlerimizi enum olarak tanımladık, ancak onların string karşılıklarını da tanımlamamız gerekiyor. İşte tanımladığımız keywords ve punctuations map'leri burada devreye girecek:
void tokenizer_add_keyword(struct tokenizer *tokenizer, const char *keyword, size_t id)
Tokenizera bir keyword kaydeder.
void tokenizer_add_punctuation(struct tokenizer *tokenizer, const char *punctuation, size_t id)
Tokenizera bir punctuation kaydeder..
tokenizer_add_keyword() ve tokenizer_add_punctuation(), kodun okunabilirliğini artırmak için tanımlanmış basit fonksiyonlar (thin wrapper):
const char *keyword,
size_t id)
{
}
const char *punctuation,
size_t id)
{
}
void map_insert(struct map *map, const void *key, const void *value)
map'e key-value ikilisini ekler.
Bahsettiğimiz identifier'lara sayısal ID atamaysa aşağıdaki fonksiyonlarla gerçekleşiyor:
{
}
const char *ident, size_t ident_len)
{
if (id_in_map) {
return *id_in_map;
} else {
return id;
}
}
void map_insert2(struct map *map, const void *key, size_t keylen, const void *value)
map'e key-value ikilisini ekler.
void * map_get2(struct map *map, const void *key, size_t keylen)
Key ile eşleşen valueyı döner, key bulunamazsa NULL döner.
size_t tokenizer_ident_id2(struct tokenizer *tokenizer, const char *ident, size_t ident_len)
Identifier'a ait ID'yi döner.
size_t tokenizer_ident_id(struct tokenizer *tokenizer, const char *ident)
Identifier'a ait ID'yi döner.
Şimdi tokenize etmeye başlayabiliriz.
{
"Tokenizerda zaten bir lexeme mevcut. Önce tokenizer_next çağırın.");
}
enum lexeme::lexeme_kind kind
Tokenizing
tokenizer_next() fonksiyonunu daha yakından inceleyelim. Bu fonksiyon, lexeme tipine göre yardımcı fonksiyon çağıran dağıtıcı işlevine sahip:
{
case LEXEME_EOF:
case LEXEME_IDENT:
return consume_ident(t);
case LEXEME_PUNCT:
return consume_punct(t);
case LEXEME_INT:
case LEXEME_FLOAT:
return consume_num(t);
}
assert(0);
}
static const size_t TK_NOTOKEN
tokenizer_feed() ile verilen lexemenin bittiğini belirtir.
Tokenizer'ın karakter parçalarını anlamlandırmasını irdeleyelim. TK_INT ve TK_FLOAT üretmek, nispeten daha düz mantık:
{
tk.seminfo.num_int = strtoimax(num, NULL, 10);
} else {
tk.seminfo.num_float = strtod(num, NULL);
}
clear_lexeme(t);
return tk;
}
static const size_t TK_FLOAT
Ondalık sayı token ID'si.
static const size_t TK_INT
Tam sayı token ID'si.
Lexer'da grupladığımız identifier ve punctuation'ları şimdi anlamlandıracağız. Lexer keyword ve identifier ayrımını yapmamıştı, şimdi tokenizer'da karakter grubunun keyword olup olmadığına bakıp token üreteceğiz:
{
clear_lexeme(t);
if (keyword_id) {
return (
struct token) { .id = *keyword_id };
} else {
return (
struct token) { .id =
TK_IDENT, .seminfo.ident_id = ident_id};
}
}
static const size_t TK_IDENT
Identifier token ID'si.
Tek parça olarak gruplanmış punctuation'ları böleceğiz.
{
size_t *punct_id;
size_t i;
for (
i > 0;
i--) {
if (punct_id) {
break;
}
}
assert(i != 0 && "Bilinmeyen punctuation.");
clear_lexeme(t);
return (
struct token) { .id = *punct_id };
}
Soyut syntax elemanlarını, tokenleri, ürettik. Artık yazılım dilimizin söz dizimini, gramerini, tasarlayabiliriz: Parser