【问题标题】:Which contemporary computer languages are LL(1)?哪些当代计算机语言是 LL(1)?
【发布时间】:2017-05-15 20:42:18
【问题描述】:

(我将假期时间花在一些语言理论上。如果这是一个幼稚的问题,请原谅。)

根据here

LL 文法,尤其是 LL(1) 文法,非常实用 兴趣,因为这些语法的解析器很容易构建,并且 由于这个原因,许多计算机语言被设计为 LL(1)。

那么,出于好奇,哪些当代计算机语言是 LL(1) ? C、Java、C# 或 Python 是否属于这一类?

【问题讨论】:

  • 我确实阅读了一篇关于这个主题的非常有趣的文章[并使用语法和正则表达式],并以为我已经为它添加了书签,但不幸的是它似乎没有。我似乎认为 Perl 在那里。 (很抱歉这个全面无益的评论)
  • @Martin Perl 绝对不是 LL(1)。解析 Perl 是不可判定的。
  • 然后是反转,Perl 在底部(LL4?)。有趣的是,这是唯一由语言学家编写的语言,因此作为人类编写比作为机器阅读要容易得多(相对而言)。
  • 问这个问题的更好的地方是Lambda the Ultimate。请参阅相关问题Good languages with simple grammar
  • @martin:LL(k) 中的(k) 指的是您可能需要查看的前瞻令牌的数量,以便决定如何处理当前令牌。许多语言根本不是LL,实际上,增加k 的值很少有帮助,尽管如果你允许k 不受限制,你可以增加LL 解析的能力(参见ANTLR,例如)。在这种情况下,解析器不再是线性时间,您可能需要更强大的算法,例如 LR。

标签: compiler-construction formal-languages ll


【解决方案1】:

我想我很想用[citation needed] 标记维基百科的引用;这些假设至少是有问题的。例如,有大量基于yacc 的编译器构建工具,在实践中,使用更强大(同样快速)的 LALR 算法构建解析器非常容易,有些还实现了更多强大的 GLR 算法(在大多数实用语法中几乎一样快)。几十年来,手写解析器已经不再需要了。 [注1]

通过尝试回答问题:

  1. 大多数计算机语言在“技术上”不是 LL,因为它们甚至不是上下文无关的。这将包括需要声明标识符的语言(C、C++、C#、Java 等),以及具有预处理器和/或宏工具的语言(C 和 C++ 等),具有歧义的语言只能是使用语义信息解决(Perl 将是最严重的违规者,但 C 和 C++ 也在那里)。而且,为了更多地传播欢乐,它还包括具有类似 Python 布局意识的语言(当然是 Python,还有 Haskell)。

    我在“技术上”周围加上了吓人的引号,因为很多人会说这些例外“不算数”。这是他们的意见,他们有权这样做(无论如何我已经放弃了争论,尽管我不同意这种意见)。例如,您可以通过在应用语法之前预处理文本来消除 C/C++ 中的预处理器,或者通过插入 INDENT、NEWLINE 和 DEDENT 标记而不是空格来预处理可识别空格的语言,之后您可以提出某种声明关于一种神秘的“核心语言”。 (这对于 C++ 模板来说要困难得多,只能通过解析文本来消除,所以不能说在解析之前就可以进行扩展。)

    声称一种语言不是上下文无关的,因为它需要声明标识符,这可能更具争议性。在某些语言中(不是 C/C++,其中需要语义分析以避免歧义),您可能会争辩说可以在不验证声明规则的情况下构造解析树,这使得该规则“不是语法”。但是,您仍然可以在语法上强制执行声明规则;你不能用上下文无关的语法来做到这一点(例如,你可以使用Van Wijngaarden grammar)。

    在任何情况下,一种常见的解析策略是识别一种语言的超集,然后通过对生成的解析树进行后续分析来拒绝不符合要求的程序;在这种情况下,问题就变成了超集是否是 LL。在我看来,为了有意义,每个有效的程序都必须能够被解析为正确的语法树,这消除了使用语义分析来消除歧义。

  2. 解析的目标是生成语法树,而不仅仅是识别文本是否有效。 (如果您略读倾向于关注识别的正式语言教科书,您可能会错过这个重要的事实,可能是因为语法树的构造不太有趣。)因此,坚持提出的语法实际上代表句法结构似乎是合理的。语言。

    例如,您可以使用简单的正则语言识别代数表达式:

    (PREFIX* OPERAND POSTFIX*) (INFIX PREFIX* OPERAND POSTFIX*)*
    

    其中 PREFIX 是前缀运算符集以及 (,POSTFIX 是后缀运算符集(如果有)以及 ),INFIX 是中缀运算符集(加法、减法、乘法、等),而 OPERAND 是标识符或常量。

    那个正则表达式不会正确拒绝带有不平衡括号的表达式,但我们已经同意识别语言的超集是可以的,对吧? :-)

    如果需要,我们可以从 PREFIX 和 POSTFIX 集中删除括号,并使 OPERAND 成为递归产生式。生成的文法通常是 LL(1) [注 2]:

    operand    => IDENTIFIER | CONSTANT | '(' expression ')'
    term       => operand | PREFIX operand | operand POSTFIX
    expression => term | term INFIX expression
    

    然而,问题在于该语法没有捕获运算符优先级。它甚至没有尝试承认a+b*ca*b+c 都是和的事实,因此在这两种情况下顶级运算符都是+,而不是表达式中出现的任何运算符。 (如果你在解析 APL,这不会是一个问题。但大多数语言都符合对运算符优先级的通常期望。)

    这一点很重要,因为 LL 语法不能处理左递归产生式,并且您需要左递归产生式才能正确解析左关联运算符。 (也就是说,正确地将 a-b-c 解析为 ((a-b)-c) 而不是 (a-(b-c)),这将具有不同的值。)同样,您可能会认为这是一个小问题,因为您可以按顺序对解析树进行后处理纠正关联性。这当然是对的,但结果是你用来解析的语法与你用来解释语言语法的语法不同。这可能会或可能不会打扰您。

    值得在这里补充的是,有些语言(Haskell、Prolog 可能还有更多)可以在程序文本中定义运算符及其优先级。这显然使得在没有语义分析的情况下无法生成正确的语法树,而解析此类语言的常用方法是精确使用上述简化的“交替操作数和运算符”语言。一旦所有运算符的优先级都知道了,大概是在解析结束时,然后使用类似 Shutting Yard 算法的东西重新分析所有表达式,以便产生正确的解析。

  3. 假设我们放弃上述反对意见,只问“LL 解析器可以用于哪些常用的编程语言?”

    但是,为了避免作弊,我们确实应该要求 LL 解析器具有固定的前瞻,并且不需要回溯。如果您允许任意前瞻和回溯,那么您将大大扩展可解析语言的领域,但我认为您不再属于 LL 领域。例如,这将消除 C++ 的无模板和无预处理器子集,即使常见的编译器实现使用递归下降解析器,因为需要回溯来解决 "Most Vexing Parse" 歧义。 (无论如何,你不能真正从 C++ 中删除模板,用模板进行解析真的很难。[注 3])

    Java 和 Python 都被设计为 LL(1)“伪可解析”。我相信 Haskell 也属于这一类。如果没有语义信息,C 就无法正确解析(经典歧义:(x)*(y) 是强制转换还是乘法?——这取决于 x 是否已被 typedef 或声明为变量)但我很确定它可以使用非回溯递归下降解析器捕获。我没有深入研究过 C#,但 this answer by Eric Lippert 建议它在某些情况下需要回溯。

