词法分析程序之正规式转换成NFA

前言

编译原理课里面书本有一个作业——使用C++实现:

  • 将正规式变成NFA
  • NFA确定化(变成DFA)
  • DFA最小化(化简)

自己学习过程中,深刻知道自己动手写东西是多么的重要。所以,我开始从0开始写这个作业。

每个自学的人都要明白自己和科班的人差距在哪里——基础!基础!所以一定要花时间去补回来。

正规式变成NFA

前备知识

在动手之前,一定学会分析要怎么做怎么做,有哪几个类,不要上去就是一段代码——参考某大佬的话。
(1)第一步,我们需要分析程序的流程是什么——画一个简单的思维导图:

在这里插入图片描述
NFA——是一个五元组:M = ( K , ∑ , f , S , Z ) ,∑有穷字母表,即是正规式的字母表,而其他的像K 状态集,f 转移函数,S 非空初态集,Z 终态集。

接下来我们可以使用Thompson方法实现正规式转NFA。
(2)NFA如何表示——需要什么的数据结构?
我们知道MFA是有节点信息、带信息的边的有向图。所以我们可以设计这样的数据结构

  • 节点

    struct NodeState{
    	string StateName;//节点状态
    };
    
  • 边:(有向边)

    struct Edge{
        NodeState startNode;//开始节点状态
    	NodeState endNOde;//终止节点状态
    	char TransSymbol;//边上的转化符号_空转化符我们使用#号表示
    };
    
  • NFA单元——一个大的NFA可以有NFA单元组成。

    struct NFACeil{
    		NodeState startNode;//开始节点状态
    		NodeState endNOde;//终止节点状态
    		int EdgeCount;//边数
    		Edge Edgeset[MAX];//该NFA拥有的边数
    };
    

(3)了解Thompson方法原理(参考编译原理书本即可)
将输入的r分解为最基本的子表达式,然后利用3条规则构造NFA。不断重复,直到输入的r的所有子表达式都得到了NFA。规则1、2和3 是NFA构建的基本单元,含有两个状态以及状态转换的边。

上面的过程用程序来实现就是传入一个符号,得到一个NFA单元——NFACell init(char element)

之后使用下面规则合并得到的NFA单元:

  • s|t——NFACell do_aORb(NFACell a,NFACell b)
  • st——NFACell do_aANDb(NFACell a,NFACell b)
  • s*——NFACell do_aStar(NFACell a)
    图解:
    在这里插入图片描述
    在这里插入图片描述
    上面这些过程我们需要具体程序来实现。

中缀表达式和后缀表达式(细节处理)

我们需要把中缀表达式转后缀表达式,以便处理符号优先级和去除括号。进而简化我们的代码书写。而在这个过程中很重要的一点是把经过检查后得到的有效正规式(中缀表达式)做一个预处理:

  • 加入+号,将省略的连接符显式加进去,表明这两个正规式的关系,如ab变成a+b,ab变成a+b,说明该正规式由a*和b组成。要加入加号的情况有4种:
/* 给中缀表达式添加 +号 */
string joinAddSymbol(string regxValid){
    //------------------需要添加+号的情况--------------------//
    // ab -》 a+b ----ab                                   //
    //ab*->a+b*------ab 情况1                              //
    //                                                     //
    // a(b|c)-》a+(b|c)--a(  情况2                          //
    // (a|b)c-》(a|b)+c--)b      情况3                      //
    //                                                     //
    // a*b-》 a* + b----*b           情况4                  //
    //-----------------------------------------------------//

    if(regxValid.length()<2) return regxValid;//边界处理
    int len = regxValid.length();
    int newLen = len+len+2;//添加+号之后的长度至多为该len
    char *return_string = new char[newLen];//最多是两倍
    int index=0;//记录返回字符串的长度
    char pre,now;//两个字符来判断,pre,now必然会被初始化一次
    for(int i=0;i+1<len;i++){//保证了有两个字符
        pre = regxValid[i];
        now = regxValid[i+1];
        return_string[index++] = pre;//都是在第一个字符后面才添加的+号
        //处理情况1
        if(isLetter(pre) && isLetter(now)){
            return_string[index++] = '+';//插入+号
        }else if( isLetter(pre) && now=='('){
            //情况2 a(
            return_string[index++] = '+';//插入+号
        }else if(pre==')' && isLetter(now)){
            //情况3 )b
            return_string[index++] = '+';//插入+号
        }else if(pre=='*' && isLetter(now)){
            //情况4 *b
            return_string[index++] = '+';//插入+号
        }
        //上面的某些分支可以合起来写,但是为了逻辑清晰则分开写。

    }
    //最后写入now符号
    return_string[index++] = now;
    return_string[index++] = '\0';//添加结束符号
    string newRegex(return_string);//创建为string
    cout<<"中缀表达式添加+号:"<<newRegex<<endl;
    return newRegex;
}

