【Suatin】不学编译原理就制作语言2——Concrete Syntax Tree

前言

之前的文章
lang:谈谈自制编程语言
lang:C++自定义异常类——用来处理自制编程语言的异常信息
lang:计算器改进版本_默认函数_小数_指数
lang:计算器
lang:使用BNF范式设计一个文法
lang:总结9种编程语言的语法来设计自己的编程语言Suatin-lang

之前的项目重写了,第一次写的果然大部分都是辣鸡代码!!!没用到的暂时不要写,因为之前学了点Java,粘染了写辣鸡类的风气!!!第一个项目中,成功把Token分离了,本来下一步是分割简单的语句,然后由简单语句构造语法树,但是那样就把一些关键字给剔除了,只能给简单语句类添加一些关键字的标志,表示这句话有被这个关键字修饰!!!

实在进行不下去,休息了半个月,然后画了一周写了第二个项目!把加、减、乘、除、乘方、括号构成的算术式子给构造了抽象语法树!!!虽然功能还不及上次用后缀表达式写的计算器,但是还是基本搞懂了一些CreateASTree的方法!


文法

设计这样一个简单的文法G

1)E     ::=  E biop E
2)biop  ::=  +|-|*|/|^
3)E     ::=  "(" E ")"
4)E     ::=  Num|Identifier
5)E     ::=  (+|-) E 

假如给出一个式子 : 1 + ( 2 + 3),根据G可以做出下面的推导

E => E + E 
  => identifier + ( E )
  => identifier + ( E + E )
  => identifier + ( identifier + identifier )

这样的推导就是由上而下的,先把整体推导出不同的表达式部分,然后再由各种的表达式向下推导不同的部分或内容!!!但是,从遍历上来讲,我只想遍历一次,而迭代暂时也不太会写!!!所以像上面这样会多次遍历到一个Token的推导方法,我这次就不考虑了。
我的方案是从头到尾,一个一个Token来处理,每个Token都只处理一次!(不是只判断一次)

1 =>  +     =>    +     =>   +   =>   +        =>   +      =>   +
     / \         / \        / \      / \           / \         / \
    1   NULL    1   (      1   (    1   (         1   (       1   ()
                               |        |             |           |
                               1        +             +           +
                                       / \           / \         / \
                                      2   NULL      2   3       2   3

   \;

设计解释器的类

我这个项目用了解释器模式,简单来讲就是一颗类继承的树中,除基类外有两种类——一直是可以有子类,即有孩子节点的,称为非终结符——一种是不可以有子类,即不能有孩子节点的,称为终结符!
然后通过一个封装类,操控这颗类继承的树,来构造语法树!
在构造完成后,通过类继承的树中都有的interpret() 方法,返回最终语法树的结果!
在这里插入图片描述

设计构造语法树的指针

除了一些函数内临时的指针,其他必须的指针如下

  • root保存语法树的根节点,这里的语法树就是指总的语法树,不会保存其他的小树!
  • expTmp保存待处理的节点的父节点,比如上面推导中的None,本身就是空,所以我要保留的就是那个加号节点!——待处理的节点只能是加减乘除、乘方、左括号节点这几种!
  • expTmpLeft保存最底层的没有匹配到右括号的左括号节点,这个节点就叫做左括号节点!在遍历全局中缀表达式时,出现了左括号就要创建一个左括号节点,这个指针会保存最新的左括号节点,当出现一个右括号时,这个指针指的左括号节点就把标志matched_flag置为true
  • v_NoMatchedLLeft保存所有的未匹配的左括号节点,用于expTmpLeft指针的上移——每匹配到一个右括号,对应的左括号节点就被pop出这个容器了!!!(这个容器中的左括号节点,都是在一条链上的)

   \;    \;

Num|Identifier

1.如果语法树为空,直接创建一个Num/Identifeir节点(下面简写为N)

NULL -> N

2.如果语法树不为空,那么肯定有待处理的节点,expTmp肯定不为空
——1)如果待处理节点是左括号节点,就只有下面一种情况,直接把N接在此左括号节点下即可!
待处理节点还在这个左括号节点上!

(  ->  (
       |
       N

——2)如果待处理节点是加减乘除、乘方, 就社设置待处理节点的右孩子,为新创建的N。
待处理节点由op上移到expTmpLeft的位置——【表示下面的树完成了,可以考虑匹配右括号了,但是如果接下来还没匹配到右括号的话,还可以处理下面的子树。】

op = +|-|*|/|^
?=一棵子树
   op         ->    op
  /  \             /  \
 ?    NULL        ?    Num

   \;
   \;

加减

1.如果语法树为空时,此时出现的+、-可以当做正负号,创建一个N,左孩子默认0,右孩子为空。
待处理节点设置为N的位置。

op = +|-
NULL  ->   op
          /  \
         0    NULL

2.存在语法树,还存在待处理的节点
——1)待处理的节点是左括号节点。对左括号节点中的内容进行P1操作!
——2)待处理的节点是加减乘除、乘方、Num。对待处理节点指针expTmp进行P1操作
3.不存在待处理的节点。对root节点进行P1操作

P1操作
就是对已有的一棵树进行左结合操作!然后更新原有的根节点为op,
待处理节点指针放在op上!

op = +|-
_node = 某个树的根节点
 _node ->   op
           /   \
       _node    NULL

   \;
   \;

乘除

1.如果语法树为空,这时出现一个*、/ 肯定有问题,要抛出异常。
2.如果存在语法树,存在待处理节点
——1)待处理的节点是左括号节点。对左括号节点中的内容进行P2操作!
——2)待处理的节点是加减乘除、乘方、Num。对待处理节点指针expTmp进行P2操作
3.不存在待处理的节点。对root节点进行P2操作

