编译原理学习笔记(二)左递归消除、递归下降

正则文法与上下文无关文法

文法规则是定义在一个字母表或符号集之上。在正则表达式中,这些符号通常就是字符,而在文法规则中,符号就是这些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'| A -> Aa|b 可以推导出来,Aa这些非终结符最终都会变成终结符;
A展开到最后肯定是b开头,可以领 A -> bA',然后A'是右递归。
    A' -> aA'| 
        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 
#include 
#include 

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;
}

你可能感兴趣的:(编译原理)