我已经进行了一些搜索,包括在我面前浏览红色龙书,但我还没有找到明确的答案。大多数人都在谈论缩进方面的空白敏感性,但这不是我的理由。
我想为一种简单的语言实现一个转换器。这种语言有一个"命令"的概念,它是一个保留关键字,后跟一些参数。为了让您了解我所谈论的内容,一系列命令可能如下所示:
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>
为简单起见,我们可以定义以下内容
<string> is an arbitrary sequence of characters surrounded by quotes
<int> is a sequence of characters '0'..'9'
<variable> is a sequence of characters 'a'..'z'
现在这通常不会有任何问题。实际上,只有这个规范,我有一个工作实现,lexer默默地吃掉所有空格。但是,这里有一个问题:
命令的参数必须用空格分隔!
换句话说,写
应该是非法的while running@print"hello";@
尽管就语法而言,这显然不是模棱两可的。我有两个关于如何解决这个问题的想法。
每当消耗一些空格时输出一个标记,并在语法中包含空格。我怀疑这会使语法变得更加复杂。
重写语法,而不是&#34;硬编码&#34;每个命令的参数,我有一个&#34;参数&#34;的生产规则。照顾空白。它可能看起来像
<command> ::= <cmdtype> <arguments>
<arguments> ::= <argument> <arguments>
<argument> ::= <expression>
<cmdtype> ::= print | set | read | readint | launch
然后我们可以确保词法分析器以某种方式(?)在遇到<argument>
令牌时处理前导空格。但是,这会将处理内置命令的arity(以及其他内容?)的复杂性转移到解析器中。
这通常如何解决? 当语言的语法在特定的地方需要空格但几乎在其他任何地方都是可选的时候,在词法分析器或解析器中处理它是否有意义?
我希望我可以将这种语言的规范捏造一点点,因为这样可以使实现起来更简单,但遗憾的是这是一个向后兼容的问题而且不可能。
答案 0 :(得分:2)
通常采用向后兼容性仅适用于正确的程序;接受以前会因为语法错误而被拒绝的程序不能改变任何有效程序的行为,因此不会违反向后兼容性。
在这种情况下,这可能不相关,但是,正如您所指出的那样,它会大大简化问题,似乎值得一提。
一种解决方案是将空格传递给解析器,然后将其合并到语法中;通常,您将定义一个终端WS
,并从那个非终端中为可选空格定义:
<ows> ::= WS |
如果您小心确保终端和非终端中只有一个在任何上下文中都有效,这不会影响可解析性,并且结果语法虽然有点混乱,但仍然可读。优点是它使空白规则显式化。
另一种选择是在词法分析器中处理问题;这可能很简单,但这取决于语言的确切性质。
根据您的描述,如果两个令牌没有被空格分隔,则目标是产生语法错误,除非其中一个令牌是&#34;自定界线&#34 ;;在所示的示例中,我相信唯一的这样的标记是分号,因为您似乎表明@
必须是以空格分隔的。 (可能是您的完整语言有更多自我划分的令牌,但这并没有实质性地改变问题。)
可以在词法分析器中使用单个启动条件来处理(假设您使用的是允许显式状态的词法分析器生成器);读取空格会使您处于任何令牌有效的状态(如果您使用的是lex-derivative,则为初始状态INITIAL
)。在另一个状态中,只有自定义标记才有效。读取令牌后的状态将是受限状态,除非令牌是自我分隔的。
这要求每个词法分析器操作都包含一个状态转换操作,但语法保持不变。其结果是将混乱从解析器移动到扫描器,代价是模糊了空格规则。但它可能不那么混乱,如果在你的计划中,它肯定会简化未来向空白无关方言的过渡。
有一个不同的场景,它是一个类似posix的shell,其中标识符(在shell语法中称为&#34; words&#34;)不仅限于字母字符,但可能包含任何非自定义字符字符。在posix shell中,print"hello, world"
是单个单词,与两个标记序列print "hello, world"
不同。 (第一个最终将被重新引入单个令牌printhello, world
。)
这种情况实际上只能在词汇上处理,尽管它并不一定复杂。它也可能是你问题的指南; fir exame,你可以添加一个词法规则,它接受除空格和自定界字符之外的任何字符串;最大munch规则将确保仅在令牌不能被识别为标识符或字符串(或其他有效令牌)时才采取操作,因此您可以在操作中抛出错误。
这比基于状态的词法分析器更简单,但它的灵活性稍差。