SDUT编译原理上机测试

题目链接

小C语言–词法分析程序

#include <bits/stdc++.h>
using namespace std;
string s[2010];
string temp;
int check(char c) {
    //运算符输出1,数组输出2,字母输出3,界符输出4
    //其他字符输出3(因为可能下划线之类的字符出现在自定义标识符中)
    if (c == '=' || c == '-' || c == '+' || c == '*' || c == '/' || c == '>' ||c == '<' || c == '!')
        return 1;
    if (c >= '0' && c <= '9') return 2;
    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) return 3;
    if (c == '{' || c == '}' || c == ',' || c == ';' || c == '(' || c == ')')
        return 4;
    return 3;
}
void check_s(string s) {
    //判断字符串为整数、关键字还是自定义标识符
    if (temp.size() == 0) return;
    int f = 0;
    for (int i = 0; i < s.size(); i++)
        if (check(s[i]) == 3) {//如果有一个字符为字母,则肯定不是整数
            f = 1;
            break;
        }
    //检查是否为关键字
    if (s == "main" || s == "if" || s == "else" || s == "for" || s == "while" ||
        s == "int") {
        cout << "(keyword," << s << ')' <<endl;
        return;
    }
    //不是关键字且f=1则为自定义标识符
    if (f == 1) {
        cout << "(identifier," << s << ')' << endl;
    } else {//否则为数字
        cout << "(integer," << s << ')' << endl;
    }
}
int main(int argc, char const *argv[]) {
    int n = 0;
    while(cin >> s[n++]);//输入所有的字符串,存储在string数组中
    for(int i = 0; i < n; i++){//遍历每个字符串
        for(int j = 0; j < s[i].size(); j++) {//遍历字符串中的每个字符
            switch (check(s[i][j])) {
                case 1 : {//当前字符为运算符
                    check_s(temp);//处理运算符之前的字符串,下同
                    temp.clear();//清空,下同
                    if (j + 1 < s[i].size() && check(s[i][j + 1]) == 1)//判断是否为双字符的运算符
                        printf("(operator,%c%c)\n",s[i][j],s[i][j+1]),j++;//下标下移
                    else
                        printf("(operator,%c)\n", s[i][j]);
                    break;
                }
                case 2://数字加入temp
                    temp += s[i][j];
                    break;
                case 3://字母加入temp
                    temp += s[i][j];
                    break;
                case 4: {//处理界符
                    check_s(temp);
                    temp.clear();
                    printf("(boundary,%c)\n",s[i][j]);
                    break;
                }
            }
        }
        check_s(temp);//注意最后可能还剩下没有输出的字符串,需要进行处理
        temp.clear();
    }
    return 0;
}

识别浮点常量问题

#include <bits/stdc++.h>
using namespace std;
int check(char c) {
    if(c <= '9'&&c >= '0') return 1;
    if(c == '+' || c == '-') return 2;
    return 0;
}
int main(int argc, char const *argv[]) {
    string s;
    cin >> s;
    int cnt = 0;//记录有几个 '.'和'e','E'
    int i = 0;
    int front = 0;//记录某个符号前数字的长度
    while(i < s.size()) {//遍历字符串
        if(s[i] == 'e' || s[i] =='E'){
            //e和E的前面不能没有数字,同时不能有两个以上的'e'或'.',同时不能在末尾,同时后面只能接数字或者正负号
            if(front == 0 || cnt >= 2 || i == s.size() - 1 || check(s[i+1]) == 0) {
                puts("NO");
                return 0;
            }
            cnt++;//记录小数符的个数
            front = 0;//记录下一个符号之前数字的长度,所以要清空
        }
        else if(s[i] == '.') {
            // '.'的前面不能没有数字,同时前面不能有'.'或'E',同时不能在末尾,同时后面只能接数字或者正负号
            if(front == 0 || cnt != 0|| i == s.size() - 1 || check(s[i+1]) == 0) {
                puts("NO");
                return 0;
            }
            cnt++;//同上
            front = 0;
        }
        else if(s[i] == '+' || s[i] == '-') {
            //正负号前不能有数字,且后面必须接数字
            if(front!=0 || i == s.size() - 1 || check(s[i+1]) != 1) {
                puts("NO");
                return 0;
            }
            front = 0;
        }
        else if(check(s[i]) != 1) {
            //如果不为数字以及其他合法字符,则输出NO
            puts("NO");
            return 0;
        }else//如果为数字,则记录数字长度的front+1
            front++;;
        i++;
    }
    //如果小数符有一个或两个(一个'.' + 一个 'E')则合法,否则不是浮点数
    if(cnt == 1 || cnt == 2) puts("YES");
    else puts("NO");
    return 0;
}

