Tôi đang dần dần làm việc để hoàn thành văn bằng của mình và học kỳ này là Trình biên dịch 101. Chúng tôi đang sử dụng Sách Rồng . Một thời gian ngắn tham gia khóa học và chúng ta đang nói về phân tích từ vựng và cách nó có thể được thực hiện thông qua automata hữu hạn xác định (sau đây, DFA). Thiết lập các trạng thái từ vựng khác nhau của bạn, xác định chuyển tiếp giữa chúng, v.v.
Nhưng cả giáo sư và cuốn sách đều đề xuất thực hiện chúng thông qua các bảng chuyển tiếp với một mảng 2d khổng lồ (các trạng thái không đầu cuối khác nhau như một chiều và các ký hiệu đầu vào có thể khác) và một câu lệnh chuyển đổi để xử lý tất cả các đầu cuối cũng như gửi đến các bảng chuyển tiếp nếu ở trạng thái không đầu cuối.
Lý thuyết là tốt và tốt, nhưng như một người thực sự đã viết mã trong nhiều thập kỷ, việc thực hiện là vô ích. Nó không thể kiểm tra được, nó không thể bảo trì, không thể đọc được và đó là một nỗi đau và một nửa để gỡ lỗi. Tệ hơn nữa, tôi không thể thấy nó sẽ thực tế đến mức nào nếu ngôn ngữ có khả năng UTF. Có một triệu mục nhập bảng chuyển đổi cho mỗi trạng thái không đầu cuối trở nên khó khăn trong sự vội vàng.
Vậy thỏa thuận là gì? Tại sao cuốn sách dứt khoát về chủ đề nói làm theo cách này?
Là chi phí chung của các cuộc gọi chức năng thực sự nhiều? Đây có phải là một cái gì đó hoạt động tốt hoặc là cần thiết khi ngữ pháp không được biết trước (biểu thức thông thường?)? Hoặc có lẽ một cái gì đó xử lý tất cả các trường hợp, ngay cả khi các giải pháp cụ thể hơn sẽ hoạt động tốt hơn cho các ngữ pháp cụ thể hơn?
( Lưu ý: có thể trùng lặp " Tại sao sử dụng một cách tiếp cận OO thay vì một câu lệnh switch khổng lồ? " Gần, nhưng tôi không quan tâm đến OO Một cách tiếp cận chức năng hoặc thậm chí tiếp cận saner bắt buộc với các chức năng độc lập sẽ là tốt..)
Và vì lợi ích của ví dụ, hãy xem xét một ngôn ngữ chỉ có định danh và những định danh đó là [a-zA-Z]+
. Trong triển khai DFA, bạn sẽ nhận được một cái gì đó như:
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;
}
(mặc dù một cái gì đó sẽ xử lý chính xác cuối tập tin)
So với những gì tôi mong đợi:
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'));
}
Với mã được NextToken
tái cấu trúc thành chức năng của chính nó một khi bạn có nhiều điểm đến từ khi bắt đầu DFA.