zoukankan      html  css  js  c++  java
  • 仿查询分析器的C#计算器——4.语法分析

    相关文章:1.初步分析  2.记号对象  3.词法分析
    上一篇中介绍通过词法分析将表达式转换成TokenRecord对象列表。在第一篇中提到将表达式用树形结构表示,然后就可以很方便的从下级节点取值计算了。那么如何将列表分析成一棵树的结构呢?
    还是以例子来说明,比如3*7+56/8-2*5,分析成TokenRecord列表就是

    记号对象

    对应表达式

    TokenValue

    3

    TokenMultiply

    *

    TokenValue

    7

    TokenPlus

    +

    TokenValue

    56

    TokenDivide

    /

    TokenValue

    8

    TokenMinus

    -

    TokenValue

    2

    TokenMultiply

    *

    TokenValue

    5

    分析成树就是

    根据实际的算术规则,运算符优先级高的要先计算,然后由低优先级的运算符去调用它的运算结果。表现在树视图上就是高优先级的节点是低优先级节点的下级,即优先级越高,其位置越靠近树叶。因为这里采用统一的对象,把所有元素都用TokenRecord表示,所以TokenValue也是有优先级的。而通过对树视图的分析,所有的TokenValue都是处在叶子的位置,则TokenValue的优先级最高。

    分析到这里就要用代码实现了。这里需要用到TokenRecord中的优先级Priority属性,还要用到堆栈。和词法分析一样,也是需要用循环依次分析各个TokenRecord。拿上面的TokenRecord列表进行分析,粗体字代表当前分析的TokenRecord。分析的过程中有一个原则叫“高出低入原则”,需要解释一下。
    “高出低入原则”是指:
    1.栈顶TokenRecord的优先级高于当前TokenRecord的优先级,则将栈顶TokenRecord弹栈(高出)到临时变量。
    1.1如果堆栈为空,将临时变量中的TokenRecord加入当前TokenRecord的ChildList中,然后将当前TokenRecord压栈(低入)。
    1.2如果堆栈不为空,找出栈顶TokenRecord和当前TokenRecord中优先级高的一个(相同则按栈顶高算),将临时变量中的TokenRecord加入高优先级TokenRecord的ChildList中。再用高出低入原则处理栈顶和当前TokenRecord。
    2.栈顶TokenRecord的优先级低于当前TokenRecord的优先级,则将当前TokenRecord直接压栈。
    可能文字表述的并不清晰,其中涉及到循环和递归的操作,具体的过程通过下面的例子来讲解。

    1.列表分析状态:

    TokenValue(3)

    TokenMultiplay

    TokenValue(7)

    …...

    堆栈分析:当前堆栈为空,将当前分析的TokenRecord压栈。

    TokenValue(3)

    栈底

    堆栈对应树视图:


    2.列表分析状态:

    TokenValue(3)

    TokenMultiply

    TokenValue(7)

    …...

    堆栈分析:栈顶为TokenValue,当前TokenRecord为TokenMultiply,TokenValue优先级最高。遵循高出低入原则,将TokenValue弹栈并添加到TokenMultiply的ChildList中,然后将TokenMultiplay压栈。

    TokenMultiply

    栈底

    堆栈对应树视图:


    3.列表分析状态

    …...

    TokenMultiply

    TokenValue(7)

    TokenPlus

    …...

    堆栈分析:栈顶为TokenMultiplay,当前TokenRecord为TokenValue,TokenMultiply优先级高于TokenValue,则将TokenValue加入TokenMultiplay的ChildList中。

    TokenMultiply

    栈底

    堆栈对应树视图:


    4.列表分析状态

    ……

    TokenValue(7)

    TokenPlus

    TokenValue(56)

    …...

    堆栈分析:栈顶为TokenMultiply,当前TokenRecord为TokenPlus,TokenMultiply优先级高于TokenPlus。遵循高出低入原则,将TokenMultiply弹栈并添加到TokenPlus的ChildList中,然后将TokenPlus压栈。

    TokenPlus

    栈底

    堆栈对应树视图:


    5.列表分析状态

    ……

    TokenPlus

    TokenValue(56)

    TokenDivide

    …...

    堆栈分析:栈顶为TokenPlus,当前TokenRecord为TokenValue,TokenPlus优先级低于TokenValue。遵循高出低入原则,不需要弹栈,直接将TokenValue压栈。

    TokenValue(56)

    TokenPlus

    栈底

    堆栈对应树视图:


    6.列表分析状态 

    ……

    TokenValue(56)

    TokenDivide

    TokenValue(8)

    …...

    堆栈分析:栈顶为TokenValue,当前TokenRecord为TokenDivide,TokenValue优先级高于TokenDivide。遵循高出低入原则,将TokenValue弹栈并加入TokenDivide的ChildList中。此时栈顶为TokenPlus,TokenDivide优先级高于TokenPlus,遵循高出低入原则,将TokenDivide压栈。 

    TokenDivide

    TokenPlus

    栈底

    堆栈对应树视图:


    7.列表分析状态 

    ……

    TokenDivide

    TokenValue(8)

    TokenMinus

    …...

    堆栈分析:栈顶为TokenDivide,当前TokenRecord为TokenValue,TokenDivide优先级高于TokenValue。遵循高出低入原则,将TokenValue加入TokenDivide的ChildList中。 

    TokenDivide

    TokenPlus

    栈底

    堆栈对应树视图:


    8列表分析状态 

    ……

    TokenValue(8)

    TokenMinus

    TokenValue(2)

    ……

    堆栈分析:栈顶为TokenDivide,当前TokenRecord为TokenMinus,TokenDivde优先级高于TokenMinus。遵循高出低入原则,将TokenDivide弹栈到临时变量。检测到堆栈不为空,此时栈顶为TokenPlus,TokenPlus优先级和TokenMinus一样。这里相同优先级的按高优先级处理,遵循高出低入原则,则将临时变量中的TokenDivide加入高优先级TokenPlus的ChildList中。继续用高出低入原则,将TokenPlus弹栈并加入TokenMinus的ChildList中,再将TokenMinus压栈。 

    TokenMinus

    栈底

    堆栈对应树视图:


    9.列表分析状态 

    ……

    TokenMinus

    TokenValue(2)

    TokenMultiply

    …...

    堆栈分析:栈顶是TokenMinus,当前TokenRecord是TokenValue,TokenMinus优先级低于TokenValue。遵循高出低入原则,将TokenValue压栈。

    TokenValue(2)

    TokenMinus

    栈底

    堆栈对应树视图:


    10.列表分析状态 

    ……

    TokenValue(2)

    TokenMultiply

    TokenValue(5)

    堆栈分析:栈顶是TokenValue,当前TokenRecord是TokenMultiply,TokenValue优先级高于TokenMultiply。遵循高出低入原则,将TokenValue弹栈到临时变量,检测堆栈不为空,此时栈顶为TokenMinus,TokenMinus优先级低于TokenMultiply,则将临时变量中的TokenValue加入TokenMultiplay的ChildList中。遵循高出低入原则,将TokenMultiplay加入到栈顶TokenMinus的ChildList中。 

    TokenMultiply

    TokenMinus

    栈底

    堆栈对应树视图:


    11.列表分析状态

    ……

    TokenValue(2)

    TokenMultiply

    TokenValue(5)

    堆栈分析:栈顶是TokenMultiply,当前是TokenValue,TokenMultiply优先级高于TokenValue。遵循高出低入原则,将TokenValue加入TokenMultiply的ChildList中。

    TokenMultiply

    TokenMinus

    栈底

    堆栈对应树视图:


    12.堆栈分析:此时列表分析结束,堆栈不为空,需要对堆栈进行处理。经过上面的堆栈分析,遵循高出低入原则,堆栈中的TokenRecord肯定是栈底优先级最低,栈顶优先级最高。只需要将堆栈中的TokenRecord依次弹栈,然后加入到新栈顶的ChildList中即可。最后弹栈的一个TokenRecord就是整个树视图的根节点,也就是返回值。到此,堆栈为空,也得到了预期的树视图,返回根节点TokenRecord即可。

    上面是对语法分析整个流程的分析,下面给出具体的代码。程序中的语法分析放在一个单独的文件中,SyntaxTreeAnalyse.cs,文件中也只有一个SyntaxTreeAnalyse类,专门用于语法树的分析。这个类只有一个入口即一个公开方法SyntaxTreeGetTopTokenAnalyse,传入TokenRecord列表,经过分析后返回一个顶级TokenRecord对象,即树形结构的根节点。具体代码如下:
    Code

    代码中对括号进行了特殊处理,遇到左括号之后需要查找到配对的右括号,然后对这一段调用SyntaxTreeGetTopTokenAnalyse进行递归处理,找出括号中的顶级节点。而对于方法也需要特殊处理,方法必须带括号,而且括号中不止一个根节点。处理过程是以逗号为分隔符,依次找出括号中的几个节点,然后添加到方法的ChildList中。实际的代码在SyntaxTreeMethodAnalyse方法中实现。

    这一篇写的我自己都头晕了,呵呵,堆栈一个个分析,截图都挺烦。代码里方法互相递归调用,也是比较让人容易头晕的,希望有人能看懂吧。
    代码下载:https://files.cnblogs.com/conexpress/ConExpress_MyCalculator.rar
  • 相关阅读:
    SpringBoot之Banner介绍
    SpringBoot事件监听机制
    SpringBoot 启动流程图
    ApplicationContextInitializer的理解和使用
    SpringFactoriesLoader解析
    计时器之StopWatch
    ftp上下载文件
    打印两个函数的返回值
    关闭所有已打开的文件和关闭应用
    TypeError: include() got an unexpected keyword argument 'app_name'
  • 原文地址:https://www.cnblogs.com/conexpress/p/MyCalculator_04.html
Copyright © 2011-2022 走看看