前言
之前的文章
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,操作更方便哦