P2操作
——1)当小树的根节点是乘除、乘方、Num类型时,进行左结合操作,
待处理节点指针指向op!
——2)当小树的根节点是加减类型时,进行右结合操作,
待处理节点指针指向op!

op = *|/
op1 = *|/|^|N
op2 = +|-
? = 一棵子树 
第一种情况(左结合):       op1     ->    op
                         /  \         /  \
                        ?    ?      op1   NULL
                                    / \
                                   ?   ?
                                   
第二种情况(右结合):      op2     ->     op2
                        /  \           /  \
                       ?    ?         ?    op
                                          /  \
                                         ?    NULL

   \;    \;

乘方

1.如果语法树为空,这时出现一个 ^ 肯定有问题,要抛出异常。
2.如果存在语法树,存在待处理节点
——1)待处理的节点是左括号节点。对左括号节点中的内容进行P3操作!
——2)待处理的节点是加减乘除、乘方、Num。对待处理节点指针expTmp进行P3操作
3.不存在待处理的节点。对root节点进行P3操作

P3操作
——1)如果小树的根节点是左括号或N ,那么就进行左结合操作!
待处理节点指针指向op!
——2)如果小树的根节点是其他类型的,那么判断一下小树的根节点的右孩子,如果不是N类型的,就判断小树的根节点的右孩子的右孩子,,,,直到找到一个节点,其右孩子是N类型!然后进行右结合操作!
待处理节点指针指向op!

