上下文相关的标记化是否需要词汇语法中的多个目标符号?

Does context-sensitive tokenisation require multiple goal symbols in the lexical grammar?

根据 ECMAScript spec:

There are several situations where the identification of lexical input elements is sensitive to the syntactic grammar context that is consuming the input elements. This requires multiple goal symbols for the lexical grammar.

两个这样的符号是InputElementDivInputElementRegExp

在 ECMAScript 中,/ 的含义取决于它出现的上下文。根据上下文,/ 可以是除法运算符、正则表达式文字的开头或注释分隔符。词法分析器无法自行区分除法运算符和正则表达式文字,因此它必须依赖解析器的上下文信息。

我想了解为什么这需要在词法语法中使用多个目标符号。我对语言设计了解不多,所以我不知道这是由于语法的某些正式要求还是只是约定。

问题

InputElement ::
     [...]
     DivPunctuator
     RegularExpressionLiteral
     [...]

并让解析器告诉词法分析器使用哪个产生式(DivPunctuator vs RegExLiteral),而不是使用哪个目标符号(InputElementDiv vs InputElementRegExp) ?

说词汇产生式“对使用输入元素的句法语法上下文敏感”并没有使语法上下文敏感,在该术语的形式语言定义中。事实上,几乎在每一个非平凡的语法中都有“对句法语法上下文敏感”的产生式。这是解析的本质:句法上下文有效地提供了一组潜在的可扩展非终结符,这些非终结符在不同的句法上下文中会有所不同,这意味着,例如,在大多数语言中,不能在期望表达式的地方输入语句(尽管通常情况下,表达式是语句的一种表现形式)。

但是,差异不涉及对相同非终端的不同扩展。 “上下文无关”语言的要求是,无论非终结符出现在何处,非终结符的可能派生集都是相同的集合。所以上下文可以提供非终结符的不同选择,但是每个非终结符都可以在不考虑其上下文的情况下进行扩展。这就是语法不受上下文影响的意思。

正如您所注意到的,上下文敏感性通常在语法中被抽象为在左侧具有模式的语法,而不是单个非终结符。在最初的定义中,上下文——除要扩展的非终结符之外的所有内容——都需要原封不动地通过产生式;只能扩展一个非终端,但可能的扩展取决于上下文,如产品所示。上面隐含的是,有些语法可以用 BNF 编写,甚至不符合上下文敏感性规则(或其他一些等效规则)。所以它不是二元除法,要么是上下文无关的,要么是上下文敏感的。语法可能两者都不是(并且,由于空上下文仍然是上下文,因此任何上下文无关语法也是上下文敏感的)。最重要的是,当数学家说话时,他们用词的方式有时是出乎意料的。但它总是有一个明确的基本定义。

在形式语言理论中,没有词汇和句法产生式;只是制作。如果词汇产生式和句法产生式都没有上下文,那么整个语法就是没有上下文的。但是,从实用的角度来看,组合语法更难解析,原因有很多,我不打算在这里详述。事实证明,为一种语言编写语法并解析它们要容易一些,在词法解析器和句法解析器之间进行划分。

在经典模型中,首先进行词法分析,因此解析器看不到单个字符。相反,句法分析是使用“词汇标记”的“字母表”(在非常广泛的意义上)完成的。这非常方便——这意味着,例如,词法分析可以简单地删除 whitespace 和注释,这大大简化了句法语法的编写。但它也降低了通用性,正是因为语法分析器不能“指挥”词法分析器做任何事情。在语法分析器意识到它的需要之前,词法分析器已经完成了它要做的事情。

如果解析器能够指导词法分析器,它会以与指导自身相同的方式进行操作。在某些产品中,令牌非终端将包括 InputElementDiv,而在其他产品中,InputElementRegExp 将是可接受的非终端。正如我所指出的,这不是上下文敏感——它只是上下文无关语法的正常功能——但它确实需要修改程序的组织以允许词法分析器考虑解析器的目标.这通常被称为(从业者,而不是理论家)“词汇反馈”,有时被称为价值中立性较低的术语;它有时被认为是语言设计中的一个弱点,因为整齐隔离的 lexer/parser 架构被破坏了。 C++ 是一个非常激烈的例子,确实也有人类难以解析的 C++ 程序,这是某种迹象。但是 ECMAScript 并没有真正遭受这个问题的困扰;人类通常无需付出任何显着的智力努力即可区分除法运算符和正则表达式定界符。而且,虽然实现 ECMAScript 解析器所需的词法反馈确实使架构不那么整洁,但这也确实不是一项艰巨的任务。

