除非您在词法扫描器中执行某些操作以将空格传递给解析器,否则解析器无能为力。
了解为什么this.field.array[5] 必须写成不带空格会很有用。 (或者,也许,大部分没有空格:也许this.field.array[ 5 ] 是可以接受的。)如果有空格,还有其他解释吗?还是只是脚本语言设计者的一些错误的审美判断?
第二种情况要简单得多。如果唯一的可能性是没有空格或语法错误的正确解析,则只需要在解析器识别后验证表达式。一个简单的验证函数将简单地检查每个标记的起始位置(可用 p.lexpos(i) 提供,其中 p 是操作函数的参数,i 是生产的 RHS 的标记的索引)正是前一个令牌加上前一个令牌的长度。
要求索引字段的名称紧跟. 的一个可能原因是简化词法扫描器,以防希望将其他保留字用作成员名称。理论上,任何标识符(包括语言关键字)都没有理由不能用作object.field 这样的表达式中的成员选择器。 . 是一个明确的信号,表明以下标记是成员名称,而不是不同的句法实体。例如,JavaScript 允许任意标识符作为成员名称;虽然它可能会让读者感到困惑,但没有什么能阻止你写obj.if = true。
不过,这对词法扫描器来说是一个很大的挑战。为了正确分析输入流,需要了解每个标识符的上下文;如果标识符紧跟在用作成员选择器的. 之后,则必须禁止关键字识别规则。这可以使用大多数词法分析器生成器中可用的词法状态来完成,但这绝对是一个复杂的问题。或者,可以采用成员选择器是单个标记的规则,包括.。在这种情况下,obj.if 由两个令牌(obj、IDENTIFIER 和 .if、SELECTOR)组成。最简单的实现是使用\.[a-zA-Z_][a-zA-Z0-9_]* 之类的模式识别SELECTOR。 (这不是 JavaScript 所做的。在 JavaScript 中,不仅可以在 . 和选择器之间插入任意空格,甚至可以在 cmets 之间插入任意空格。)
根据 OP 的评论,这似乎是设计原始脚本语言的部分原因,尽管它没有解释在 . 或 [ 运算符之前禁止空格。
有些语言可以根据周围空格的存在与否来解决语法歧义,例如在可以是一元或二元 (Swift) 的消歧运算符中;或将| 用作布尔运算符与将其用作绝对值表达式进行区分(不常见,但请参见https://cs.stackexchange.com/questions/28408/lexing-and-parsing-a-language-with-juxtaposition-as-an-operator);甚至将 (...) 在分组表达式中的使用与它们在函数调用中的使用区分开来。 (例如,Awk)。因此,当然可以想象一种语言,其中. 和/或[ 标记根据周围空白的存在或不存在而具有不同的解释。
如果您需要区分带有和不带有周围空格的标记的情况,以便语法可以以不同的方式识别它们,那么您需要将空格作为标记传递,这会污染整个语法,或者提供两个(或更多)不同版本的标记,其语法因空格而异。您可以使用正则表达式来做到这一点,但在词法操作本身中这样做可能更容易,再次使用词法分析器状态。请注意,词法分析器状态包括lexdata(输入字符串本身)和lexpos(下一个输入字符的索引);当前标记中第一个字符的索引在标记的lexpos 属性中。因此,例如,如果t.lexpos == 0 or t.lexer.lexdata[t.lexpos-1].isspace(),则令牌前面有空格,如果t.lexer.lexpos == len(t.lexer.lexdata) or t.lexer.lexdata[t.lexer.lexpos].isspace(),则后面有空格。
一旦你将令牌划分为两种或更多令牌类型,你会发现在大多数制作中你真的不需要划分。因此,您通常会发现为每个表示该标记的所有空白上下文变体的标记类型定义一个新的非终结符很有用;然后,您只需要在重要的产品中使用特定的变体。