在Java中解析算术表达式并从中构建树

时间:2011-01-04 01:31:43

标签: java parsing tree

我需要一些帮助,在给定算术表达式的情况下创建自定义树。比如说,你输入这个算术表达式:

(5+2)*7

结果树应如下所示:

    *
   / \
  +   7
 / \
5   2

我有一些自定义类来表示不同类型的节点,即PlusOp,LeafInt等。我不需要计算表达式,只需创建树,所以我可以在以后执行其他功能。 此外,否定运算符' - '只能有一个子节点,并且要表示'5-2',您必须将其输入为5 +( - 2)。

需要对表达式进行一些验证,以确保每种类型的运算符都具有正确的值。参数/儿童,每个开头括号都有一个右括号。

另外,我应该提一下,我的朋友已经编写了将输入字符串转换为一堆标记的代码,如果这对此有帮助的话。

我会感激任何帮助。谢谢:))

(我读过你可以写一个语法并使用antlr / JavaCC等来创建解析树,但我不熟悉这些工具或编写语法,所以如果这是你的解决方案,我会是如果您能为他们提供一些有用的教程/链接,我将不胜感激。)

5 个答案:

答案 0 :(得分:48)

假设这是某种功课,你想自己做..

我做了一次,你需要一个堆栈

所以你为这个例子所做的是:

    parse    what to do?                Stack looks like
      (      push it onto the stack     (
      5      push 5                     (, 5
      +      push +                     (, 5, +
      2      push 2                     (, 5, +, 2
      )      evaluate until (           7            
      *      push *                     7, *
      7      push 7                     +7, *, 7
      eof    evaluate until top         49

像“5”或“+”这样的符号可以存储为字符串或简单对象,或者您可以将+存储为+()对象而不设置值,并在评估时设置它们。

我认为这也需要一个优先顺序,所以我将描述它是如何工作的。

在:5 + 2 * 7

的情况下

你必须按下5推+推2下一个操作优先级更高,所以你也推它,然后推三个。当您遇到a)或文件结尾或优先级较低或相同的运算符时,您开始计算堆栈到前一个(或文件的开头。

因为您的堆栈现在包含5 + 2 * 7,所以当您评估它时,首先弹出2 * 7并将生成的*(2,7)节点推入堆栈,然后再一次评估前三个堆栈(5 + *节点)所以树出来是正确的。

如果是以另一种方式订购:5 * 2 + 7,你会推动直到你得到一个“5 * 2”的堆栈然后你会达到较低的优先级+这意味着评估你现在得到的。你将5 * 2评估成一个*节点并推送它,然后你继续推动+和3,这样你就得到了* node + 7,此时你就可以评估它了。

这意味着你有一个“当前最高优先级”变量,当你按下一个+/-时存储1,当你按*或/和一个3来表示“^”时存储2。这样你就可以测试变量,看看你的下一个运算符的优先级是否为< =你当前的优先权。

如果“)”被视为优先级4,您可以将其视为其他运算符,但它会删除匹配的“(”,较低的优先级不会。

答案 1 :(得分:12)

我想回答Bill K.的答案,但我缺乏在那里添加评论的声誉(这真的是这个答案所属的地方)。您可以将此视为Bill K.答案的附录,因为他有点不完整。缺少的考虑因素是operator associativity;即,如何解析像:

这样的表达式
49 / 7 / 7

取决于除法是左对齐还是右对应,答案是:

49 / (7 / 7) => 49 / 1 => 49

(49 / 7) / 7 => 7 / 7 => 1

通常,除法和减法被认为是左关联的(即上面的情况二),而取幂是右关联的。因此,当您遇到具有相同优先级的一系列运算符时,如果它们是关联的,则需要按顺序解析它们,如果是右关联的,则按顺序解析它们。这只是确定你是在推送还是弹出堆栈,所以它不会使给定的算法过于复杂,它只是为连续运算符具有相同优先级时添加的情况(即如果左关联则评估堆栈,如果是右关联则推送到堆栈)

答案 2 :(得分:9)

"Five minute introduction to ANTLR"包含算术语法示例。值得一试,特别是因为antlr是开源的(BSD许可证)。

答案 3 :(得分:2)

有几种选择:

  1. 重用现有的表达式解析器。如果您在语法和语义上很灵活,那将会奏效。我推荐的一个好的是Java内置的统一表达式语言(最初用于JSP和JSF文件)。

  2. 从头开始编写自己的解析器。有一种定义明确的方法来编写一个考虑运算符优先级等的解析器。详细描述完成的方式超出了本答案的范围。如果你走这条路,找一本关于编译器设计的好书。语言解析理论将在前几章中介绍。通常,表达式解析是其中一个示例。

  3. 使用JavaCC或ANTLR生成词法分析器和解析器。我更喜欢JavaCC,但他们更喜欢自己的JavaCC。只需google“javacc samples”或“antlr samples”。你会发现很多。

  4. 在2到3之间,即使您必须学习新技术,我强烈推荐3。有一个原因是已经创建了解析器生成器。

    另请注意,创建一个可以处理格式错误的输入的解析器(不仅仅是使用parse异常失败),编写一个只接受有效输入的解析器要复杂得多。你基本上必须编写一个语法来解释各种常见的语法错误。

    更新:这是我使用JavaCC编写的表达式语言解析器的示例。语法松散地基于统一表达式语言。它应该让你很好地了解你的反对意见。

    Contents of org.eclipse.sapphire/plugins/org.eclipse.sapphire.modeling/src/org/eclipse/sapphire/modeling/el/parser/internal/ExpressionLanguageParser.jj

答案 4 :(得分:1)

给定的表达式(5 + 2)* 7我们可以作为中缀

Infix  :     (5+2)*7
Prefix :     *+527
从上面我们知道树的预订和顺序...我们可以很容易地构建树。 谢谢,