Python版本

正则表达式yyds!

import re

s = r'[+-]?\d+((\.\d+)([Ee][+-]?\d+)?|([eE][+-]?\d+))$'
if re.match(s,input().strip()) :
    print("YES")
else :
    print("NO")

表达式语法分析——递归子程序法

这题需要注意的是,对于句子:$E \rightarrow TG$ 和 $T \rightarrow FS$ 需要先判断一下 $SELECT$ 集再进行递归,如果输入字符不属于 $SELECT$ 集则直接输出 ERROR。

当然对于这道题是需要计算SELECT集的,但是对于书上的递归下降子程序法是不需要判断的,直接往下递归就行,不过如果遇到的文法中包含句子 $E \rightarrow F|S$ ,那么就不能用递归下降子程序法了,因为不知道选取哪个候选式进行递归,此时就需要计算SELECT集了。

下面给出的代码是在PTA上可以过的(但自认为与递归子程序法的描述有些出入):

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
void T();
void G();
void S();
void F();
string s;
int pos, cnt;
void E() {
    if (s[pos] != 'i' && s[pos] != '(') {
        cout << "error" << endl;
        exit(0);
    }
    printf("%d E-->TG\n", cnt++);
    T();
    G();
}

void G() {
    if (s[pos] == '+') {
        printf("%d G-->+TG\n", cnt++);
        pos++;
        T();
        G();
    } else
        printf("%d G-->&\n", cnt++);
}
void T() {
    if (s[pos] != 'i' && s[pos] != '(') {
        cout << "error" << endl;
        exit(0);
    }
    printf("%d T-->FS\n", cnt++);
    F();
    S();
}
void S() {
    if (s[pos] == '*') {
        printf("%d S-->*FS\n", cnt++);
        pos++;
        F();
        S();
    } else
        printf("%d S-->&\n", cnt++);
}
void F() {
    if (s[pos] == '(') {
        pos++;
        printf("%d F-->(E)\n", cnt++);
        E();
        if (s[pos] == ')')
            pos++;
        else {
            puts("error");
            exit(0);
        }
    } else if (s[pos] == 'i') {
        pos++;
        printf("%d F-->i\n", cnt++);
    } else {
        puts("error");
        exit(0);
    }
}
int main(int argc, char const *argv[]) {
    cin >> s;
    E();
    if (s[pos] == '#')
        puts("accept");
    else
        puts("error");
    return 0;
}

表达式语法分析——预测分析法

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e5+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double PI = acos(-1.0);
typedef pair<int,int> PII;
typedef pair<LL,LL> PLL;
string s;
int pos,cnt;
stack<char> sta;
bool check(char x) {
    char y = s[pos];
    if(x == y) {
        pos++;
        return 1;
    }
    if (x == 'E' && (y == 'i' || y == '(')) {
        printf("%d E->TG\n",++cnt);
        sta.push('G');
        sta.push('T');
        return 1;
    } else if (x == 'G') {
        if(y == '+') {
            printf("%d G->+TG\n", ++cnt);
            sta.push('G');
            sta.push('T');
            sta.push('+');
            return 1;
        }else if(y == ')' || y == '#') {
            printf("%d G->^\n", ++cnt);
            return 1;
        }
    }else if(x == 'T' && (y == 'i' || y == '(')) {
        printf("%d T->FS\n", ++cnt);
        sta.push('S');
        sta.push('F');
        return 1;
    }else if(x == 'S') {
        if(y == '+' || y == ')' || y == '#') {
            printf("%d S->^\n", ++cnt);
            return 1;
        }
        else if(y == '*') {
            printf("%d S->*FS\n", ++cnt);
            sta.push('S');
            sta.push('F');
            sta.push('*');
            return 1;
        }
    }else if(x == 'F') {
        if(y == 'i') {
            printf("%d F->i\n", ++cnt);
            sta.push('i');
            return 1;
        }
        else if(y == '(') {
            printf("%d F->(E)\n", ++cnt);
            sta.push(')');
            sta.push('E');
            sta.push('(');
            return 1;
        }
    }
    return 0;
}
int main(int argc, char const *argv[]) {
    cin >> s;
    sta.push('#');
    sta.push('E');
    while(sta.size()) {
        char now = sta.top();
        sta.pop();
        if(!check(now)){
            puts("error!");
            break;
        }
        if(sta.top() == '#') break;
    }
    if(sta.top() == '#' && s[pos] == '#') puts("acc!");
    return 0;
}

