编译原理 SLR(1) 语法分析器的构建

编译原理 SLR(1) 语法分析器的构建

在我的博客查看:https://chenhaotian.top/study/compilation-principle-slr1/

实验三 自底向上语法分析器的构建

项目代码:https://github.com/chen2438/zstu-study/tree/main/%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86/%E5%AE%9E%E9%AA%8C/%E5%AE%9E%E9%AA%8C%E4%B8%89/

一、 实验要求

运用SLR(1)或者LR(1)分析法,针对给定的上下文无关文法,给出实验方案。预估实验中可能出现的问题。

二、 实验方案

(评价依据实验方案设计是否合理,包括输入输出的设计)

使用SLR(1)文法。逐步完成Augmented Grammar、First&Follow Set、DFA、Parse Table。

然后将分析表处理为程序可读数据,程序根据分析表的内容得出分析过程和结果。

输入设计:

Parse Table、Augmented Grammar。

输出设计:

带有Parsing Stack、Input和Action的分析过程。

三、 预估问题

(是否有预估的问题,预估的问题是否合理)

  1. 程序需要事先获得Augmented Grammar、Parse Table、Terminals、Non-terminals的具体内容,这些数据都要被处理为适当的格式,过程比较繁琐。
  2. 可以将每个(non)terminal映射为数字,便于直接调用table(i,(non)terminal)。
  3. 注意非终结符id的特殊处理。

理论基础(评价依据 理论知识非常清楚)

image-20230531191404565

image-20230531191425101

四、 内容和步骤

1、考虑简单算术表达式文法G:
E→E + T | T
T→T * F | F
F→(E) | id

试设计SLR(1)或者LR(1)分析程序,以输入的 (a+b)*c+(d+e) 符号串进行语法分析。

2、实验具体步骤

实验三-1

实验三-2

输入数据:

image-20230531191245481

五、 实验结果:

1、 代码
#include <iostream>
#include <iomanip>
#include <stack>
#include <string>

using namespace std;

int table[20][20][2];// I,(non)terminals,action
int mp[200];//映射
string terminals = "+*()i$ETF";
string inputString = "(a+b)*c+(d+e)";
stack<int> pStk, iStk; //parse stack, input stack
stack<int> reduceResult;//存储规约结果, 用于分析树
string reduce[7][2]; //规约
int step = 1;

namespace Graph { //处理分析树
    const int N = 1000, M = N * 2;
    int nodemap[N] = { 0, 'E' };
    int depth[N];

    struct Edge {
        int to, nxt;
    }e[M];

    int adt, head[N];

    void add(int u, int v) {
        e[++adt] = { v,head[u] };
        head[u] = adt;
    }

    int fa[N];

    void dfs(int p1) {//输出分析树
        for (int i = 0; i < depth[p1]; i++) {
            cout << "   |";
        }
        cout << "--" << (char)nodemap[p1] << endl;
        for (int i = head[p1]; i != 0; i = e[i].nxt) {
            int p2 = e[i].to;
            if (p2 == fa[p1]) continue;
            fa[p2] = p1;
            dfs(p2);
        }
    }

    void parseTree() {
        int vst[1000] = { 0 };
        depth[1] = 0;
        int cnt = 2;
        while (!reduceResult.empty()) {//读出规约结果
            int rTop = reduceResult.top(); reduceResult.pop();
            int leftChar = reduce[rTop][0][0];//产生式左部
            string rightString = reduce[rTop][1];//产生式右部
            int oldCnt = cnt;
            for (int j = oldCnt - 1; j >= 1; j--) {//从右往左匹配父节点
                if (nodemap[j] == leftChar and !vst[j]) {
                    vst[j] = 1;
                    for (char k : rightString) {
                        nodemap[cnt] = k;//给节点编号 加映射
                        depth[cnt] = depth[j] + 1;
                        add(j, cnt);
                        cnt++;
                    }
                    break;
                }
            }
        }
        dfs(1);
    }
}


stack<int> reverse(stack<int> s) {
    stack<int> tmp;
    while (!s.empty()) {
        tmp.push(s.top());
        s.pop();
    }
    s = tmp;
    return s;
}

string reverse(string s) {//禁止使用引用
    reverse(s.begin(), s.end());
    return s;
}

