我正在为ANTLR中的一小部分C编写一个词法分析器/解析器,它将在Java环境中运行 . 我是语言语法世界的新手,在许多ANTLR教程中,他们创建了一个AST - 抽象语法树,我被迫创建一个,为什么?
使用ANTLR创建AST已纳入语法 . 您不必这样做,但它是一个非常好的工具,可以满足更复杂的要求 . 这是您可以使用的树构造tutorial .
基本上,使用ANTLR解析源时,您有几个选项 . 您可以使用语法中的重写规则生成代码或AST . AST基本上是源的内存表示 . 从那里,你可以做很多事情 .
已经's a lot to ANTLR. If you haven',我建议得到the book .
我找到了创建ANTLR的Terence Parr撰写的关于jGuru的问题的this answer . 我从链接的网站here复制了这个解释:
只有简单的,所谓的语法指导翻译才能在解析器中使用动作完成 . 这些类型的翻译只能吐出构造,这些构造是在解析中的那一点上已经看到的信息的函数 . 树解析器允许您走一个中间形式并操纵该树,逐步将其转换为几个平移阶段,最终形式可以轻松打印出来作为新的翻译 .
想象一个简单的翻译问题,你想要打印出一个 Headers 为“有n个项目”的html页面,其中n是你在输入流中找到的标识符数 . 必须在 Headers 之后打印ID,如下所示:
<html> <head> <title>There are 3 items</title> </head> <body> <ol> <li>Dog</li> <li>Cat</li> <li>Velociraptor</li> </body> </html>
来自输入
Dog Cat Velociraptor
因此,通过语法中的简单操作,您如何计算 Headers ?你不能没有阅读整个输入 . 好的,现在我们知道我们需要一个中间形式 . 最好的通常是我发现的AST,因为它记录了输入结构 . 在这种情况下,它只是一个列表,但它证明了我的观点 .
好的,现在你知道除了简单的翻译之外,树是一件好事 . 给定一个AST,你如何得到它的输出?想象一下简单的表达树 . 一种方法是在树特定类中创建节点,如PlusNode,IntegerNode等 . 然后你只要求每个节点打印出来 . 对于输入,3 4你会有树:
| 3 - 4
和课程
class PlusNode extends CommonAST { public String toString() { AST left = getFirstChild(); AST right = left.getNextSibling(); return left + " + " + right; } } class IntNode extends CommonAST { public String toString() { return getText(); } }
给定表达式树,您可以使用t.toString()将其转换回文本 . 那么,这有什么问题?似乎工作得很好,对吗?它似乎在这种情况下工作得很好,因为它很简单,但我认为,即使对于这个简单的例子,树语法也更具可读性,并且是对您在PlusNode.toString()中编码的精确描述的形式化描述 .
expr returns [String r] { String left=null, right=null; } : #("+" left=expr right=expr) {r=left + " + " + right;} | i:INT {r=i.getText();} ;
请注意,特定类(“异构AST”)方法实际上是通过hand in toString()为#(INT INT)编码完整的递归下降解析器 . 作为解析器生成器的人,这会让你感到畏缩 . ;)
异构AST方法的主要缺点是它无法方便地访问上下文信息 . 在递归下降解析器中,您可以轻松访问上下文,因为它可以作为参数传入 . 您还可以通过查看语法来准确地知道哪个规则可以调用哪个其他规则(例如,此表达式是WHILE条件还是IF条件?) . 上面的PlusNode类存在于一个独立的,孤立的世界中,它不知道谁会调用它的toString()方法 . 更糟糕的是,程序员无法通过阅读来调用它将在哪个上下文中调用 .
总之,向输入解析器添加操作适用于非常简单的翻译,其中:
输出结构的顺序与输入顺序相同
所有构造都可以从解析的信息生成,直到需要吐出它们为止
除此之外,你需要一个中间形式 - 通常AST是最好的形式 . 使用语法来描述AST的结构类似于使用语法来解析输入文本 . 特定于域的高级语言(如ANTLR)中的形式化描述优于手动编码解析器 . 树语法中的动作具有非常清晰的上下文,可以方便地访问从调用线索传递的信息 . 使用树语法操纵树以进行多遍转换的翻译也更容易 .
我认为 AST 的创建是可选的 . Abstract Syntax Tree非常有用用于后续处理,如解析程序的语义分析 .
只有您可以决定是否需要创建一个 . 如果您的唯一目标是语法验证,那么您不需要生成一个 . 在javacc(类似于ANTLR)中有一个名为JJTree的实用程序,它允许生成AST . 所以我想这在ANTLR中也是可选的 .
3 回答
使用ANTLR创建AST已纳入语法 . 您不必这样做,但它是一个非常好的工具,可以满足更复杂的要求 . 这是您可以使用的树构造tutorial .
基本上,使用ANTLR解析源时,您有几个选项 . 您可以使用语法中的重写规则生成代码或AST . AST基本上是源的内存表示 . 从那里,你可以做很多事情 .
已经's a lot to ANTLR. If you haven',我建议得到the book .
我找到了创建ANTLR的Terence Parr撰写的关于jGuru的问题的this answer . 我从链接的网站here复制了这个解释:
只有简单的,所谓的语法指导翻译才能在解析器中使用动作完成 . 这些类型的翻译只能吐出构造,这些构造是在解析中的那一点上已经看到的信息的函数 . 树解析器允许您走一个中间形式并操纵该树,逐步将其转换为几个平移阶段,最终形式可以轻松打印出来作为新的翻译 .
想象一个简单的翻译问题,你想要打印出一个 Headers 为“有n个项目”的html页面,其中n是你在输入流中找到的标识符数 . 必须在 Headers 之后打印ID,如下所示:
来自输入
因此,通过语法中的简单操作,您如何计算 Headers ?你不能没有阅读整个输入 . 好的,现在我们知道我们需要一个中间形式 . 最好的通常是我发现的AST,因为它记录了输入结构 . 在这种情况下,它只是一个列表,但它证明了我的观点 .
好的,现在你知道除了简单的翻译之外,树是一件好事 . 给定一个AST,你如何得到它的输出?想象一下简单的表达树 . 一种方法是在树特定类中创建节点,如PlusNode,IntegerNode等 . 然后你只要求每个节点打印出来 . 对于输入,3 4你会有树:
| 3 - 4
和课程
给定表达式树,您可以使用t.toString()将其转换回文本 . 那么,这有什么问题?似乎工作得很好,对吗?它似乎在这种情况下工作得很好,因为它很简单,但我认为,即使对于这个简单的例子,树语法也更具可读性,并且是对您在PlusNode.toString()中编码的精确描述的形式化描述 .
请注意,特定类(“异构AST”)方法实际上是通过hand in toString()为#(INT INT)编码完整的递归下降解析器 . 作为解析器生成器的人,这会让你感到畏缩 . ;)
异构AST方法的主要缺点是它无法方便地访问上下文信息 . 在递归下降解析器中,您可以轻松访问上下文,因为它可以作为参数传入 . 您还可以通过查看语法来准确地知道哪个规则可以调用哪个其他规则(例如,此表达式是WHILE条件还是IF条件?) . 上面的PlusNode类存在于一个独立的,孤立的世界中,它不知道谁会调用它的toString()方法 . 更糟糕的是,程序员无法通过阅读来调用它将在哪个上下文中调用 .
总之,向输入解析器添加操作适用于非常简单的翻译,其中:
输出结构的顺序与输入顺序相同
所有构造都可以从解析的信息生成,直到需要吐出它们为止
除此之外,你需要一个中间形式 - 通常AST是最好的形式 . 使用语法来描述AST的结构类似于使用语法来解析输入文本 . 特定于域的高级语言(如ANTLR)中的形式化描述优于手动编码解析器 . 树语法中的动作具有非常清晰的上下文,可以方便地访问从调用线索传递的信息 . 使用树语法操纵树以进行多遍转换的翻译也更容易 .
我认为 AST 的创建是可选的 . Abstract Syntax Tree非常有用用于后续处理,如解析程序的语义分析 .
只有您可以决定是否需要创建一个 . 如果您的唯一目标是语法验证,那么您不需要生成一个 . 在javacc(类似于ANTLR)中有一个名为JJTree的实用程序,它允许生成AST . 所以我想这在ANTLR中也是可选的 .