翻译布尔表达式

关于拉链-回填技术可以看这篇博客:传送门

但是对于这道题,我们不用真正实现拉链回填技术(虽然题目说练习拉链回填技术),因为拉链回填技术是在中间代码生成的过程中完成的,而中间代码生成又是在语法分析中完成的,这就意味着,如果是要实现拉链回填技术,需要先进行某种文法的分析(LR(1)或LALR(1)等),然后实现布尔表达式的翻译模式,这就太复杂了。不过我们还是参照了拉链回填技术的思想。

具体思想是这样的:

由于只有 andor ,所以我们只需要分析这两种逻辑就行。

对于 and ,两边只要有一个为假则就都为假,全真才为真。多个 and 连接也是一样:只要有一个表达式为假则全假,全真整体才为真。所以,我们可以把一段连续的 and 看做一个整体,这个整体中每个 and 的假链都连向最后一句的下一句。但是由于需要拉链,所以这个整体的最后一句的假链连向最后第二句的假地址,最后第二句连向最后第三句的假……直到第一句的假链连向整体的最后一句的下一句。 那么对于每个表达式的真链,都指向下一个表达式的开始,因为需要逐个判断都为真才整体真,最后一个表达式的真链指向整体的真链。

对于 or 类似分析即可。

(文字分析比较麻烦,看一下代码就能懂)

#include <bits/stdc++.h>
const int maxn = 1e5+10;
const int inf = 0x3f3f3f3f;
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
char s[111][111], temp[111][111];
int cnt;
int main(int argc, char const *argv[]) {
    while(cin >> s[cnt++]);  // 先按照空格读入每个单词
    int True = 1, False = 100, id = 100;// 初始真链为1,假链为100,四元式编号为100
    vector<char *> temp;//存放一段连续的and
    for(int i = 0; i < cnt; i++) {
        if(strcmp(s[i], "or") == 0 || i == cnt - 1) { //当前单词为or或者最后一个单词
            if(strcmp(s[i], "or") == 0) False += 2; //如果为 or 则假链再+2
            else False = 0; //否则假链为整个式子的假链出口 0
            //先将前面的几个逻辑语句处理完,剩最优一个
            for(int j = 0; j < temp.size() - 3; j += 3) {
                printf("%d(j%s,%s,%s,%d)\n", id++, temp[j+1], temp[j], temp[j+2], id+2);
                printf("%d(j,_,_,%d)\n", id, False);
                False = id++;
            }
            //处理最后一句
            int j = temp.size() - 3;
            printf("%d(j%s,%s,%s,%d)\n", id, temp[j+1], temp[j], temp[j+2], True);
            True = id++;
            printf("%d(j,_,_,%d)\n", id++, False);
            temp.clear();
        }
        else if(strcmp(s[i], "and") == 0) False += 2; //如果为and则将假链后移2
        else temp.push_back(s[i]); //将当前单词压入temp
    }
    return 0;
}

DAG优化

#include <bits/stdc++.h>
using namespace std;
struct node
{
    int l, r;          // 左右儿子编号
    char op;           //存节点的标记
    vector<char> fu;  //存附加标记
}tr[111];
int cnt, flag[111];
char ans[111][1111];

