【问题标题】:How should I go about building a simple LR parser?我应该如何构建一个简单的 LR 解析器?
【发布时间】:2010-02-23 19:23:50
【问题描述】:

我正在尝试为一种模板(配置)文件构建一个简单的 LR 解析器,该文件将用于生成其他一些文件。我已经阅读并阅读了有关 LR 解析器的信息,但我似乎无法理解它!我知道有一个解析堆栈、一个状态堆栈和一个解析表。令牌被读入解析堆栈,当规则匹配时,令牌被移动或减少,具体取决于解析表。这会递归地继续,直到所有标记都被减少并且解析完成。

问题是我真的不知道如何生成解析表。我已经阅读了很多描述,但语言是技术性的,我就是不明白。谁能告诉我该怎么做?

另外,我将如何存储我的语法规则之类的东西?

http://codepad.org/oRjnKacH 是我尝试解析的文件示例,我尝试对其语言进行语法分析。

我以前从未这样做过,所以我只是在寻求一些建议,谢谢。

【问题讨论】:

  • 如果你想了解LR解析背后的理论,最好的资源是龙书:en.wikipedia.org/wiki/Dragon_Book_%28computer_science%29
  • 我在编译器书籍中阅读了一些关于LR解析的描述,但我不明白。在我开始自己弄清楚一些事情之前,我作为最后的手段来到这里。

标签: c++ parsing configuration file


【解决方案1】:

在您对解析器理论的研究中,您似乎忽略了一个更实际的事实:几乎没有人考虑过像您所讨论的那样手动编写一个表驱动、自下而上的解析器。对于大多数实际用途,手写解析器使用自顶向下(通常是递归下降)结构。

使用表驱动解析器的主要原因是它允许您编写(相当)少量的代码来操作表等,这几乎是完全通用的(即它适用于任何解析器)。然后,您将有关特定语法的所有内容编码为计算机易于操作的形式(即一些表格)。

显然,如果您真的想这样做,完全可能手动完成,但几乎从来没有真正的意义。完全手动生成表格本身就非常痛苦。

例如,您通常首先构建一个 NFA,它是一个大表 - 通常,每个解析器状态为一行,每个可能的输入为一列。在每个单元格中,您编码下一个状态以在您开始时进入该状态,然后接收该输入。大多数这些转换基本上是空的(即他们只是说当您处于该状态时不允许输入)。注意:由于有效的转换非常稀疏,大多数解析器生成器都支持某种方式来压缩这些表,但这并没有改变基本思想)。

然后,您逐步完成所有这些操作,并遵循一些相当简单的规则来收集 NFA 状态集,从而成为 DFA 中的一个状态。这些规则很简单,很容易将它们编程到计算机中,但是您必须为 NFA 表中的每个单元重复它们,并且基本上完美地记账以生成有效的 DFA正确。

计算机可以而且将会很好地做到这一点——对它来说,对 NFA 状态表中两万个单元格中的每一个单元格应用几个简单的规则是小菜一碟。很难想象让一个人做同样的事情——我很确定根据联合国的指导方针,那将是非法的酷刑。

【讨论】:

  • 谢谢,这是迄今为止最好的回应。我决定尝试自下而上的方法,因为在阅读完所有内容后,我觉得这是最好的方法。当然,如果您想要一些完全通用的东西,正如您所说,但显然我不在这里。我现在再看看递归下降。
【解决方案2】:

经典的解决方案是 lex/yacc 组合:

http://dinosaur.compilertools.net/yacc/index.html

或者,正如 gnu 所说的那样 - flex/bison。

编辑:

Perl 有 Parse::RecDescent,它是一个递归下降解析器,但它可能更适合简单的工作。

【讨论】:

  • 对不起,你可能没听懂。我试图从“手动”创建东西的过程中学习一些东西,所以我不想使用生成器。
  • @Isaac:好吧,我认为您通常是在尝试解析,而不是特别尝试学习解析。无论如何,你会在 yacc/bison 的实现中找到丰富的信息:他们做你想学的。
【解决方案3】:

你需要了解 ANTLR

【讨论】:

    【解决方案4】:

    我查看了您的文件格式的定义,虽然我遗漏了一些为什么您需要专门的 LR 解析器的上下文,但我的第一个想法是为什么不使用现有格式,如 xml 或 json。沿着 parsergenerator 路线走通常会产生高昂的启动成本,这不会为您要解析的简单数据带来回报。

    正如保罗所说的 lex/yacc 是一个选项,您可能还想看看Boost::Spirit

    我都没有使用过,一年前,Qt/Nokia 的人使用QLALR 编写了一个更大的解析器。当我研究解析器时,尽管文档很少,但它的启动占用空间最小(只有 1 个工具),但它不支持词法分析。 IIRC 当时我无法弄清楚 ANTLR 中的 C++ 支持。

    10,000 英里视图:通常,您正在查看两个组件,一个词法分析器,它获取输入符号并将它们转换为更高阶的标记。要使用标记,您的语法描述将说明规则,通常您会在规则中包含一些代码,这些代码将在规则匹配时执行。编译器生成器(例如 yacc)会将您对规则和代码的描述转化为可编译的代码。除非您手动执行此操作,否则您不会自己操作表格。

    【讨论】:

      【解决方案5】:

      好吧,你不能理解它像

      “函数 A1 对对象 B 执行 f,然后函数 A2 对 D 执行 g 等等”

      更像

      "函数 A 执行动作 {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o 或 p,或无操作} 并移位/减少对 {B,C,D,E,F,or G} 类型的堆栈头上的对象 {1-1567} 及其包含的对象进行一定的计数etc} 根据规则列表进行某些组合"

      它确实需要一个数据表(或从数据表之类的东西生成的代码,比如一组 BNF 语法数据)告诉函数要做什么。

      您可以从头开始编写它。你也可以用睫毛刷刷墙。您可以在运行时解释数据表。你也可以把 Sleep(1000);每隔一行在你的代码中声明。我也没试过。

      编译器很复杂。因此编译器生成器。

      编辑

      您正在尝试根据文件本身的内容来定义标记。

      我认为您“不想使用正则表达式”的原因是您希望能够访问文本块中不同标记的行号信息,而不仅仅是整个文本块。如果每个单词的行号是不必要的,并且整个块将适合内存,我倾向于将整个括号内的块建模为一个标记,因为这可能会提高处理速度。无论哪种方式,您都需要一个自定义的 yylex 函数。首先使用 lex 生成一个带有固定标记“[”和“]”的内容开始和结束,然后将其冻结并修改它以获取有关要从 yacc 代码中查找哪些标记的更新数据。

      【讨论】:

      • 我意识到这很复杂,我也知道我要解析的文件相对简单。你看了吗?我不是在编写编程语言或编译器,我只是从文件中查找信息,我不想使用正则表达式。你对我想出的语法有任何 cmet 吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-26
      • 2010-09-20
      • 2021-06-05
      • 2021-03-31
      • 1970-01-01
      • 2020-08-23
      • 1970-01-01
      相关资源
      最近更新 更多