Parsing 在半空白敏感语言中标记空白?
我做了一些搜索,包括在我面前的红龙书中再看一眼,但我没有找到一个明确的答案。大多数人都在谈论缩进对空格的敏感度,但我的情况并非如此 我想实现一个简单语言的transpiler。这种语言有一个“command”的概念,它是一个保留关键字,后跟一些参数。为了让您了解我所说的内容,一系列命令可能如下所示:Parsing 在半空白敏感语言中标记空白?,parsing,Parsing,我做了一些搜索,包括在我面前的红龙书中再看一眼,但我没有找到一个明确的答案。大多数人都在谈论缩进对空格的敏感度,但我的情况并非如此 我想实现一个简单语言的transpiler。这种语言有一个“command”的概念,它是一个保留关键字,后跟一些参数。为了让您了解我所说的内容,一系列命令可能如下所示: print "hello, world!"; set running 1; while running @ read progname; launch progname; p
print "hello, world!";
set running 1;
while running @
read progname;
launch progname;
print "continue? 1 = yes, 0 = no";
readint running;
@
非正式地说,你可以把语法看作是按照
<program> ::= <statement> <program>
<statement> ::= while <expression> <sequence>
| <command> ;
<sequence> ::= @ <program> @
| <statement>
<command> ::= print <expression>
| set <variable> <expression>
| read <variable>
| readint <variable>
| launch <expression>
<expression> ::= <variable>
| <string>
| <int>
尽管就语法而言,这显然不是模棱两可的。关于如何解决这个问题,我有两个想法
<command> ::= <cmdtype> <arguments>
<arguments> ::= <argument> <arguments>
<argument> ::= <expression>
<cmdtype> ::= print | set | read | readint | launch
:=
::=
::=
::=打印|设置|读取|读取|启动
然后,我们可以确保lexer在遇到
标记时以某种方式(?)处理前导空格。但是,这会将处理内置命令的算术性(以及其他方面)的复杂性转移到解析器中我希望我能稍微篡改一下语言的规范,因为这会使实现更简单,但不幸的是,这是一个向后兼容性问题,不可能实现。向后兼容性通常只适用于正确的程序;接受一个以前会因为语法错误而被benn拒绝的程序不能改变任何有效程序的行为,因此不会违反向后兼容性 这在本例中可能不相关,但正如您所注意到的,这将大大简化问题,因此似乎值得一提 一种解决方案是将空格传递给解析器,然后将其合并到语法中;通常,您将定义一个终端,
WS
,并从中为可选空白定义一个非终端:
<ows> ::= WS |
:=WS|
如果您小心地确保在任何上下文中只有终端和非终端中的一个是有效的,那么这不会影响可分析性,并且生成的语法虽然有点混乱,但仍然是可读的。优点是它使空白规则变得明确
另一个选择是在lexer中处理问题;这可能很简单,但这取决于语言的确切性质
根据您的描述,如果两个标记没有被空格分隔,那么目标似乎是产生语法错误,除非其中一个标记是“自定界”;在所示的示例中,我认为唯一这样的标记是分号,因为您似乎指示@
必须以空格分隔。(可能是您的完整语言有更多的自定界标记,但这并没有实质性地改变问题。)
这可以通过lexer中的单个启动条件来处理(假设您使用的是允许显式状态的lexer生成器);读取空格会使您处于任何标记都有效的状态(如果您使用lex派生,则为初始状态,initial
)。在另一种状态下,只有自定界令牌有效。读取令牌后的状态将是受限状态,除非该令牌是自定界的
这要求每个lexer操作都包含一个状态转换操作,但语法保持不变。其效果是将杂乱无章的内容从解析器转移到扫描器,代价是模糊了空白规则。但它可能不会那么杂乱,而且它肯定会简化将来向空白不可知方言的过渡,如果这在您的计划中的话
还有一种不同的场景,即类似于posix的shell,其中标识符(在shell语法中称为“单词”)不限于字母字符,但可能包括任何非自定界字符。在posix shell中,print“hello,world”
是一个单词,不同于两个令牌序列print“hello,world”
。(第一个最终将被取消引用到单个令牌printhello,world
)
这种情况实际上只能在词汇上处理,尽管不一定复杂。它也可能是你问题的指南;例如,您可以添加一个词汇规则,该规则接受除空格和自定界字符以外的任何字符串;maximal-munch规则将确保仅当无法将令牌识别为标识符或字符串(或其他有效令牌)时才执行操作,因此您可以在操作中抛出错误
这甚至比基于状态的lexer更简单,但灵活性稍差。我担心的是,通过在解析器/语法中包含空格,几乎所有的产品都会看起来像
::=
,其中“凌乱”有点轻描淡写。然而,您在lexer中有两个状态来处理在某些情况下仅允许自定界标记的想法是非常棒的。我想我会走那条路,它比我以前想的要干净得多。我喜欢当我问一个有点利基的问题,而像你这样的人展示了堆栈溢出绝对最好的一面。非常感谢@kqr:如果您的规则是操作数必须用逗号分隔,那么您的语法将在您编写的地方有一个显式的逗号操作数。我想你还好吧
<command> ::= <cmdtype> <arguments>
<arguments> ::= <argument> <arguments>
<argument> ::= <expression>
<cmdtype> ::= print | set | read | readint | launch
<ows> ::= WS |