我正在慢慢地完成学业,这个学期是Compilers101。我们正在使用Dragon Book。在课程开始不久,我们将讨论词法分析以及如何通过确定性有限自动机(以下称DFA)来实现它。设置各种词法分析器状态,定义它们之间的过渡等。
但是教授和这本书都建议通过过渡表来实现它们,过渡表相当于一个巨大的2d数组(一个维的各种非终端状态,而另一个维可能的输入符号),以及一个用于处理所有终端的switch语句以及在非终端状态下调度到过渡表。
这个理论很好,但是作为一个几十年来实际编写代码的人,实现是不道德的。它不可测试,不可维护,不可读,调试起来很麻烦。更糟糕的是,如果该语言具有UTF功能,那么我将看不到它在远程实用。每个非终端状态都有一百万个左右的过渡表条目,这会很不方便。
那怎么办?为什么有关该主题的权威书籍说要这样做呢?
函数调用的开销真的那么多吗?当语法不为人所知时(正则表达式?),这是否行得通?也许可以处理所有情况的东西,即使更具体的解决方案更适合于更具体的语法?
(注意:可能重复的“ 为什么使用OO方法而不是巨大的switch语句? ”已经很接近了,但我并不关心OO。使用功能性方法甚至具有独立功能的更明智的命令式方法都可以。)
并且为了示例,考虑一种仅具有标识符的语言,而这些标识符为[a-zA-Z]+
。在DFA实施中,您将获得以下内容:
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;
}
(尽管可以正确处理文件结尾的内容)
与我期望的相比:
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'));
}
NextToken
一旦您从DFA开始就有多个目标,就可以将代码重构为自己的功能。