通常讲授的普通解析器在解析器接触输入之前有一个词法分析器阶段。词法分析器(也称为“扫描器”或“令牌生成器”)将输入切成带有类型注释的小令牌。这使主解析器可以将令牌用作终端元素,而不必将每个字符都视为终端,这可以显着提高效率。特别是,词法分析器还可以删除所有注释和空白。但是,单独的令牌生成器阶段意味着关键字也不能用作标识符(除非该语言支持某种程度上不受欢迎的straping或在所有标识符之前加上sigil前缀$foo
)。
为什么?假设我们有一个简单的令牌生成器,它可以理解以下令牌:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
令牌生成器将始终匹配最长的令牌,并且优先使用关键字而不是标识符。因此interesting
将被词汇化为IDENT:interesting
,但in
将被词汇化为IN
,永不如此IDENT:interesting
。像这样的代码片段
for(var in expression)
将被转换为令牌流
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
到目前为止,这可行。但是任何变量in
都将被分类为关键字IN
而不是变量,这会破坏代码。词法分析器在标记之间不保留任何状态,并且in
除非我们处于for循环中,否则不知道它通常应该是变量。另外,以下代码应合法:
for(in in expression)
第一个in
是标识符,第二个是关键字。
对于此问题有两个反应:
上下文关键字令人困惑,让我们重新使用关键字。
Java有许多保留字,其中一些无用,只是向从C ++转换为Java的程序员提供了更有用的错误消息。添加新关键字会破坏代码。除非上下文上下文关键字具有良好的语法突出显示功能,否则添加上下文关键字会使代码的读者感到困惑,并且使工具难以实施,因为它们必须使用更高级的解析技术(请参见下文)。
当我们想扩展语言时,唯一明智的方法是使用以前在该语言中不合法的符号。特别是,这些不能是标识符。使用foreach循环语法,Java重用了:
具有新含义的现有关键字。借助lambda,Java添加了一个->
关键字,该关键字以前在任何合法程序中都不会出现(-->
仍将被分类为'--' '>'
合法,并且->
可能先前被分类为'-', '>'
,但是该序列将被解析器拒绝)。
上下文关键字简化了语言,让我们实现它们
词法编辑器无疑是有用的。但是,我们可以在解析器前串联运行它们,而不是在解析器前运行词法分析器。自下而上的解析器始终知道在任何给定位置可接受的令牌类型集。然后,解析器可以请求词法分析器在当前位置匹配这些类型中的任何一种。在for-each循环中,解析器将在·
找到变量后位于(简化的)语法中所指示的位置:
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
在该位置,合法令牌是SEMICOLON
或IN
,但不是IDENT
。关键字in
将是完全明确的。
在此特定示例中,自上而下的解析器也不会出现问题,因为我们可以将上述语法重写为
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
并且无需回溯就可以看到决策所需的所有令牌。
考虑可用性
Java一直倾向于语义和句法简化。例如,该语言不支持运算符重载,因为这会使代码复杂得多。因此,在为for-each循环语法选择之间in
以及:
为每个循环使用语法时,我们必须考虑哪种混淆方式对用户更不明显。极端的情况可能是
for (in in in in())
for (in in : in())
(注意:Java为类型名称,变量和方法提供了单独的命名空间。我认为这主要是一个错误。这并不意味着以后的语言设计必须添加更多的错误。)
哪种选择可以在迭代变量和迭代集合之间提供更清晰的视觉分隔?扫一眼代码,可以更快地识别出哪种方法?我发现在涉及这些标准时,分隔符号要比字符串更好。其他语言具有不同的值。例如,Python用英语拼写了许多运算符,以便可以自然地阅读它们并易于理解,但是这些相同的属性可能使乍一看很难理解一段Python。