Saya perlahan-lahan bekerja untuk menyelesaikan gelar saya, dan semester ini adalah Compiler 101. Kami menggunakan Buku Naga . Singkat ke kursus dan kita berbicara tentang analisis leksikal dan bagaimana itu dapat diimplementasikan melalui automata terbatas deterministik (selanjutnya, DFA). Siapkan berbagai status lexer Anda, tentukan transisi di antaranya, dll.
Tetapi baik profesor dan buku mengusulkan menerapkannya melalui tabel transisi yang berjumlah array 2d raksasa (berbagai negara non-terminal sebagai satu dimensi, dan simbol input yang mungkin sebagai yang lain) dan pernyataan saklar untuk menangani semua terminal serta pengiriman ke tabel transisi jika dalam kondisi non-terminal.
Teorinya baik dan bagus, tetapi sebagai seseorang yang sebenarnya menulis kode selama beberapa dekade, implementasinya keji. Itu tidak dapat diuji, tidak dapat dipertahankan, tidak dapat dibaca, dan itu adalah rasa sakit dan setengah untuk debug melalui. Lebih buruk lagi, saya tidak bisa melihat bagaimana praktisnya jika bahasa itu mampu UTF. Memiliki sejuta atau lebih entri tabel transisi per kondisi non-terminal menjadi tidak terburu-buru.
Jadi, apa masalahnya? Mengapa buku definitif tentang subjek mengatakan untuk melakukannya dengan cara ini?
Apakah overhead panggilan fungsi benar-benar sebanyak itu? Apakah ini sesuatu yang berfungsi dengan baik atau diperlukan ketika tata bahasa tidak diketahui sebelumnya (ekspresi reguler?)? Atau mungkin sesuatu yang menangani semua kasus, bahkan jika solusi yang lebih spesifik akan bekerja lebih baik untuk tata bahasa yang lebih spesifik?
( catatan: kemungkinan duplikat " Mengapa menggunakan pendekatan OO alih-alih pernyataan switch raksasa? " sudah dekat, tapi saya tidak peduli dengan OO. Pendekatan fungsional atau bahkan pendekatan imperatif yang lebih waras dengan fungsi mandiri akan baik-baik saja.)
Dan sebagai contoh, pertimbangkan bahasa yang hanya memiliki pengidentifikasi, dan pengidentifikasi itu [a-zA-Z]+
. Dalam implementasi DFA, Anda akan mendapatkan sesuatu seperti:
private enum State
{
Error = -1,
Start = 0,
IdentifierInProgress = 1,
IdentifierDone = 2
}
private static State[][] transition = new State[][]{
///* Start */ new State[]{ State.Error, State.Error (repeat until 'A'), State.IdentifierInProgress, ...
///* IdentifierInProgress */ new State[]{ State.IdentifierDone, State.IdentifierDone (repeat until 'A'), State.IdentifierInProgress, ...
///* etc. */
};
public static string NextToken(string input, int startIndex)
{
State currentState = State.Start;
int currentIndex = startIndex;
while (currentIndex < input.Length)
{
switch (currentState)
{
case State.Error:
// Whatever, example
throw new NotImplementedException();
case State.IdentifierDone:
return input.Substring(startIndex, currentIndex - startIndex);
default:
currentState = transition[(int)currentState][input[currentIndex]];
currentIndex++;
break;
}
}
return String.Empty;
}
(meskipun sesuatu yang akan menangani akhir file dengan benar)
Dibandingkan dengan apa yang saya harapkan:
public static string NextToken(string input, int startIndex)
{
int currentIndex = startIndex;
while (currentIndex < startIndex && IsLetter(input[currentIndex]))
{
currentIndex++;
}
return input.Substring(startIndex, currentIndex - startIndex);
}
public static bool IsLetter(char c)
{
return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));
}
Dengan kode yang di- NextToken
refactored menjadi fungsinya sendiri setelah Anda memiliki beberapa tujuan dari awal DFA.