【问题标题】:Parsing a C family language out of order乱序解析 C 家族语言
【发布时间】:2013-02-05 14:42:14
【问题描述】:

C 被严格按顺序解析,即所有东西都必须在使用前声明;特别是,类型必须在这些类型的变量之前声明。这是有道理的,因为如果您不知道类型的名称是什么,而类型的名称不是什么,那么语法就会模棱两可,例如a * b 取决于 a 是否命名类型。

另一方面,一些 C 家族语言具有放松这一限制的理想特性(从而消除了对头文件的手动处理)。我正在为 C 超集语言编写一个解析器,该语言旨在同样放宽该限制,所以现在我需要弄清楚如何去做。

我想到的一种方法是进行两次传递。第一遍遍历所有内容,利用顶层的所有内容都必须是声明而不是声明的事实,并选择所有类型。在这个阶段,函数体没有被检查,只是作为由匹配的大括号分隔的标记流被拾取。第二遍解析函数体。函数中的局部声明必须是有序的,但这不是真正的问题。

这种方法有什么我没有想到的绊脚石吗?

C++、Java、C# 等的编译器通常如何处理那些不需要按顺序声明的语言部分?

【问题讨论】:

  • 在此之前你会完全预处理源代码吗?
  • 是的,预处理仍需作为单独的初始步骤进行。
  • 好吧,我放弃了这种方法,因为a * b 提出的模棱两可。顺便说一句,要考虑的其他一些事情包括不完整的类型。首先收集所有类型然后应用它们可能是不正确的。如果这样可行,但代码实际上并没有编译,因为在某些时候类型仍然不完整,该怎么办?
  • 那么第二遍就会产生错误信息。
  • 如果你保留了足够的关于事物顺序的信息。

标签: c parsing compiler-construction programming-languages


【解决方案1】:

您不必在解析时进行名称解析。首先,如果您正在设计一种“类 C”语言(而不是新的 C 实现),您可以定义您的语法,以便声明、表达式、方法等在语法中都是明确的。然后解析顺序无关紧要。 (这也可以通过将预处理器以结构化的方式集成到语言中来解决预处理器的问题)。

如果您坚持使用类似 C 的语法,您可以使用可以容忍歧义的解析器,例如,is happy to process "x*y;" 并将其作为表达式和声明保存,直到它获得更多数据。在极端情况下,只需将此视为基于约束的解决方案。 C 和 C++ 坚持首先知道定义,因为最初编译器的内存空间非常有限,您不能只保留所有内容;这不再是真的。解析时不必坚持知道答案。

我们在 DMS Software Reengineering Toolkit 中为此使用 GLR 解析器,并且非常高兴能够很好地解析 C 和 C++11。我们在解析后进行名称解析;这将解析和名称解析隔离开来,从而使前端更简洁、更易于管理。

【讨论】:

    【解决方案2】:

    C++ 确实需要按顺序声明。

    请记住,C 和 C++ 是完全不同的游戏。他们正在使用非常古老的链接器技术(C 因为它古老的,C++ 因为它几乎与 C 链接器一样古老并且兼容)。两者都会生成直接在 CPU 上运行的二进制文件,没有运行时支持。

    Java 和 C# 有很多改进的链接器可供依赖,以及庞大的运行时支持系统可供使用。

    各有利弊。 C/C++ 方法的缺点之一是必须在编译时解决所有问题,因为在运行时应用程序是独立的。优点是一切都在编译时解决,因此在运行时应用程序可以单独运行。

    【讨论】:

    • 其实这是一个很好的观点,C++ 不需要在调用类中的函数之前声明它们,但它仍然需要在使用它们之前声明类型。我打算坚持使用 C 链接器技术,但我希望这并不妨碍在解析方面进行一些改进。
    【解决方案3】:

    几乎所有的编译器都会做两遍。另一种方法是允许在语法本身中声明变量,这会使语法更难手动解析,但无需第二遍。

    【讨论】:

    • 进行多次传递是很常见的,但不是在解析阶段本身。我不确定在事先不知道类型的情况下,如何编写语法来消除变量声明和非变量声明之间的歧义;您能更具体地说明您的想法吗?
    猜你喜欢
    • 2015-07-20
    • 2011-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-16
    • 2013-09-12
    相关资源
    最近更新 更多