递归下降语法分析器c语言输出推导过程,递归下降语法分析器实现过程

相比词法分析器,构造语法分析器的方法有很多,其中手写起来最简单的自然是递归下降的方法了。

文法

要实现一种语言的分析器,我们需要先写出这种语言的上下文无关文法(Context-free grammer),我选择语言的文法如下:

P -> S $

S -> S ; S

S -> id = EXP

S -> print ( EXPS )

EXPS -> EXP

EXPS -> EXPS , EXP

EXP -> int

EXP -> id

EXP -> EXP OP EXP

EXP -> ( EXP )

OP -> +

OP -> -

OP -> *

OP -> /

其中大写单词表示非终结符,小写字母和符号表示终结符。

处理文法

递归下降分析器的构造方法非常简单,简单来说,就是为每一个非终结符写一个递归函数,函数中对该终结符可能转换成的所有情况的第一个token进行判断,做出对应的处理,例如,S的函数形式如下(要注意的是这个函数是错的,只是为了表现形式,下面会说明):

void S(){

switch(current_token){

case S:

S(); eat(';'); S(); break;

case id:

eat(id); eat('='); EXP(); break;

case print:

eat(print); eat('('); EXPS(); eat(')'); break;

default:

error_exit();

}

}

看起来很简单吧?但是上面的函数是有问题的,函数只会执行case S的情况,并将无限的递归下去,这称为左递归。

这不是我们方法的问题,而是文法格式的问题,递归下降方法只能处理没有左递归和左因子的文法,我们需要消除它。

消除左递归

类似

E -> E + T

E -> T

的文法是左递归的,我们可以通过引入一个新的非终结符来消除它,结果如下:

E -> T E'

E' -> + T

E' ->

上面展示的并不是一种模板,而是一种思想,我们的文法中,在非终结符EXP,S中也出现了左递归的情况,但是要复杂得多,你需要理解消除左递归的思想来解决它们。

提取左因子

考虑文法中的EXPS,我们发现我们无法根据其转换的第一个符号来判断下一步该做什么。

当一个非终结符的不同转换的前几个符号是相同的符号,这种情况也会发生类似左递归的问题,我们需要通过提取左因子的方法消除它。

类似

S -> E + E

S -> E

的文法是有问题的,依然是引入一个新的非终结符:

S -> E S'

S' -> + E

S' ->

最终的文法

在消除了EXP 和 S的左递归,提取了EXPS的左因子之后,文法如下:

P -> S $

S -> id = EXP S'

S -> print ( EXP S ) S'

S' -> ; S

S' ->

EXPS -> EXP EXPS'

EXPS' -> , EXPS

EXPS' ->

EXP -> ( EXP )

EXP -> int EXP'

EXP -> id EXP'

EXP' -> OP EXP

EXP' ->

OP -> +

OP -> -

OP -> *

OP -> /

构建分析器

现在让我们从语言到结果,构造我们的代码。

词法分析(myrdp.l)

首先,我们需要把字符串形式的语言转换成token串,这一步我们需要词法分析器,我使用flex完成这一步的工作,在分析过程中存储每个token的类型和值,存储类型的具体定义见下面的 myrdp.h 文件代码,代码如下:

%{

#include "myrdp.h"

#include

#include

#include

using namespace myrdp;

%}

%%

; tokens.push_back(Token(SEMICOLON));

= tokens.push_back(Token(ASSIGN));

\( tokens.push_back(Token(LPAREN));

\) tokens.push_back(Token(RPAREN));

, tokens.push_back(Token(COMMA));

\+ tokens.push_back(Token(PLUS));

- tokens.push_back(Token(MINUS));

\* tokens.push_back(Token(TIMES));

\/ tokens.push_back(Token(DIV));

print tokens.push_back(Token(PRINT));

[0-9]+ tokens.push_back(Token(INT,atoi(yytext)));

[a-zA-Z]+[a-zA-Z0-9]* {

tokens.push_back(Token(ID,std::string(yytext)));

}

