语法分析之 LL1分析法实现

一、设计目的

根据某一文法编制调试LL(1)分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对预测分析LL(1)分析法的理解。

二、设计要求

程序输入/输出示例:

对下列文法,用LL(1)分析法对任意输入的符号串进行分析:

原文法:

E->E+T|E-T|T

T->T*F|T/F|F

F->id|(E)|num

其中: id: a-f, A-F,num:0-9

消左递归:

E->TA A->+TA A->-TA A->e

T->FB B->*FB B->/FB B->e

F->i F->(E) F->n

其中:i:id, n:num, e:epsilonE->TG

FIRST集和FOLLOW集:

TA +TA -TA e FB *FB /FB e i (E) n
FIRST i,(,n + - e i,(,n * / e i ( n
E A T B F
FOLLOW $,) | $,) +,-,\(,) | +,-,\),) *,/,+,-,$,)

输出的格式如下:

(1)输入一以#结束的符号串(包括+—*/()i#):

(2)输出过程如下:

输入 输出
\(E | (a-1)*(3+4/2)+((8*2))\) E->TA

(3)输入符号串为非法符号串(或者为合法符号串)

注意:

1.表达式中允许使用运算符(+-*/)、分割符(括号)、字符i,结束符#;

2.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好);

3.测试用的表达式可以事先放在文本文件中,一行存放一个表达式,同时以分号分割。同时将预期的输出结果写在另一个文本文件中,以便和输出进行对照;

三、设计说明

1. 需求分析:

  a****) 输入及其范围

​ 输入为文法,表达式中允许使用运算符(+-*/)、分割符(括号)、字符a。

b****) 输出形式

输入 输出
\(E | (a-1)*(3+4/2)+((8*2))\) E->TA

c) 程序功能

根据输入的文法进行分析,利用LL(1)控制程序根据显示栈栈顶内容、向前看符号以及LL(1)分析表,对输入符号串自上而下的分析过程

d) 测试数据

​ 输入:文件“fin.txt”输入待分析串

输出:命令行界面输出预测分析表,LL(1)分析过程输出至“fout.txt”

2. 概要设计

a****)数据类型的定义

​ vector<vector> table(5,vector(9)) //预测分析表

vector G //消除左递归后的文法产生式

map<char, int> index //文法符号到下标的转换字典

string terminal("in+-*/()$") //终结符

string nonTerminal("EATBF") //非终结符

vector First // 产生式右部符号串的first集

vector Follow //非终结符的follow集

b****)主程序流程