void show(stack<int> ps, stack<int> is) {
    ps = reverse(ps);
    int width = 25;
    string str1, str2;
    int odd = 1;
    while (!ps.empty()) {
        if (odd == 1)  str1 += (char)ps.top();
        else str1 += to_string(ps.top());
        odd *= -1;
        ps.pop();
    }
    while (!is.empty()) {
        str2 += (char)is.top();
        is.pop();
    }
    string strBlank(width - str1.size() - str2.size(), ' ');
    cout << str1 << strBlank << str2;
}

void init() {
    for (int i = 0; i < terminals.size(); i++) {
        mp[terminals[i]] = i; // 映射 (non)terminals -> number
        if (terminals[i] == 'i') {// id = {a ~ z}
            for (int j = 'a'; j <= 'z'; j++) {
                mp[j] = i;
            }
        }
    }
    for (int i = 0; i <= 11; i++) {
        for (int j = 0; j < terminals.size(); j++) {
            char c; cin >> c;
            table[i][j][0] = c;
        }
    }
    for (int i = 0; i <= 11; i++) {
        for (int j = 0; j < terminals.size(); j++) {
            int num; cin >> num;
            table[i][j][1] = num;
        }
    }
    for (int i = 0; i < 7; i++) {
        cin >> reduce[i][0] >> reduce[i][1];
        reduce[i][1] = reverse(reduce[i][1]);
    }
}

int parseTable() {
    pStk.push('$');
    pStk.push(0);
    iStk.push('$');
    for (int i = inputString.size() - 1; i >= 0; i--) {
        iStk.push(inputString[i]);
    }
    cout << "---------------------------"
        << "--------------------------" << endl;
    cout << "Step   Parsing Stack       Input    Action" << endl;
    while (!iStk.empty() and !pStk.empty()) {
        cout << step << "     ";
        if (step++ < 10) putchar(' ');
        show(pStk, iStk);
        int pTop = pStk.top(), iTop = iStk.top();
        int action[2] = { table[pTop][mp[iTop]][0],
                          table[pTop][mp[iTop]][1] };
        if (action[0] == 'A') {// 接受
            cout << "    Accept" << endl;
            return 200;
        } else if (action[0] == 'S') {// 移进
            cout << "    Shift " << action[1] << endl;
            pStk.push(iTop);
            pStk.push(action[1]);
            iStk.pop();
        } else if (action[0] == 'R') {// 规约
            reduceResult.push(action[1]);

            cout << "    Reduce " << action[1]
                << ": " + reduce[action[1]][0] + " -> "
                << reverse(reduce[action[1]][1]) << endl;

            for (auto i : reduce[action[1]][1]) {
                while (!pStk.empty()) {
                    int c = pStk.top();
                    pStk.pop();
                    if (c == i) break;
                    if (i == 'i') {
                        if ('a' <= c && c <= 'z') break;
                    }
                }
            }
            int pTop1 = pStk.top();
            pStk.push(reduce[action[1]][0][0]);
            int pTop2 = pStk.top();
            pStk.push(table[pTop1][mp[pTop2]][1]);
        } else {
            return 500;
        }
    }
    return 500;
}

void solve() {
    init();
    int res = parseTable();
    if (res == 200) {
        puts("\nParsing Success");
    } else {
        puts("\nParsing Failed");
    }
    puts("\nParsing Tree:");
    Graph::parseTree();
}

int main() {
    FILE* fp;
    freopen_s(&fp, "input.txt", "r", stdin);
    solve();
    fclose(fp);
}

input.txt 放在程序同目录下

n n S n S n Y Y Y
S n n n n A n n n
R S n R n R n n n
R R n R n R n n n
n n S n S n Y Y Y
R R n R n R n n n
n n S n S n n Y Y
n n S n S n n n Y
S n n S n n n n n
R S n R n R n n n
R R n R n R n n n
R R n R n R n n n

 0 0 4 0 5 0 1 2 3
 6 0 0 0 0 0 0 0 0
 2 7 0 2 0 2 0 0 0
 4 4 0 4 0 4 0 0 0
 0 0 4 0 5 0 8 2 3
 6 6 0 6 0 6 0 0 0
 0 0 4 0 5 0 0 9 3
 0 0 4 0 5 0 0 0 10
6 0 0 11 0 0 0 0 0
 1 7 0 1 0 1 0 0 0
 3 3 0 3 0 3 0 0 0
 5 5 0 5 0 5 0 0 0

