正则文法与上下文无关文法
文法规则是定义在一个字母表或符号集之上。在正则表达式中,这些符号通常就是字符,而在文法规则中,符号就是这些token。基本正则表达式规则有3种运算: choice(由竖线元字符表示), concatenation(不带元符号), repeat(由星号元符号提供).上下文无关文法相对于正则文法最大的区别就是:没有定义重复运算但是涉及了递归的文法规则,有递归自然容易得到重复。
左递归消除
递归下降要求不能有左递归,那样就陷入无穷递归了。通过整数计算的表达式文法可以看出来。
一个简单的例子,只有加法的计算表达式,其规则可以表示成 exp -> exp + num|num
, 符合
exp -> exp + num|num
A -> Aa|b (a : + num)
A = b
Aa -> ba
Aa -> Aaa -> baa
...
A -> ba^n
num (+ num) (+ num) ...
有点类似数列的思想,不过其实文法的递归,匹配了特性模式一直递归下去,直到最终的终结符。消除左递归的方法也很简单:
拆成两个规则,引入一个新规则:
A -> Aa|b
A -> bA'
A' -> aA'| <empty>
从 A -> Aa|b 可以推导出来,Aa这些非终结符最终都会变成终结符;
A展开到最后肯定是b开头,可以领 A -> bA',然后A'是右递归。
A' -> aA'| <empty>
A' -> aaA'
A' -> aaa
A' -> a^n
A -> bA'
A -> ba^n
关键点就是,引入新的中间规则,可以把左递归变右递归,右递归每次归约,规模会缩写,不会死循环。
具体可以看:编译原理与实践。
递归下降计算器
/* Grammar rules
exp -> term exp_tail
exp_tail -> addop term exp_tail | empty
addpp -> + | -
term -> factor term_tail
term_tail -> mulop factor term_tail | empty
mulop -> * | /
factor -> ( exp ) | num
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int exp();
int exp_tail(int lvalue);
int term();
int term_tail(int lvalue);
int factor();
void next();
void match(char token);
enum E_TOKEN
{
E_NUM = 0,
};
const char *g_src;
char g_token; // + - * / ( ) E_NUM
int g_value; // if g_token is E_NUM, g_value is its value
int exp()
{
int lvalue = term();
return exp_tail(lvalue);
}
int exp_tail(int lvalue)
{
int value = 0;
switch (g_token) {
case '+':
match('+');
value = lvalue + term();
return exp_tail(value);
case '-':
match('-');
value = lvalue - term();
return exp_tail(value);
default:
return lvalue;
}
}
int term()
{
int lvalue = factor();
return term_tail(lvalue);
}
int term_tail(int lvalue)
{
int value = 0, tmp = 0;
switch (g_token) {
case '*':
match('*');
value = lvalue * factor();
return term_tail(value);
case '/':
match('/');
tmp = factor();
if (tmp != 0) {
value = lvalue / tmp;
} else {
printf("error divided by 0\n");
exit(-1);
}
return term_tail(value);
default:
return lvalue;
}
}
int factor()
{
int value = 0;
if (g_token == '(') {
match('(');
value = exp();
match(')');
} else {
value = g_value;
match(E_NUM);
}
return value;
}
void match(char token)
{
if (token != g_token) {
if (token == E_NUM) {
printf("expect number, real is %c(ascii %u)\n", g_token, g_token);
} else {
printf("expcet %c, real is %c(%u)\n", token, g_token, g_token);
}
exit(-1);
}
next();
}
void next()
{
while (*g_src == ' ')
++g_src;
if (*g_src == '+'
|| *g_src == '-'
|| *g_src == '*'
|| *g_src == '/'
|| *g_src == '('
|| *g_src == ')') {
g_token = *g_src++;
} else if (isdigit(*g_src)) {
g_value = 0;
while(isdigit(*g_src)) {
g_value = g_value * 10 + (*g_src) - '0';
++g_src;
}
g_token = (char)E_NUM;
} else if (*g_src == '\0') {
return; // expression end
} else {
printf("input error: %c", *g_src);
exit(-1);
}
}
int main()
{
const char *test = "1+2+4* 16/ (3-1)";
int result = 0;`
g_src = test;
next();
result = exp();
printf("%s = %d\n", test, result);
return 0;
}