学习数据结构的时候学到栈的时候都会学习使用栈来实现求解中缀表达式,思路就是扫描一遍,数字输出,符号根据优先级决定入栈和出栈的时间,然后生成后缀表达式,对后缀表达式的求解是容易的,直接扫描一遍,遇到数字入栈,遇到操作符就直接取栈顶的两个元素做运算。
好久之前实现上面所说的程序是第一个感受“编译”的感觉,感觉很爽,因此一直想学一下真正的编译原理,龙书比较深奥,一直未得其奥义。这两天阅读了vczh大大的系列文章 《手写语法分析器》、《正则表达式引擎》等文章,颇有感触,遂有此文。
表达式比定义语言要容易的多,因为它可以把词法分析和语法分析很容易的结合起来,因为表达式中除了数字其它的符号均是单字符,直接判断就可以知道token类型,比较简单。
递归下降的核心在于写出无左递归的语法,写出来之后就直接写个程序去解释这个正则表达式就可以容易的搞定。源码点击这里
下面简单介绍一下前缀或者后缀表达式的递归下降分析:
对于前缀,scheme中的表达式都采取这样的形式,这样的形式就无需词法分析了,没有优先级的歧义。
语法定义如下:
expr="(" op number number ")" | "(" expr ")" |number number= [0-9]* op=‘+‘ | ‘-‘ | ‘* | ‘/‘我们之间对每个部分进行翻译即可;
比如对于numbergetnumber函数应该这么写:expr GetNumber(char* &stream) { expr number; //存储结果,为一个expr类对象 char* lookup = stream; while ((*lookup) == ‘ ‘) lookup++; bool flag = false; while (true) { if ((*lookup )<= ‘9‘ && (*lookup) >= ‘0‘) { number.result = number.result * 10 + (*lookup) - ‘0‘; ++lookup; flag = true; } else break; } if (flag) //获得当前开头的数字,并将当前指针所指调整到数字后面一位 stream = lookup; else //当前开头开始的不是数字,当前指针不变 { number.error = "there need a number!\n"; number.error_position = lookup; } return number; }
有了getnumber之后getexpression就很容易了,核心部分如下:
expr expression; char *lookup = stream; expression = GetNumber(lookup); if (expression.error) { if (Is(lookup, "(")) { expression.error = 0; char op = 0; if (op=IsOperator(lookup)) { expr left_expr = Getexpression(lookup); if (left_expr.error) return left_expr; char *copy_lookup = lookup; expr right_expr = Getexpression(lookup); if (right_expr.error ) return right_expr; switch (op) { case ‘+‘: expression.result = left_expr.result + right_expr.result; break; case ‘-‘: expression.result = left_expr.result - right_expr.result; break; case ‘*‘: expression.result = left_expr.result * right_expr.result; break; case ‘/‘: { if (right_expr.result == 0) { expression.error = "the number divided can not be zero!\n"; expression.error_position = copy_lookup; break; } else { expression.result = left_expr.result / right_expr.result; break; } } default: { expression.error = "the wrong operator!\n"; expression.error_position = lookup; return expression; } } } else { expression= Getexpression(lookup); if (expression.error != 0) return expression; } if (!Is(lookup, ")")) { expression.error = "there need right bracket!\n"; expression.error_position = lookup; } } } if(expression.error==0) //成功获得了数字,因此将stream指针置于数字后面 { stream = lookup; } return expression;中缀的代码就得自己写啦,我也是参考vczh大大的前缀代码写了一份前缀之后,自己写了一份中缀,我们很容易从中得到启示,找到递归下降的核心所在,据此可以写出中缀表达式的求解(中缀的求解代码完全是我自己写的,错误处理部分可能存在一些漏洞),相信根据上文自己写一个中缀一定有很多收获。
时间: 2024-11-12 20:19:41