对中缀表达式进行了预处理以后,我们还需要处理表达式中涉及符号的优先级:
关于运算符的优先级设计参考:https://blog.csdn.net/gongsai20141004277/article/details/106607743

最后我们编写中缀表达式转后缀表达式的程序——string utils_postFix (string e)

输入正规式和转换NFA

输入正规式,我们要检查其合法性,我们编写一个工具类来做即可:

#include "utils.h"

/*输入正则式 */
string input(){
    cout<<"please input one regexExperssion:( valid character included  () * | a~z A~Z ) "<<endl;
    string regex;
    cin>>regex;
    if(isValid(regex)==1){
        cout<<"input is valid"<<endl;
        return regex;
    }else {
        cout<<"no a valid regexExperssion,please check your input!"<<endl;
        throw "error";
    }
}
/*
 * 判断输入字符e是否为字母
*/
bool isLetter(char e){
    if((e>='a' && e<='z') || (e>='A' && e<='Z')){
        return true;
    }
    return false;
}
/*
 * 是否为合法字符
*/
bool isCharacter(string regex){
    int n = regex.length();
    bool res = true;
    for(int i=0;i<n;i++){
        if(!((regex[i]=='(' || regex[i]==')' || regex[i]=='|' || regex[i]=='*')
            || isLetter(regex[i]))){
            res = false;
            break;
        }
    }
    return res;
}
/*
 * 判断输入的正则式是否含有括号,其括号是否匹配
*/
bool isMatchParenthesis(string checkregx){
    stack<char> s;
    int n = checkregx.length();
    int i=0;
    while(!s.empty() && i<n){
        if(checkregx[i]=='('){
            s.push(checkregx[i]);
        }
        if(checkregx[i]==')'){
            if(s.empty()){
                cerr << "有多余的右括号" << endl;
                return false;
            }else s.pop();//s只允许( 入栈
        }
        i++;
    }
    if(!s.empty()){
        cerr << "有多余的左括号" << endl;
        return false;
    }
    return true;

}
/*
 * 判断输入的正则式是否有效
*/
bool isValid(string regex){
    bool res1 = isMatchParenthesis(regex);//括号是否匹配
    bool res2 = isCharacter(regex);//是否为合法字符
    return res1 && res2;
}

转化NFA:

/**表达式转NFA处理函数,返回最终的NFA结合
 * 利用三条规则
*/
NFACell regex2NFA(string expression){
    int length = expression.size();
    char element;
    NFACell Cell, Left, Right;
    stack<NFACell> STACK;
    for (int i = 0; i<length; i++)
    {
        element = expression.at(i);
        switch (element)
        {
        case '|'://或
            Right = STACK.top();
            STACK.pop();
            Left = STACK.top();
            STACK.pop();
            Cell = do_aORb(Left, Right);
          //  displayNFA(Cell);
           //       cout<<Cell.StartState.StateName<<Cell.EndState.StateName<<endl;
            STACK.push(Cell);
            break;
        case '*'://闭包
            Left = STACK.top();
            STACK.pop();
            cout<<Left.StartState.StateName<<Left.EndState.StateName<<endl;
            Cell = do_aStar(Left);
           // displayNFA(Cell);
            //  cout<<Cell.StartState.StateName<<Cell.EndState.StateName<<endl;
            STACK.push(Cell);
            break;
        case '+'://链接
            Right = STACK.top();
            STACK.pop();
            Left = STACK.top();
            STACK.pop();
            Cell = do_aANDb(Left, Right);
              //    cout<<Cell.StartState.StateName<<Cell.EndState.StateName<<endl;
            STACK.push(Cell);
            break;
        default:
            Cell = init(element);//初始化
           // cout<<Cell.StartState.StateName<<Cell.EndState.StateName<<endl;
            //displayNFA(Cell);

            STACK.push(Cell);
        }
    }
    cout << "处理完毕!" << endl;
    Cell = STACK.top();
    STACK.pop();

    return Cell;
}

展示NFA:

void displayNFA(NFACell nfa){
    cout << "NFA 的边数:" << nfa.EdgeCount << endl;
    cout << "NFA 的起始状态:" << nfa.StartState.StateName << endl;
    cout << "NFA 的结束状态:" << nfa.EndState.StateName << endl;
    for (int i = 0; i<nfa.EdgeCount; i++)
    {
        cout << "第" << i + 1 << "条边的起始状态:" << nfa.EdgeSet[i].StartState.StateName
             << "  结束状态:" << nfa.EdgeSet[i].EndState.StateName
             << "  转换符:" << nfa.EdgeSet[i].TransSymbol << endl;
    }
    cout << "结束" << endl;
}

测试结果

输入ab:
在这里插入图片描述

输入ab*:
在这里插入图片描述

输入ab*|b:
在这里插入图片描述

输入abcb:
在这里插入图片描述
使用DFA程序来跑:
在这里插入图片描述

完整代码

  • 8
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨夜※繁华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值