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

文法

要实现一种语言的分析器,我们需要先写出这种语言的上下文无关文法(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;

}

}

}

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

  1. python中的递归思想_使用python语言表达分形与递归

    Fibonacci数列 Fibonacci数列是一个很有趣的结构,每后一项都等于前两项之和.它的前几位如下:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 134.. ...

  2. C语言反转二叉树的递归和迭代解决方案(附完整源码)

    C语言反转二叉树的递归和迭代解决方案 C语言反转二叉树的递归和迭代解决方案完整源码(定义,实现,main函数测试) C语言反转二叉树的递归和迭代解决方案完整源码(定义,实现,main函数测试) #in ...

  3. ll1语法分析器c语言E-E T,算术表达式的 LL(1)语法分析器

    算术表达式的 LL(1)语法分析器 张会霞 (辽宁师范大学 计算机与信息技术学院,辽宁 大连 116000) 摘要:语法分析是编译程序的核心部分,对其进行研究有着重要意义.本文介绍了编译过程语法分析阶 ...

  4. lr1分析器c语言实验报告怎么写,编译原理课程的设计构造LR分析法语法分析器.doc...

    编译原理课程的设计构造LR分析法语法分析器 太 原 学 院 课程设计报告书 课程名称 设计题目 构造LR(0)分析法语法分析器 专业班级 学 号 姓 名 指导教师 2016年 12 月 15日 目 录 ...

  5. lr 1 语法分析器c语言,LR语法分析器

    1 LR语法分析器 本节介绍一个有效的自底向上的分析技术,可以用于一大类上下文无关文法的语法分析.这种技术叫做LR(k)分析法,其中L表示从左到右扫描输入串,R表示构造一个最右推导的逆过程,k指的是在 ...

  6. C语言零基础入门级 递归+回调+字符串函数+面试题全讲解【系统学习第六天】

    C语言零基础入门级 函数大全+面试题全讲解 [1]C语言->函数入门 [2]C语言->函数的定义 [3]C语言-> 函数实参与形参 [4]C语言->函数调用的流程 [5]C语言 ...

  7. [GO语言基础] 二.编译运行、语法规范、注释转义及API标准库知识普及

    作为网络安全初学者,会遇到采用Go语言开发的恶意样本.因此从今天开始从零讲解Golang编程语言,一方面是督促自己不断前行且学习新知识:另一方面是分享与读者,希望大家一起进步.前文介绍了什么是GO语言 ...

  8. python 递归 写平方_Python算法:推导、递归和规约

    注:本节中我给定下面三个重要词汇的中文翻译分别是:Induction(推导).Recursion(递归)和Reduction(规约) 本节主要介绍算法设计的三个核心知识:Induction(推导).R ...

  9. c++ 14新的语法特性之返回类型推导

    c++ 14新的语法特性之返回类型推导 一.返回类型推导 返回类型推导 即编译器会在运行期间,推导出该函数方法的返回类型,不过应该也会存在问题,比如返回类型不同的时候,所以这个特性 还是谨慎使用比较好 ...

  10. C语言丨函数的递归调用和递归函数

    目录 前言 一.从阶乘引入 二.递归模板 1.递归函数模板 2.举例分析 三.从数学归纳法理解递归 四.更多递归实例 1.用递归方法编程计算Fibonacci数列 题目分析 程序 2.汉诺塔(Hano ...

最新文章

  1. linux下基于apache下的svn的搭建
  2. html 连续发送数据库,不要在循环中对数据库进行操作.htm
  3. 固定资产管理有关的计算机知识,计算机技术在固定资产管理中的具体应用.pdf...
  4. 客户端javascript对象的几何属性(获得大小及坐标)
  5. dhcp 服务器发出了 dhcpnack 消息,跪求DHCP报文分析
  6. 注解形式控制器配置(3)
  7. 危险的转变:Python正在从简明转向臃肿,从实用转向媚俗
  8. 菜鸟的IT道路ing (一)
  9. 面试了一个 35+ 岁的大佬,一言难尽......
  10. java分布式面试题_2021,Java最全的分布式面试题合集附答案,共2w字!
  11. acer软件保护卡怎么解除_Acer软件保护卡下载
  12. 《FLUENT 14流场分析自学手册》——2.4 FLUENT14.5的功能模块和分析过程
  13. 三菱FX3UFX2NFX1N PLC 模拟器模拟通信功能,模拟PLC实体
  14. 【网络工程】2、eNSP工具下载与安装
  15. React Native 版本的商米打印 Demo
  16. 力扣刷题 DAY_80 贪心
  17. 史上最详细的AVL树(含代码实现)
  18. 每日英语:China Destroys Six Tons of Confiscated Ivory
  19. 1688图片搜索淘宝商品接口
  20. Verilog 总结

热门文章

  1. libaio介绍和使用
  2. vue使用a标签下载文件
  3. 重置IE浏览器的操作
  4. STorM32三轴云台控制器PID参数调节(1)
  5. 视频教程-Mikrotik RouterOS 软路由基础教程-网络技术
  6. Python+OpenCV摄像头人脸识别
  7. 2022最全毕设题目选题
  8. shell编程实战总结
  9. HTML中背景图片如何自适应屏幕
  10. 复旦大学计算机学院邱锡鹏,复旦大学邱锡鹏教授为我院师生做学术报告