bool has(int id, char c) {  // 检查编号为id的节点中是否存在附加节点 k
    for(int i = 0; i < tr[id].fu.size(); i++) 
        if(tr[id].fu[i] == c) return 1;
    return 0;
}
int creat(char c) {  //返回含有k的节点编号
    for (int i = cnt; i >= 1; i--)  //倒序查找是否存在含有k的节点
        if(has(i, c) || tr[i].op == c) return i;
    tr[++cnt].op = c;  //不存在则新建节点,标记为k
    return cnt;
}
void add(int l, int r, char op, char c) { //建边
    for(int i = cnt; i >= 1; i--) { //寻找是否已经存在这样的点
        if(tr[i].l == l && tr[i].r == r && tr[i].op == op) {
            tr[i].fu.push_back(c);
            return;
        }
    }
    cnt++;  //不存在则新建
    tr[cnt] = {l,r,op};
    tr[cnt].fu.push_back(c);
}
void dfs(int x) {
    if(tr[x].l && tr[x].r) {
        flag[x] = 1; //由于要按照顺序输出,所以先标记,最后一起输出
        dfs(tr[x].l);
        dfs(tr[x].r);
    }
}
void solve(int so, int ta, int pos) {
    //在编号为so的节点中查找,结果存在第ta个优化后表达式的pos位置
    if(tr[so].fu.size() == 0) { //如果so中没附加标记,则op的值赋到pos
        ans[ta][pos] = tr[so].op;
        return;
    }
    //遍历附加标记,优先使用A和B赋值,题目保证不会同时存在A和B
    for(int i = 0; i < tr[so].fu.size(); i++) { 
        if(tr[so].fu[i] == 'A' || tr[so].fu[i] == 'B') {
            ans[ta][pos] = tr[so].fu[i];
            return;
        }
    }
    ans[ta][pos] = tr[so].fu[0];//不存在A和B则用第一个附加标记赋值
}
int main(int argc, char const *argv[]) {
    int n;
    cin >> n;
    for(int i = 0; i < n; i++) {
        string s;
        cin >> s;
        int l = creat(s[2]), r = creat(s[4]);//寻找表达式的左右节点
        add(l,r,s[3],s[0]);//表达式建边
    }
    for(int i = 1; i <= cnt; i++) {//优化每个表达式
        if(tr[i].l && tr[i].r) {//如果不是叶子节点,说明该节点表示一个表达式
            ans[i][1] = '=', ans[i][3] = tr[i].op, ans[i][5] = '\0';
            //分别进行表达式的优化
            solve(i, i, 0);
            solve(tr[i].l, i, 2);
            solve(tr[i].r, i, 4);
        }
    }
    //要求只保留A和B相关的表达式,从后往前找最后一个A和B出现的位置,递归输出整个子树
    for(int i = cnt; i >= 1; i--) {
        if(ans[i][0] == 'A') {dfs(i);break;}
    }
    for(int i = cnt; i >= 1; i--) {
        if(ans[i][0] == 'B') {dfs(i); break;}
    }
    for(int i = 1; i <= cnt; i++) {
        if(flag[i]) printf("%s\n",ans[i]);
    }
    return 0;
}

简单的代码生成程序

#include <bits/stdc++.h>
using namespace std;
string s[111];
char r[111];
int n, m, cnt; // n表示三地址代码数量,m代表寄存器最多个数,cnt表示已经使用的寄存器数量
int use(int i, char c) {
    /*
    查找从第i个三地址代码开始,最早出现变量c的三地址代码的编号
    */
    for( ; i < n; i++) { // 从i开始遍历所有的代码
        if(s[i][3] == c || s[i][5] == c) return i;
    }
    return n;
}
int find(int pos) {
    /*
    返回一个可用的寄存器下标,有两种情况:
    1. 有空寄存器,则直接返回空寄存器下标
    2. 没有空寄存器,遍历所有寄存器,寻找寄存器中所存储的变量最晚被用到的那个寄存器,将其中的变量存到内存中。
    */
    if(cnt < m) return cnt++; //空寄存器
    int Max = -1, id;
    for(int i = 0; i < cnt; i++) {
        int temp = use(pos, r[i]); //use查找从pos开始最早出现r[i]的三地址代码
        if(temp > Max) {
            Max = temp;//维护最晚出现的,即三地址代码下标最大的寄存器
            id = i;
        }
    }
    return id;
}

int main(int argc, char const *argv[]) {
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> s[i];
    for(int i = 0; i < n; i++) {
        int id = find(r, r + cnt, s[i][3]) - r; //寻找当前寄存器中是否存在左操作数
        if(id == cnt) {//不存在,需要LD左操作数
            id = find(i); //寻找用于LD左操作数的寄存器
            if(r[id] != '\0' && use(i, r[id]) < n) { 
                // 如果用于LD的寄存器非空,且该寄存器中存的变量之后还要被用到,则需要ST一下寄存器中的变量
                printf("ST R%d, %c\n", id, r[id]);
                r[id] = '\0'; // ST完后这个寄存器就空了,一定要清空!!
            }
            printf("LD R%d, %c\n", id, s[i][3]); // LD左操作数
        }
        char op = s[i][4]; // 输出op
        if(op == '+') printf("ADD ");
        else if(op == '-') printf("SUB ");
        else if(op == '*') printf("MUL ");
        else printf("DIV ");
        printf("R%d, ", id); //输出左操作数所在寄存器
        int id2 = find(r, r + cnt, s[i][5]) - r;//寻找当前寄存器中是否有右操作数
        if(id2 == cnt) printf("%c\n", s[i][5]); //不存在则直接输出右操作数,表示读取内存
        else printf("R%d\n", id2); // 否则直接从寄存器中取右操作数,输出所在的寄存器
        r[id] = s[i][0]; //最后将结果存在左操作数所在的寄存器中
    }
    return 0;
}

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