编译原理 词法分析 Java代码实现

词法分析代码步骤

借助GraphViz

生成NFA,DFA,miniDFA等借助GraphViz工具
环境配置详情参见https://blog.csdn.net/qq_18208435/article/details/70464654

生成逆波兰表达式

对正则表达式添加操作符

如a(a|b)*b应该改为a·(a|b)*·b

给定输入字符串st,添加操作符,代码如下:

//假定字符串输入为小写字母。
for(int i = 0; i < st.length(); i++) {
   
	if(i-1>=0) {
   
        char t0 = st.charAt(i-1);
		char t1 = st.charAt(i);
        if((t0 >= 'a' && t0 <= 'z') && (t1 >= 'a' && t1 <= 'z')) {
   //ab->a·b
            st.insert(i, "·");
        }
        else if(t0 == ')' && (t1 >= 'a' && t1 <= 'z')) {
   //)a->)·a
            st.insert(i, "·");
        }
        else if((t0 >= 'a' && t0 <= 'z') && t1=='(') {
   //a(->a·(
            st.insert(i, "·");
        }
        else if(t0 == '*' && (t1 >= 'a' && t1 <= 'z')) {
   //*a->*·a
            st.insert(i,"·");
        }
        else if(t0 == '*' && t1 == '('){
   //*(->*·(
            st.insert(i,"·");
        }
    }
}

生成逆波兰表达式

采用调度场算法

  • 顺序扫描表达式,如果当前字符是字母(优先级为0的符号) ,则直接输出;如果当前字符为运算符或者括号(优先级不为 0的符号),则判断:
    • 若当前运算符为’(’ ,直接入栈;
    • 若为’)’ ,出栈并顺序输出运算符直到遇到第一个’(’ ,遇到的第一个 '('出栈但不输出;
    • 若为其它,比较运算符栈栈顶元素与当前元素的优先级:
      • 如果栈顶元素是’(’,当前元素直接入栈
      • 如果栈顶元素优先级>=当前元素优先级,出栈并顺序输出运算符直到 栈顶元素优先级<当前元素优先级,然后当前元素入栈
      • 如果栈顶元素优先级<当前元素优先级,当前元素直接入栈
  • 重复上述操作直至表达式扫描完毕
  • 顺序出栈并输出运算符直到栈元素为空

代码实现:

StringBuilder ans = new StringBuilder();
//char[] Table= {'*','·','|'};
Map<Character,Integer> map = new HashMap<Character,Integer>();//数字表示优先级
map.put('|', 1);
map.put('·', 2);
map.put('*', 3);
Stack<Character> stack = new Stack<>();
for(int i = 0; i < st.length(); i++) {
   
    char t = st.charAt(i);
    if(t >= 'a' && t <= 'z') {
   
        ans.append(t);
    }
    else if(t == '(' || stack.empty()) {
   
        stack.push(t);
    }
    else if(t == ')') {
   
        char top = stack.pop();
        while(top!='(') {
   
            ans.append(top);
            top = stack.pop();
        }
    }
    else {
   
        char top = stack.pop();
        if(top == '(') {
   
            stack.push(top);
            stack.push(t);
        }
        else {
   
            if(map.get(top) >= map.get(t)){
   
                while(map.get(top) >= map.get(t)) {
   
                    ans.append(top);
                    if(!stack.empty()) {
   
                        top = stack.pop();
                        if(top == '(') {
   
                            stack.push(top);
                            stack.push(t);
                            break;
                        }
                    }
                    else {
   
                        stack.push(t);
                        break;
                    }
                }
            }
            else {
   
                stack.push(top);
                stack.push(t);
            }
        }
    }
}
while(!stack.empty()) {
   
    ans.append(stack.pop());
}

生成NFA

采用存储边的思想,利用如下数据结构将每一条边存储:

public class Part{
   
    public int s;//存储一条边的start
    public int e;//存储一条边的end
    public char op;//存储一条边上的op
    public Part(int s,int e,char op) {
   
        this.s = s;
        this.e = e;
        this.op = op;
    }
}

通过处理逆波兰表达式得到最终的NFA,处理流程如下:

对你逆波兰表达式扫描

  • 如果当前字符为字母,则增加一条新的边Part(num,num+1,op),并将该部分两端的节点(num,num+1)入栈

  • 如果当前字符为’ · ',则从Stack中取出两条边,p1,p2。增加三条边:

    new Part(p1.e,p2.s,'ε')
    new Part(num,p1.s,'ε')
    new Part(p2.e,++num,'ε')
    

    并将该部分两端的节点(num-1,num)入栈

  • 如果当前字符为’ | ',则从Stack中取出两条边,p1,p2。增加四条边:

    v.add(new Part(num,p1.s,'ε'));
    v.add(new Part(num,p2.s,'ε'));
    ++num;
    v.add(new Part(p1.e,num,'ε'));		
    v.add(new Part(p2.e,num,'ε'));
    

    并将改部分两端的节点(num-1,num)入栈

  • 如果当前字符为’ * ',则从Stack中取出一条边,p。增加四条边:

    v.add(new Part(p.e,p.s,'ε'));
    v.add(new Part(num,p.s,'ε'));
    ++num;
    v.add(new Part(num-1,num,'ε'));
    v.add(new Part(p.e,num,'ε'));
    

    并将该部分两端的节点(num-1,num)入栈

注:完成后stack中栈底的边p,p.s为整个NFA的start,p.e为整个NFA的end(终态)。

while(!stack.isEmpty()) {
   
    p = stack.pop();
}
start = p.s;
end = p.e;

生成DFA

这部分比较思路上比较简单,主要是做一下几个方面的工作

  • 求ε闭包(状态合并)

  • 状态合并后标记终态(含NFA终态节点的DFA节点都是终态,可能不止一个)

    注:含NFA初态节点的DFA节点为初态,只有一个

  • 将合并后的节点重新编号,重新将DFA的边记录

代码实现思路为:

  1. 求NFA start节点的epsilon闭包
  2. 求闭包内节点通过不同操作符到达的节点的epsilon闭包,如果该节点是一个新的节点,则重复2操作直至没有新的节点产生

以下为关键代码实现:

//求start节点的epsilon闭包
public void GetIEpsilon(TreeSet<Integer> s,int start){
   
    vis[start] = 1;
    s.add(start);
    for (Part part : v) {
   
        if (part.s == start && part.op == 'ε') {
   
            //s.add(v.get(i).e);
            if (vis[part.e] == 0)
                GetIEpsilon(s, part.e);
        }
    }
}
//求经过op操作符为a所到达的点的epsilon闭包,存入一个TreeSet
public void GetI(TreeSet<Integer> s,int start,char a){
   
    for (Part part : v) {
   
        if (part.s == start && part.op == a) {
   
            s.add(part.e);
            Arrays.fill(vis, 0);
            GetIEpsilon(s, part.e);
        }
    }
}

//将含NFA的start和end节点标记出来,PointSet[i]为DFA的节点,集合内容为NFA的节点编号。
public void GetSE(){
   
    System.out.println("start:"+ TurnNFA.start);
    System.out.println("end:"+ TurnNFA.end);
    for(int i = 0; i < setNum; i++){
   
        if(pointSet[i].contains(TurnNFA.start)){
   
            start.add(i+1);//只有一个
        }
        if(pointSet[i].contains(TurnNFA.end)){
   
            end.add(i+1);
        }
    }
    Iterator
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值