[ \t\n] ;

. std::cout<

%%

int yywrap(){

return 1;

}

语法分析(myrdp.h myrdp.cpp)

现在我们得到了一个存储了token的vector,我们只要按照上面所说的构建方法,从头到尾以递归的方式遍历一遍vector就可以得到结果,代码很简单,直接看代码吧。

程序的入口结构,即写在main函数里的部分

yyin = fopen(argv[1],"r");

yylex();

prog();

关键结构的定义和函数声明见myrdp.h

myrdp.h

#pragma once

#include

#include

#include

namespace myrdp{

/*token 类型*/

enum Type{

SEMICOLON=1, ASSIGN, PRINT,

LPAREN, RPAREN, COMMA,

INT, ID, PLUS,

MINUS, TIMES, DIV

};

/*token值存储*/

struct Value{

int num;

std::string id;

Value() = default;

Value(int _n):num(_n){}

Value(const std::string& _i):id(_i){}

~Value(){}

};

/*token结构*/

struct Token{

Type type;

Value value;

Token(Type _t):type(_t),value(0){}

Token(Type _t,int _v):type(_t),value(_v){}

Token(Type _t,const std::string& _i):type(_t),value(_i){}

};

extern std::vector tokens;

extern std::map<:string> table;

extern int cur;

extern std::vector<:string> TypeToString;

void prog();

void stm();

void stm_prime();

void exps();

void exps_prime(int);

int exp();

int exp_prime(int);

}

具体函数和变量的定义

myrdp.cpp

#include

#include

#include

#include

#include "myrdp.h"

namespace myrdp{

std::vector tokens;

std::map<:string> table;

int cur = 0;

std::vector<:string> TypeToString = {

"0","SEMICOLON", "ASSIGN", "PRINT",

"LPAREN", "RPAREN", "COMMA",

"INT", "ID", "PLUS",

"MINUS", "TIMES", "DIV"

};

//prog,stm,exps,exp

void err_exit(){

std::cout<

std::cout<

exit(EXIT_FAILURE);

}

void eat(Type t){

if(t==myrdp::tokens[myrdp::cur].type){

myrdp::cur++;

}else{

err_exit();

}

}

void prog(){

stm();

}

void stm(){

const Token &tok = myrdp::tokens[myrdp::cur];

switch(tok.type){

case ID:

myrdp::cur++;

eat(ASSIGN);

myrdp::table[tok.value.id]=exp();

stm_prime();

break;

case PRINT:

eat(PRINT);

eat(LPAREN);

exps();

eat(RPAREN);

stm_prime();

break;

default:

err_exit();

}

}

void stm_prime(){

const Token &tok = myrdp::tokens[myrdp::cur];

switch(tok.type){

case SEMICOLON:

eat(SEMICOLON);

stm();

break;

default:

break;

}

}

void exps(){

exps_prime(exp());

}

void exps_prime(int a){

const Token &tok = myrdp::tokens[myrdp::cur];

switch(tok.type){

case COMMA:

eat(COMMA);

exps_prime(exp());

break;

default:

std::cout<

break;

}

}

int exp(){

const Token &tok = myrdp::tokens[myrdp::cur];

int a;

switch(tok.type){

case LPAREN:

eat(LPAREN);

a = exp();

eat(RPAREN);

return a;

case ID:

a = myrdp::table[tok.value.id];myrdp::cur++;

return exp_prime(a);

case INT:

a = tok.value.num;cur++;

return exp_prime(a);

default:

err_exit();

break;

}

//cannot reach here

return 0;

}

int exp_prime(int a){

const Token &tok = myrdp::tokens[myrdp::cur];

switch(tok.type){

case PLUS:

eat(PLUS);

return a+exp();

case MINUS:

eat(MINUS);

return a-exp();

case TIMES:

eat(TIMES);

return a*exp();

case DIV:

eat(DIV);

return a/exp();

default:

return a;

}

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值