无论如何,词法语法中的“目标符号”只是 ECMAScript 参考文献的作者决定使用的一个短语。那些“目标符号”只是普通的词法非终结符,就像任何其他产生式一样,所以说有“多个目标符号”和说“解析器指示词法分析器使用不同的产生式”没有区别,我希望能解决您提出的问题。

备注

  1. 两种语境的词汇差异不仅仅是/的意思不同。如果仅此而已,那么根本不需要词汇反馈。问题是标记化本身发生了变化。如果可以使用运算符,则

    中的 /=
    a /=4/gi;
    

    是单个标记(复合赋值运算符),gi 是单个标识符标记。但是,如果此时可以使用正则表达式文字(但事实并非如此,因为正则表达式文字不能跟在标识符之后),那么 /= 将是单独的标记,g 也是如此和 i.

  2. 一些程序员更喜欢从一组产品构建的解析器(但不是写这篇文章的人:-));它们通常被称为“无扫描器解析器”。在 ECMAScript 的无扫描器解析器中,不会有词法反馈,因为没有单独的词法分析。

  3. 形式语言理论的理论纯度与编写实际编程语言的工作解析器的实际细节之间确实存在差距。理论模型非常有用,如果不了解它们就很难编写解析器。但是很少有解析器严格遵守模型,这没关系。同样,通常称为“正则 表达式”的东西在形式语言意义上根本不是正则的;一些“正则表达式”运算符甚至不是上下文无关的(反向引用)。因此,假设某些理论结果(“正则表达式可以在线性时间和常数 space 中识别”)对于“正则表达式”库实际上是正确的,那将是一个巨大的错误。我不认为解析理论是唯一表现出这种二分法的计算机科学分支。

Why not just use a single goal symbol like so:

InputElement ::
  ...
  DivPunctuator
  RegularExpressionLiteral
  ...

and let the parser tell the lexer which production to use (DivPunctuator vs RegExLiteral), rather than which goal symbol to use (InputElementDiv vs InputElementRegExp)?

请注意,DivPunctuator 和 RegExLiteral 本身并不是产品,而是非终结符。在这种情况下,它们是您为 InputElement 提出的产品中的右侧(备选方案)。因此,我将您的问题改写为:为什么不让句法解析器告诉词法解析器使用这两种替代方案中的哪一种? (或者等价地,抑制这两者中的哪一个。)

在 ECMAScript 规范中,有一种机制可以实现这一点:语法参数(在 section 5.1.5 中解释)。

例如,您可以定义参数 Div,其中:

  • +Div 表示“斜杠应被识别为 DivPunctuator”,并且
  • ~Div 表示“应将斜杠识别为 RegExLiteral 的开头”。

那么你的作品就会变成

InputElement[Div] ::
  ...
  [+Div] DivPunctuator
  [~Div] RegularExpressionLiteral
  ...

但是请注意,句法解析器仍然必须告诉词法解析器使用 InputElement[+Div]InputElement[~Div] 作为目标符号,因此您回到规范的当前解决方案,模重命名。

What are some other languages that use multiple goal symbols in their lexical grammar?

我认为大多数人不会尝试定义一个派生所有标记(或输入元素)的单一符号,更不用说将它分成像 ECMAScript 的 InputElementFoo 这样的变体了,所以可能很难找到另一种语言其规格类似。

相反,简单地为不同种类的标记(例如标识符、数字文字)定义语法规则,然后从语法产生式中引用它们是很常见的。所以这有点像有多个词汇目标符号,但不是(我会说)你问的那种感觉。

How would we classify the ECMAScript lexical grammar?

它基本上是上下文无关的,加上一些扩展。