语法分析~LL1的实现
语法分析之 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的实现的更多相关文章
- Compiler Principles 语法分析
语法分析的两种思维方式:1:自顶向下分析 :从语法树的根部推下来一直推到需要确认的终结符号串为止:就是为了找到一个符号串的最左推导 自顶向下分析,因为文法有些是以非终结符开头的另外文法中还可能含有右部 ...
- 【编译原理】LL1文法语法分析器
上篇文章[编译原理]语法分析--自上向下分析 分析了LL1语法,文章最后说给出栗子,现在补上去. 说明: 这个语法分析器是利用LL1分析方法实现的. 预测分析表和终结符以及非终结符都是针对一个特定文法 ...
- TINY语言采用递归下降分析法编写语法分析程序
目录 自顶向下分析方法 TINY文法 消左提左.构造first follow 基本思想 python构造源码 运行结果 参考来源:聊聊编译原理(二) - 语法分析 自顶向下分析方法 自顶向下分析方法: ...
- Linux源码Kconfig文件语法分析
Kconfig是我们进行内核配置的关键文件,用于生成menuconfig的界面并生成最终确定编译选项的.config文件.关于Kconfig文件的编写规则,在Documentation/kbuild/ ...
- 跟vczh看实例学编译原理——三:Tinymoe与无歧义语法分析
文章中引用的代码均来自https://github.com/vczh/tinymoe. 看了前面的三篇文章,大家应该基本对Tinymoe的代码有一个初步的感觉了.在正确分析"print ...
- 简单的词法分析和语法分析(C++实现,CodeBlocks+GCC编译)
说明: 分析的语言是SNL语言,详见<编译程序的设计与实现>( 刘磊.金英.张晶.张荷花.单郸编著) 词法分析就是实现了词法分析的自动机 语法分析使用递归下降法 运行结果: 词法分析 得到 ...
- Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法
Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...
- 编译原理LL1文法分析树(绘图过程)算法实现
import hjzgg.analysistable.AnalysisTable; import hjzgg.first.First; import hjzgg.follow.Follow; impo ...
- LALR(1)语法分析生成器--xbytes
0.概述: 看了编译器龙书和虎书后,自己手动写了一个LALR(1)语法分析生成器,使用的语法文件格式和lemon的差不多. 程序里面很多的算法也都是摘录自虎书,龙书虽然讲的很详细,但是真正动手写的时候 ...
- JavaCC首页、文档和下载 - 语法分析生成器 - 开源中国社区
JavaCC首页.文档和下载 - 语法分析生成器 - 开源中国社区
随机推荐
- yaml文件读取转化为类
首先你要有一个文件读取的方法,写一个根据传入路径 + 类来自动返回对应类的方法. /** * 根据传入的path,加载配置文件内容到对应class中 */ public static <T> ...
- Linux 系统jdk安装详细教程
安装jdk步骤先下载jdk的tar压缩包然后解压jdk并压缩至指定安装目录,如果不需要指定安装目录直接写tar -zxvf jdk压缩包名即可tar -zxvf jdk压缩包 -C /这里写指定安装目 ...
- Mosquitto安装与部署
版本说明: Mosquitto版本:v2.0.10 libwebsockets版本:v3.0.1(用于支持websockets) mosquitto-go-auth(Mosquitto ...
- 钉钉群机器人群发[ PHP ]
// secret 机器人设置 - 加签秘钥 // access_token 机器人设置 - Webhook带此参数 // message 机器人设置- 关键词设置的内容需要和message一致 pu ...
- 羊了怎么居家办公?免费不限速的远控软件RayLink一解燃眉之急!!
近期疫情放开,各地症状不断的小阳人数量猛增(R君盼望大家早日转阴!!).不论是"羊"后居家,还是天选打工人被迫居家,远程控制公司电脑实现居家办公,成了大部分人的默契选择. 想远程控 ...
- git 与远程仓库关联返回 fatal: remote origin already exists 解决方法
今天领导新建了一个代码仓库,我按照流程一步步推送代码,结果到了关联仓库的时候,返回 fatal: remote origin already exists(远程源已经存在) 下面是解决方法 1.删除远 ...
- PaddleOCR(PaddleHub Serving)离线部署包制作
PaddleOCR(PaddleHub Serving)离线部署包制作 环境与版本: 系统 CPU架构 Anaconda3 PaddlePaddle PaccleOCR 银河麒麟Server V10 ...
- js 三维数组转对象数组 二维数组转对象数组
1. 三维数组转对象数组 输出: 代码如下: let dataArr = [ [ [109.654541015625, 29.34387539941801], [110.467529296875, ...
- Pytest Fixture(二)
作用域 固件的作用是为了抽离出重复的工作和方便复用,为了更精细化控制固件(比如只想对数据库访问测试脚本使用自动连接关闭的固件),pytest 使用作用域来进行指定固件的使用范围. 在定义固件时,通过 ...
- 构建自动发现的Docker服务架构
------------恢复内容开始------------ 建立consul服务 在建立consul服务中,每个提供服务的节点(在Docker主机上)都要部署和运行consul的client,ser ...