LR 分析器实现:C# 代码详解及原理步骤
LR 分析器实现:C# 代码详解及原理步骤
本文将深入解析两段 C# 代码,阐述 LR 分析器实现的原理及步骤。代码展示了如何使用 LR 分析表对输入句子进行语法分析,并通过一个简单的例子进行说明。
代码 1:按钮点击事件处理
private void button6_Click(object sender, EventArgs e)//分析句子
{
if (textBox1.Text.Length == 0)
{
MessageBox.Show('输入为空,分析失败');
return;
}
// 分析句子
for (int i = 0; i < textBox1.Text.Length; i++)
{
if (!lr.exist(lr.Echar, textBox1.Text[i]))
{
MessageBox.Show('含有未知字符!');
return;
}
}
step = 0;//单步操作置零
listView3.Clear();
listView3.Columns.Clear();
listView3.Items.Clear();
listView3.View = View.Details;
string[] text = { '步骤', '状态栈', '符号栈', '输入串', '所用产生式' };
int[] width = { 45, 70, 70, 70, 100 };
for (int i = 0; i < text.Count(); i++)
{
listView3.Columns.Add(text[i], width[i]);
}
lr.sen_Analyze(textBox1.Text);//调用
if (lr.Success)//判断
MessageBox.Show('分析成功,是该文法的一个句子!');
else
MessageBox.Show('分析失败!');
listView3.GridLines = true;
button7.Enabled = true;
button8.Enabled = true;
}
代码 1 中的 button6_Click 函数用于处理分析句子的按钮点击事件。
- 输入检查: 首先判断输入是否为空,若为空则弹出提示框并返回。
- 字符校验: 遍历输入串中的每一个字符,若存在未知字符则弹出提示框并返回。
- 初始化列表视图: 清空
listView3并设置其为View.Details格式,添加列名称和设置列宽。 - 调用分析函数: 调用
LR.cs中的sen_Analyze函数进行句子分析。 - 显示分析结果: 根据
sen_Analyze的分析结果弹出相应的提示框。 - 启用按钮: 启用单步执行和一键执行按钮。
代码 2:LR 分析过程实现
public void sen_Analyze(string text)
{
Jz = new Analyze();
text += '#';
Stack<int> Status = new Stack<int>();
Stack<char> Symbol = new Stack<char>();
Status.Push(0);
Symbol.Push('#');
Jz.stack_state.Add("0 ");
Jz.stack_symbol.Add("#");
Jz.Input_str.Add(text);
Jz.Tran_pro.Add("Initial State");
int p = 0;
while (true)
{
if (LRAna[Status.Peek()][FindID(Echar, text[p])].error)//遇到ERROR 程序结束 分析失败
{
//ERROR();
Success = false;
break;
}
else if (LRAna[Status.Peek()][FindID(Echar, text[p])].type == 'A')//遇到ACC,程序结束 分析成功
{
// MessageBox.Show("分析成功,是该文法的一个句子!");
Success = true;
Jz.stack_state.Add(Jz.stack_state[Jz.stack_state.Count - 1]);//分析成功时 三个属性都不需要改变 加入上一次的内容即可
Jz.stack_symbol.Add(Jz.stack_symbol[Jz.stack_symbol.Count - 1]);
Jz.Input_str.Add(Jz.Input_str[Jz.Input_str.Count - 1]);
Jz.Tran_pro.Add("分析成功!完毕!");
break;
}
if (LRAna[Status.Peek()][FindID(Echar, text[p])].type == 'S')//移进
{
Symbol.Push(text[p]);
Status.Push(LRAna[Status.Peek()][FindID(Echar, text[p])].id);
Jz.stack_state.Add(Jz.stack_state[Jz.stack_state.Count - 1] + Status.Peek().ToString() + " ");//加入状态 Sj 的 j
Jz.stack_symbol.Add(Jz.stack_symbol[Jz.stack_symbol.Count - 1] + text[p].ToString());//移进当前字符
string tempstr = "";
for (int ti = p + 1; ti < text.Length; ti++)//克隆剩余部分字符串
{
tempstr += text[ti];
}
Jz.Input_str.Add(tempstr);//克隆后的字符串入数组
Jz.Tran_pro.Add("移进");
p++;
}
else if (LRAna[Status.Peek()][FindID(Echar, text[p])].type == 'r')//归约
{
int len = LRproNum[LRAna[Status.Peek()][FindID(Echar, text[p])].id].Right.Length;//取第j条产生式 len为长度
char st = LRproNum[LRAna[Status.Peek()][FindID(Echar, text[p])].id].Left[0];//左部就一个字符 转换成char
string Output = st + "->";
string waitout = "";//用来翻转出栈的顺序
for (int i = 0; i < len; i++)
{
Status.Pop();
waitout += Symbol.Peek();
Symbol.Pop();
}
for (int i = waitout.Length - 1; i >= 0; i--)
{
Output += waitout[i];
}
if (LRAna[Status.Peek()][Echar.Count + FindID(Nchar, st)].error)//遇到ERROR 程序结束 分析失败
{
//ERROR();
Success = false;
break;
}
else//GOTO表
{
Symbol.Push(st);
Status.Push(LRAna[Status.Peek()][Echar.Count + FindID(Nchar, st)].id);//非终结符
}
string tempstr = "", tempstr2 = "";
int cnt = 0, cnt2 = 0;//cnt2记录上一行空格数 也就是状态数量 因为10算俩字符 需要加判断 cnt在循环时记录已经添加了几个状态 只在新行需要添加cnt2-len个状态
for (int i = 0; i < Jz.stack_state[Jz.stack_state.Count - 1].Length; i++)
{
if (Jz.stack_state[Jz.stack_state.Count - 1][i] == ' ')
cnt2++;
}
for (int ti = 0; cnt < cnt2 - len; ti++)//由于有出栈情况 重新构造一个字符串 *2是因为有空格
{
if (Jz.stack_state[Jz.stack_state.Count - 1][ti] == ' ')
cnt++;
tempstr += Jz.stack_state[Jz.stack_state.Count - 1][ti];
}
for (int ti = 0; ti < Jz.stack_symbol[Jz.stack_symbol.Count - 1].Length - len; ti++)//由于有出栈情况 重新构造一个字符串
{
tempstr2 += Jz.stack_symbol[Jz.stack_symbol.Count - 1][ti];
}
Jz.stack_state.Add(tempstr + Status.Peek().ToString() + " ");
Jz.stack_symbol.Add(tempstr2 + st);//移进归约后的非终结符
Jz.Input_str.Add(Jz.Input_str[Jz.Input_str.Count - 1]);//不变
Jz.Tran_pro.Add("归约用" + Output);
}
}
}
代码 2 中的 sen_Analyze 函数实现了 LR 分析表的分析过程。
- 初始化: 初始化
Jz对象,添加结束标记#,创建状态栈和符号栈,并将初始状态 0 和结束标记#分别入栈。 - 添加初始信息: 将初始状态、符号、输入串及所用产生式加入对应的数组中。
- 分析循环: 进入分析循环,根据 LR 分析表进行移进归约操作,并将分析过程中产生的信息加入对应的数组中。
分析循环中的具体步骤:
- 遇到 ERROR: 若遇到 ERROR 则分析失败,退出循环。
- 遇到 ACC: 若遇到 ACC 则分析成功,加入相应的信息并退出循环。
- 遇到移进操作: 将字符入符号栈,状态入状态栈,并加入相应的信息。
- 遇到归约操作: 根据产生式进行出栈操作,并将产生式左部非终结符入符号栈,状态入状态栈,并加入相应的信息。
原理步骤
LR 分析器是一种自下而上的语法分析器,其工作原理如下:
- 构建 LR 分析表: 根据文法构造 LR 分析表,该表包含状态、输入符号、动作(移进、归约、接受或错误)和 GOTO 表信息。
- 初始化状态栈和符号栈: 将初始状态和结束标记入栈。
- 分析循环: 根据当前状态和输入符号,从 LR 分析表中查找对应动作,并进行相应的操作。
- 移进操作: 将输入符号和对应的状态入栈。
- 归约操作: 根据产生式进行出栈操作,并将产生式左部非终结符和对应的状态入栈。
- 接受操作: 当遇到 ACC 状态时,分析成功。
- 错误操作: 当遇到 ERROR 状态时,分析失败。
- 重复步骤 3 直至分析成功或失败。
总结
代码 1 的 button6_Click 函数调用了代码 2 的 sen_Analyze 函数进行 LR 分析表的分析过程,并将分析结果显示在 listView3 中。sen_Analyze 函数实现了 LR 分析表的分析过程,根据输入串和 LR 分析表进行移进归约操作,并将分析过程中产生的信息加入对应的数组中。
通过本文的讲解,读者可以更深入地理解 LR 分析器的原理和实现过程,并为进一步学习编译原理和语法分析打下基础
原文地址: https://www.cveoy.top/t/topic/oIeQ 著作权归作者所有。请勿转载和采集!