![img](file:///C:/Users/CARRAW~1/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)

3. 详细设计

\1. int main()

{

for(文法G每个产生式itG,itFirst为其右部符号串的first集){

​ x = itG左部非终结符号的下标;

​ for(itFirst中的每个终结符号first){

​ y = 终结符号first的下标;

​ 把itG加入分析表表G[x][y];

}

if(终结符号first == epsilon)

for(Follow集中的每个符号follow){

​ y = follow的下标;

​ 把itG加入分析表G[x][y];

}

​ }

​ for(所有非终结符号的Follow集)

if(对应表项为空)

写入synch;

​ 将分析表输出到命令行界面;

return analysis();

}

\2. int analysis(void)

{

从文件fin.txt读取待分析串到s;

s末尾加‘$’;

​ 分析栈vector analyStack;

​ 向栈压入‘$’、‘E’;

​ ip指向s的第一个字符;

​ do{

​ top是栈顶符号;

​ cur是ip所指向的输入符号;

​ if(cur是字母) cur = ‘i’;

​ if(cur是数字) cur = ‘n’;

​ if(top是终结符号或‘$’){

​ if(top == cur){从栈顶弹出cur;ip前移一个位置;}

​ else error;

​ }

​ else{

​ x = top对应下标; y = cur对应下标;

​ 产生式production = table[x][y];

​ if(production非空){

​ 栈顶弹出cur;

​ 把production右部逆序压栈;

​ 输出production;

​ }

​ else error;

​ }

​ while(top != ‘$’);

}

四、运行结果及分析

1.测试数据

fin.txt文件入字符串:(a-1)(3+4/2)+((82))

2.测试输出的结果

)

输出文件:

3.设计和思考

主要的难点在于对LL(1)的理解部分,消除二义性、消除左递归、提取左因子,判断是否为LL(1)文法,然后开始整理思路进行编码阶段。开始要对错误的文法进行分析,并提示详细的错误信息。思考之后实现了表达式中允许使用运算符(+-*/)、分割符(括号)、字符a。

五、总结

本次课程设计是本周实验来难点最大的一次作业,首先需要温习LL(1)的知识,如何消除左递归,区别二义性文法,以及对文法的分析。在实验的过程中,最重要的还是要理顺思路,想好解决办法,这也是我经过不断实验总结出的自我思考的方法。然后就进入了编码阶段,此次编码也有一定的难度,在代码量以及代码的整体设计上都有了提升,也是最值得思考的地方。最后,通过实验报告的书写,以及参考资料的查找,对今后的学习和工作都有很大的帮助。

代码

#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <string>
#include <map>
#include <stdexcept> using namespace std; // 预测分析表
vector<vector<string> > table(5, vector<string>(9));
// 文法的产生式
vector<string> G = {"E->TA", "A->+TA", "A->-TA", "A->e", "T->FB", "B->*FB", "B->/FB", "B->e", "F->i", "F->(E)", "F->n"}; // 文法符号到下标转换
map<char, int> index = {{'E', 0}, {'A', 1}, {'T', 2}, {'B', 3}, {'F', 4}, {'i', 0}, {'n', 1}, {'+', 2}, {'-', 3}, {'*', 4}, {'/', 5}, {'(', 6}, {')', 7}, {'$', 8}, {'e', 9}};
// 终结符
string terminal("in+-*/()$");
// 非终结符
string nonTerminal("EATBF"); // 产生式右部的first集
vector<string> First = {"i(n", "+", "-", "e", "i(n", "*", "/", "e", "i", "(", "n"}; // 非终结符的follow集
vector<string> Follow = {"$)", "$)", "+-$)", "+-$)", "*/+-$)"}; int analysis(void); // 预测分析过程
int analysis(void) {
ifstream fin("fin.txt");
if (!fin.is_open()) {
cout << "输入文件不存在 fin.txt." << endl;
return 1;
}
ofstream fout("fout.txt");
if (!fout.is_open()) {
cout << "无法打开输出文件 fout.txt." << endl;
return 1;
} //输入缓冲区
string s;
fin >> s;
cout << "成功读取待分析串:" << endl << s << endl;
int wid = s.length() + 1;
s.push_back('$'); //分析栈
vector<char> analyStack;
analyStack.push_back('$');
analyStack.push_back('E'); // 栈顶和当前输入
char top = '\0', cur = '\0';
auto ip = s.begin(); // 输出头
fout << left << setw(wid + 10) << "栈" << right << setw(wid) << "输入" << " " << "输出" << endl;
do {
// 输出当前栈和当前剩余输入
string str1(analyStack.begin(), analyStack.end());
string str2(ip, s.end());
fout << left << setw(wid + 10) << str1 << right << setw(wid) << str2 << " "; // 栈顶和当前输入符号
top = analyStack.back();
cur = *ip; // 标识符及数字变换
if (isalpha(cur))
cur = 'i';
else if (isdigit(cur))
cur = 'n'; // 栈顶是终结符号或$
if (terminal.find(top) != terminal.npos || top == '$') {
if (top == cur) {
analyStack.pop_back();
++ip;
fout << endl;
} else {
fout << "出错! 不匹配,弹出" << top << endl;
analyStack.pop_back();
}
}
// 栈顶非终结符
else {
//坐标转换
int x = index.at(top);
int y;
try {
y = index.at(cur);
} catch (out_of_range) {
fout << "输入字符非法!" << endl;
break;
} // 产生式
string production = table.at(x).at(y);
// 产生式非空
if (!production.empty()) {
if (production == "synch") { //同步
fout << "出错!synch,弹出" << top << endl;
analyStack.pop_back();
} else { //正常分析
analyStack.pop_back();
string expr(production.begin() + 3, production.end());
if (expr == "e") //epsilon产生式
expr = "";
// 逆序压栈
for (auto iter = expr.rbegin(); iter != expr.rend(); ++iter)
analyStack.push_back(*iter);
// 输出产生式
fout << production << endl;
}
} else { //表项空白
fout << "出错!空白,跳过" << *ip << endl;
++ip;
}
}
} while (top != '$');
cout << endl << "分析结果已输出至 fout.txt." << endl;
return 0;
} int main() {
//算法4.2 构造预测分析表
// 遍历G的每个产生式
for (auto itG = G.begin(), itFirst = First.begin(); itG != G.end() && itFirst != First.end(); ++itG, ++itFirst) {
// 非终结符下标转换
int x = index.at(*(itG->begin()));
for (auto first = itFirst->begin(); first != itFirst->end(); ++first) {
if (*first != 'e') {
int y = index.at(*first);
table.at(x).at(y) = *itG;
} else {
for (auto follow = Follow.at(x).begin(); follow != Follow.at(x).end(); ++follow) {
int y = index.at(*follow);
table.at(x).at(y) = *itG;
}
}
}
}
// 写入同步信息
for (string::size_type i = 0; i < nonTerminal.length(); ++i) {
int x = index.at(nonTerminal.at(i));
for (vector<string>::size_type j = 0; j < Follow.at(i).length(); ++j) {
int y = index.at(Follow.at(i).at(j));
if (table.at(x).at(y).empty())
table[x][y] = "synch";
}
}
// 输出预测分析表
cout << "预测分析表:" << endl;
// 输出终结符
for (string::size_type i = 0; i < terminal.size(); ++i)
cout << '\t' << terminal[i];
cout << endl;
// 输出非终结符
for (string::size_type x = 0; x < nonTerminal.size(); ++x) {
cout << nonTerminal[x];
// 输出产生式
for (string::size_type y = 0; y < table.at(x).size(); ++y)
cout << '\t' << table.at(x).at(y);
cout << endl;
}
cout << endl;
return analysis();
}

语法分析~LL1的实现的更多相关文章

  1. Compiler Principles 语法分析

    语法分析的两种思维方式:1:自顶向下分析 :从语法树的根部推下来一直推到需要确认的终结符号串为止:就是为了找到一个符号串的最左推导 自顶向下分析,因为文法有些是以非终结符开头的另外文法中还可能含有右部 ...

  2. 【编译原理】LL1文法语法分析器

    上篇文章[编译原理]语法分析--自上向下分析 分析了LL1语法,文章最后说给出栗子,现在补上去. 说明: 这个语法分析器是利用LL1分析方法实现的. 预测分析表和终结符以及非终结符都是针对一个特定文法 ...

  3. TINY语言采用递归下降分析法编写语法分析程序

    目录 自顶向下分析方法 TINY文法 消左提左.构造first follow 基本思想 python构造源码 运行结果 参考来源:聊聊编译原理(二) - 语法分析 自顶向下分析方法 自顶向下分析方法: ...

  4. Linux源码Kconfig文件语法分析

    Kconfig是我们进行内核配置的关键文件,用于生成menuconfig的界面并生成最终确定编译选项的.config文件.关于Kconfig文件的编写规则,在Documentation/kbuild/ ...

  5. 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析

    文章中引用的代码均来自https://github.com/vczh/tinymoe.   看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...

  6. 简单的词法分析和语法分析(C++实现,CodeBlocks+GCC编译)

    说明: 分析的语言是SNL语言,详见<编译程序的设计与实现>( 刘磊.金英.张晶.张荷花.单郸编著) 词法分析就是实现了词法分析的自动机 语法分析使用递归下降法 运行结果: 词法分析 得到 ...

  7. Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法

    Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...

  8. 编译原理LL1文法分析树(绘图过程)算法实现

    import hjzgg.analysistable.AnalysisTable; import hjzgg.first.First; import hjzgg.follow.Follow; impo ...

  9. LALR(1)语法分析生成器--xbytes

    0.概述: 看了编译器龙书和虎书后,自己手动写了一个LALR(1)语法分析生成器,使用的语法文件格式和lemon的差不多. 程序里面很多的算法也都是摘录自虎书,龙书虽然讲的很详细,但是真正动手写的时候 ...

  10. JavaCC首页、文档和下载 - 语法分析生成器 - 开源中国社区

    JavaCC首页.文档和下载 - 语法分析生成器 - 开源中国社区

随机推荐

  1. yaml文件读取转化为类

    首先你要有一个文件读取的方法,写一个根据传入路径 + 类来自动返回对应类的方法. /** * 根据传入的path,加载配置文件内容到对应class中 */ public static <T> ...

  2. Linux 系统jdk安装详细教程

    安装jdk步骤先下载jdk的tar压缩包然后解压jdk并压缩至指定安装目录,如果不需要指定安装目录直接写tar -zxvf jdk压缩包名即可tar -zxvf jdk压缩包 -C /这里写指定安装目 ...

  3. Mosquitto安装与部署

    版本说明: Mosquitto版本:v2.0.10     libwebsockets版本:v3.0.1(用于支持websockets)     mosquitto-go-auth(Mosquitto ...

  4. 钉钉群机器人群发[ PHP ]

    // secret 机器人设置 - 加签秘钥 // access_token 机器人设置 - Webhook带此参数 // message 机器人设置- 关键词设置的内容需要和message一致 pu ...

  5. 羊了怎么居家办公?免费不限速的远控软件RayLink一解燃眉之急!!

    近期疫情放开,各地症状不断的小阳人数量猛增(R君盼望大家早日转阴!!).不论是"羊"后居家,还是天选打工人被迫居家,远程控制公司电脑实现居家办公,成了大部分人的默契选择. 想远程控 ...

  6. git 与远程仓库关联返回 fatal: remote origin already exists 解决方法

    今天领导新建了一个代码仓库,我按照流程一步步推送代码,结果到了关联仓库的时候,返回 fatal: remote origin already exists(远程源已经存在) 下面是解决方法 1.删除远 ...

  7. PaddleOCR(PaddleHub Serving)离线部署包制作

    PaddleOCR(PaddleHub Serving)离线部署包制作 环境与版本: 系统 CPU架构 Anaconda3 PaddlePaddle PaccleOCR 银河麒麟Server V10 ...

  8. js 三维数组转对象数组 二维数组转对象数组

    1. 三维数组转对象数组   输出: 代码如下: let dataArr = [ [ [109.654541015625, 29.34387539941801], [110.467529296875, ...

  9. Pytest Fixture(二)

    作用域 固件的作用是为了抽离出重复的工作和方便复用,为了更精细化控制固件(比如只想对数据库访问测试脚本使用自动连接关闭的固件),pytest 使用作用域来进行指定固件的使用范围. 在定义固件时,通过  ...

  10. 构建自动发现的Docker服务架构

    ------------恢复内容开始------------ 建立consul服务 在建立consul服务中,每个提供服务的节点(在Docker主机上)都要部署和运行consul的client,ser ...