注意事项

  1. 当然,人们仍然这样做,而且在许多情况下是有充分理由的(例如,产生更好的错误消息)。但是“很难构造一个 LALR 解析器”不是一个很好的理由,因为它不是。

  2. 这不完全是 LL,因为我没有左因子。但这很容易做到;我会把它留作练习。

  3. Is C++ context-free or context-sensitive?。还有 Todd Veldhuizen 的经典作品C++ Templates are Turing Complete

【讨论】:

  • 对我来说,这有点像物理计算机不是通用的图灵机,因为它们的内存有限。微不足道的事实,但与图灵机提供了一种思考物理计算机的好方法这一事实完全一致。同样,现实生活中编程语言几乎从来都不是上下文无关的,但这个琐碎的事实与上下文无关语法提供了一种思考许多此类语言的好方法这一事实完全一致。
  • 您的“技术上”似乎是基于未能区分“语言”和“语言的语法”。 “时间过得真快”是一个语法有效的句子,在语义上几乎没有意义。语言的语法是 LL(1),并不要求所有语法上有效的程序文本也是有效的程序。此外,语言的某些符号具有特殊词汇表示(Python 中的缩进)的情况不会导致该语言不是 LL(1)。你提到大众语法。 VW 语法区分任意表示和语法终结符号。
  • 此外,大众语法相当特殊,因为它们是图灵等效语言,因此可以表达编程语言的完整语义。 (正如 Cleaveland 和 Uzgalis 在他们的“Grammars for Programming Languages”一书中所展示的那样。)
  • 这似乎是一个扩展评论,而不是一个答案。你是正确的,像 C 和 C++ 这样的语言是上下文敏感的,因为它们需要语义信息来解决句法歧义。但是可以声称所有需要声明标识符的语言都是上下文敏感的。例如,Pascal 需要声明,但这是语义要求——这些声明不是解决语法歧义或构建解析树所必需的。
  • 在某种程度上,这就是押韵的重点,也是乔姆斯基试图用语法正确的话语表达关于无色绿色思想的内容。同样地,我实际上不需要知道任何关于 Pascal 程序语义的东西来观察标识符不在范围内。在我看来,这更像是主谓分歧(句法),而不是像愤怒的睡眠这样的语义违规。正如我所说,我承认不是每个人都会从这个角度来处理语法,但这绝对是我作为数学语言学家学习这些术语的方式,我认为......
【解决方案2】:

如果我将“计算机语言”比“编程语言”更广泛地理解,我相信您可以考虑几种声明性语言,尽管它也可能取决于您认为当代的语言。

可能的候选人:

  • XML
  • LLVM 红外
  • 许多配置语言,例如 INI 文件
  • 一些网络协议
  • 某些数据格式,例如 CSV 文件

描述正则表达式的语言的大多数(全部?)风格不是正则表达式,而是 LL(1)。

对于实际的编程语言,这些语言可能都太老了,不能被认为是当代的,而且许多都有可能违反 LL(k) 的通用扩展。

  • 汇编语言
  • 基本的
  • LISP
  • Wirth 语言:Pascal、Modula、Oberon
  • 序言

我对上述所有语言的了解不足以得出结论,这就是为什么我建议它们是可能的候选者。如果您知道其中任何一个语法歧义,请在 cmets 中教育我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-29
    • 1970-01-01
    • 2020-08-09
    • 1970-01-01
    相关资源
    最近更新 更多