E' E
E E+T
E T
T T*F
T F
F (E)
F i
2、 截图

image-20230531191310815

image-20230531191321449

六、 实验结论:

1 、实验结论

(是否能够准确描述实验的结论)

本实验使用SLR(1)文法,根据给定上下文无关文法,完成它的分析程序,并在结果中给出分析过程。

程序可以处理给定token序列不满足给定文法的情况。

此程序的优势是可以快速地修改以适用于不同的SLR(1)文法。

2、分析和总结

1)对输入设计的结论

Augmented Grammar使用string reduce[7][2];存储用于规约。

Parse Table使用int table[20][20][2];存储。分两次读入。

Terminals、Non-terminals直接硬编码到代码中。

2)对输出设计的结论

注意输出格式、栈的展示方向、数据左右对齐。

输出分析树时,先根据规约结果生成分析树存到邻接表中,然后DFS遍历整张图并输出。

3)对SLR(1)或者LR(1)分析法的结论

LR(0):见到First集就移进,见到终态就归约

SLR(1)见到First集就移进,见到终态先看Follow集,与Follow集对应的项目归约,其它报错。

SLR分析法包含的展望信息是体现在利用了Follow(A)信息,可以解决“归约-归约”冲突

SLR分析法没有包含足够的展望信息,不能完成解决“移进-归约”冲突,需要改进。

LALR同心集合并不会产生“移进-归约”冲突 ,但会产生“归约-归约”冲突

3、 对预估问题的结论
  1. 程序需要事先获得Augmented Grammar、Parse Table、Terminals、Non-terminals的具体内容,这些数据都要被处理为适当的格式,过程比较繁琐。

​ Augmented Grammar使用string reduce[7][2];存储用于规约。

​ Parse Table使用int table[20][20][2];存储。分两次读入。

​ Terminals、Non-terminals直接硬编码到代码中。

  1. 可以将每个(non)terminal映射为数字,便于直接调用table(i,(non)terminal)。
  2. 注意非终结符id的特殊处理。

以下代码解决2、3问题

  for (int i = 0; i < terminals.size(); i++) {
    mp[terminals[i]] = i; // 映射 (non)terminals -> number
    if (terminals[i] == 'i') {// id = {a ~ z}
      for (int j = 'a'; j <= 'z'; j++) {
        mp[j] = i;
      }
    }
}

在问题3处,还需注意规约时的字符替换处理。

SLR(1)语法分析器是一种自下而上的语法分析器,它使用了LR(0)项目集家族和FIRST/FOLLOW集合,进行语法分析SLR(1)语法分析器的原理如下: 1. 构造LR(0)项目集家族:首先,需要根据文法G的所有产生式构造出LR(0)项目集家族,包括起始符号S的增广文法G'的所有产生式。每个项目集Ii由LR(0)项目组成,其中每个项目都有形如A→α·β的形式,表示在某个产生式的某个位置上,将会选择beta作为下一步的推导。 2. 计算FIRST集:对于文法G的每个非终结符号A,计算FIRST(A),即终结符号集合{a|A=>*aβ},其中=>*表示A能够推导出β。 3. 计算FOLLOW集:对于每个非终结符号A,计算FOLLOW(A),即终结符号集合{a|S=>*αAaβ},其中α和β是文法符号串。 4. 构造LR(0)自动机:基于LR(0)项目集家族和FIRST/FOLLOW集合,构造出LR(0)自动机。其中,每个状态代表一个LR(0)项目集,每个转换代表一个文法符号。 5. 构造SLR(1)表:在LR(0)自动机的基础上,通过计算每个状态的ACTION和GOTO函数,得到SLR(1)分析表。ACTION函数用于处理终结符号,GOTO函数用于处理非终结符号。ACTION[i,a]表示在状态i和读入符号a的情况下,执行的动作,可以是移进、规约或者接受。GOTO[i,A]表示在状态i和读入非终结符号A的情况下,转移到的状态。 6. 进行语法分析:使用SLR(1)分析表,根据输入符号串进行语法分析,具体流程为:从状态0开始,每次读取一个输入符号,根据分析表执行移进或者规约操作,直到接受或者出现错误。 SLR(1)语法分析器的优点是简单易懂,缺点是不能处理所有文法,只能处理SLR(1)文法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值