op = ^
op1 = (|N
op2 = +|-|*|/|^
? = 一棵子树 
第一种情况(左结合):       op1     ->    op
                         /  \         /   \
                        ?    ?      op1    NULL
                                    / \
                                   ?   ?

第二种情况(右结合):        op2  ->    op2     ....->   op2       ->      op2
                          /  \      /  \             /  \              /  \
                         ?    ?    ?    op2         ?   op2           ?   op2
                                        /  \            / \               / \ 
                                       ?    ?          ?   .			 ?   .
															.                 .
															 op2              op2
															/  \              / \
														   ?    N            ?   op
														                        / \
																			   N   NULL

   \;    \;

左括号

1.如果语法树为空,就创建一个左括号节点。expTmpLeft指针指向这个节点,并把这个指针push进v_NoMatchedLLeft容器中!

NULL  ->  (

2.如果有待处理的节点
——1)待处理节点是左括号节点,用expTmpLeft创建新的左括号节点(记为L),然后设置待处理的节点的内容为L。

(    ->    (
           |
           (

——2)待处理节点是加减乘除、乘方,用expTmpLeft创建L,然后设置待处理的节点的右孩子为expTmpLeft

op = +|-|*|/|^
? = 一棵小树
   op     ->     op
  /  \          /  \      
 ?    NULL     ?    (

1)2)操作完了都要把expTmpLeft放入v_NoMatchedLLeft中,让待处理的节点的指针expTmp指向expTmpLeft

3.没有待处理的节点,抛出异常。

   \;    \;

右括号

1.如果语法树不存在,抛出异常。
2.没有待处理的节点,抛出异常。
3.有待处理的节点
——1)待处理节点不是左括号节点,抛出异常
——2)待处理节点是左括号节点,判断该左括号节点是否已经匹配到了,如果匹配到了就抛出异常,如果没有匹配到,就置位该左括号节点的标志为true匹配到!然后从容器中pop出该节点!!!!将expTmpLeft上移到容器中最后一个左括号节点,并且该左括号节点没被匹配到!!!如果找到这样的一个节点,将expTmp指向expTmpLeft,,,,如果找不到就都设置为空。

   \;    \;

项目演示

项目代码放在这了,有需要的自己取!

#include"Expr.h"
/*test.suatin中的内容为 1+(3-2*4)/1 - 2^(((2)+1))  */
void main() {
    
    
    sua::file_dir = "C:\\Users\\LX\\Desktop\\test.suatin";
  //词法分析
  SHOWFUNCTIME("词法分析",sua::Lexer);
  //显示全局中缀表达式
  SHOWFUNCTIME("全局中缀表达式", sua::ShowGlobalInfix);
  //语法分析
  sua::Parser p;
  SHOWFUNCTIME("语法分析·构造语法树",p.CreateASTree);
  //显示语法树
  SHOWFUNCTIME("语法分析·显示语法树",p.ShowASTree);  
  //std::cout << "语法树是否完全==>" << (p.GetCompletedASTreeFlag()==1?"true":"false") << std::endl;
  //显示解释结果
  SHOWFUNCTIME("语法分析·解释语法树",p.interpret);


  system("pause");  
}
词法分析 time consumed 204 ms
中缀表达式>
                name     pos    type
                  1       0       7
                  +       1      15
                  (       2      27
                  3       3       7
                  -       4      16
                  2       5       7
                  *       6      13
                  4       7       7
                  )       8      28
                  /       9      14
                  1      10       7
                  -      11      16
                  2      12       7
                  ^      13      12
                  (      14      27
                  (      15      27
                  (      16      27
                  2      17       7
                  )      18      28
                  +      19      15
                  1      20       7
                  )      21      28
                  )      22      28
全局中缀表达式 time consumed 497 ms
语法分析·构造语法树 time consumed 1 ms
------------------------------------------------------------
-
├── +
│   ├── 1
│   └── /
│       ├── ()
│       │   └── -
│       │       ├── 3
│       │       └── *
│       │           ├── 2
│       │           └── 4
│       └── 1
└── ^
    ├── 2
    └── ()
        └── ()
            └── +
                ├── ()
                │   └── 2
                └── 1
------------------------------------------------------------
语法分析·显示语法树 time consumed 646 ms
result = -12
语法分析·解释语法树 time consumed 12 ms
请按任意键继续. . .

参考:
怎么用C语言画出二叉树图形

项目代码地址CSDN
https://download.csdn.net/download/weixin_41374099/12178913

项目代码地址BDWP
链接:https://pan.baidu.com/s/14lXkWNohXHIWefuU9p_EJw
提取码:eiiy
复制这段内容后打开百度网盘手机App,操作更方便哦

猜你喜欢

转载自blog.csdn.net/weixin_41374099/article/details/104434650