编译原理-First集和Follow集以及预测分析算法实现(java)

本篇文章内的源码: 这里

一. 概念

1.1 串首终结符集

定义: 给定一个文法符号串αα串首终结符集FIRST(α)被定义为可以从α推导出的所有串首终结符构成的集合。

串首终结符意思就是符号串的首字符是终结符,所以由 α 推导出的所有首字母是终结符的文法符号串,这些终结符首字母组成的集合就是FIRST(α)

定义已经了解清楚了,那么该如何求呢?
例如一个文法符号串 BCDe, 其中 B C D 都是非终结符,e 是终结符。

仔细思考一下,你会发现:

  • 要想求文法符号串 BCDe 推导出多少个串首终结符,要先求非终结符B 能够推导出多少个串首终结符,这个由 B 产生式组来求解。
  • 如果 B 能够推导出空串ε,即(B =>+ ε),那么代表 B 是可以消除的,文法符号串 BCDe相当于文法符号串CDe,所以也要求非终结符C 能够推导出多少个串首终结符。

因此对于一个文法符号串 X1X2 … Xn,求解串首终结符集 FIRST(X1X2 … Xn)算法:

遍历这个文法符号串,然后判断当前文法符号串中的字符 Xi (i属于1n)。

  • 如果 Xi 是终结符,那么它的串首终结符集FIRST(Xi)中就只有它自己,将FIRST(Xi)加入FIRST(X1X2 … Xn) 中;因为FIRST(Xi)也不可能包含空串 ε,不用再向下遍历,循环到此为止,得到最终的串首终结符集FIRST(X1X2 … Xn)
  • 如果 Xi 是非终结符,那么就将它的串首终结符集FIRST(Xi)加入FIRST(X1X2 … Xn) 中。
    • 如果FIRST(Xi)包含空串ε,那么再向下遍历一个字符;但是如果 Xi 已经是最后一个字符了,那就说明整个文法符号串 X1X2 … Xn可以推导出空串ε,因此将空串ε加到FIRST(X1X2 … Xn) 中。
    • 如果FIRST(Xi)不包含空串ε,不用再向下遍历,循环到此为止,得到最终的串首终结符集FIRST(X1X2 … Xn)

但是这里有一个关键点,如何求非终结符的串首终结符集?

如求非终结符A 的串首终结符集FIRST(A),其实就是看这个非终结符A 能够推导的所有首字符是终结符的文法符号串,那么就是看这个非终结符A 的产生式组。

因此对于一个非终结符 A, 求解串首终结符集FIRST(A)算法:

遍历非终结符A的所有产生式:

  • A→aβ : a 是终结符,那么这个产生式推导的所有文法符号串,它们的首字符只能是终结符a,因此将这个a添加到FIRST(A)中。
  • A→ε : 非终结符A 能够推导出空串ε,那么将这个空串 ε 添加到FIRST(A)中。
  • A→Bβ : 产生式右部首字符是非终结符,那么就将 FIRST(Bβ) 添加到 FIRST(A)中。

这里大家可能有个疑惑,怎么能将 FIRST(Bβ) 添加到 FIRST(A)中,如果问文法符号串 中包含非终结符 A ,就产生了循环调用的情况,该怎么办?

如果非终结符 A 的产生式组中,有包含A 的产生式,那么在计算 FIRST(A) 时候:

  • 先计算不包含 A 的其他产生式,得到一个 FIRST(A)
  • 然后计算包含 A 的产生式,遇到 A 的时候跳过,判断之前的FIRST(A)包不包含空串 ε,来决定是否将产生式 A 后面的文法符号串的串首终结符集加入 FIRST(A) 中。

对于串首终结符集,我想大家疑惑的点就是,串首终结符集到底是针对文法符号串的,还是针对非终结符的,这个容易弄混。
其实我们应该知道,非终结符本身就属于一个特殊的文法符号串
而求解文法符号串的串首终结符集,其实就是要知道文法符号串中每个字符的串首终结符集:

  • 对于终结符,它的串首终结符集就是它自己。
  • 对于非终结符,它的串首终结符集是要通过它对应的产生式组计算得来的。
  • 再判断当前字符对应的串首终结符集包不包含空串,来决定要不要添加文法符号串中下一个字符的串首终结符集。

1.2 后继符号集

定义: 对于任一非终结符A,它的后继符号集就是由文法G推导出来的所有句型,可以出现在非终结符A后边的终结符的集合,记为FOLLOW(A)

仔细想一下,什么样的终结符可以出现在非终结符A后面,应该是在产生式中就位于A后面的终结符。例如 S -> Aa,那么终结符 a 肯定属于FOLLOW(A)

因此求非终结符A后继符号集算法:

遍历文法所有的产生式,判断产生式右部中是否包含非终结符A:

  • S -> αAβ : 包含非终结符A,其中 αβ 都属于文法符号串,那么就将文法符号串β 的串首终结符集FIRST(β) 中除了空串ε外的所有终结符添加到FOLLOW(A)。如果FIRST(β)存在空串ε,那么就需要将FOLLOW(S) 也添加到FOLLOW(A)中。
    注意:这里的 β 是文法符号串,不要把它当成一个特定的非终结符。
  • S -> αA : 包含非终结符A, 其中 α 属于文法符号串,那么将FOLLOW(S) 添加到FOLLOW(A)中。
    如果非终结符 A 是产生式结尾,那么说明这个产生式左部非终结符后面能出现的终结符,也都可以出现在非终结符 A后面。
  • 刚开始的时候,需要将结束符号 $ 添加到文法开始符号S 的后继符号集FOLLOW(S) 中。
  • 后继符号集中是不会包含空串ε的。

1.3 可选集

我们可以求出LL(1)文法中每个产生式可选集:

  • A→aβ : a 是终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→aβ) 就是这个终结符,即{a}
  • A→ε : 空产生式对应的可选集SELECT(A→ε) 就是A的后继符号集,即 FOLLOW(A)
  • A→Bβ : B 是非终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→Bβ) 就是文法符号串的串首终结符集,即FIRST(Bβ)

    注意,如果FIRST(Bβ)包含空串ε,即文法符号串能推导出空串ε,那么还要将A的后继符号集添加到产生式对应的可选集中。

1.4 预测分析表

根据产生式可选集,我们可以构建一个预测分析表,表中的每一行都是一个非终结符,表中的每一列都是一个终结符,包括结束符号 $,而表中的值就是产生式。
这样进行语法推导的时候,非终结符遇到当前输入字符,就可以从预测分析表中获取对应的产生式了。

二. 代码实现

2.1 相关类

2.1.1 Symbol

/**
 * @author by xinhao  2021/8/13
 * 表示文法中字母表中一个符号,包括终结符和非终结符
 */
public class Symbol implements Comparable {
   public static final String  EPSILON = "ε";
    // 文法结束符号
    public static final Symbol END = new Symbol("$", true);
    // 表示符号的值, 这里用 String 表示,而不是 char,
    // 是因为有些符号我们不好用一个 char 表示。 比如 A 对应的 A'
    private final String label;

    private final boolean isTerminator;
    ...
}

表示字母表中一个符号, label表示符号值, isTerminator 表示终结符或非终结符。

2.1.2 Alphabet

/**
 * @author by xinhao  2021/8/13
 * 字母表, 这个类不用实例化。用来记录字母表中所有的符号
 */
public abstract class Alphabet {

    /**
     * 字母表中所有的符号
     */
    public static final Map ALL_SYMBOL = new HashMap<>();

    public static Symbol addSymbol(String label, boolean isTerminator) {
        Symbol symbol = new Symbol(label, isTerminator);
        ALL_SYMBOL.put(label, symbol);
        return symbol;
    }
    public static Symbol getSymbol(String label) {
        return ALL_SYMBOL.get(label);
    }
}

字母表,用来记录字母表中所有的符号。

2.1.3 Production

public class Production {
    public static final List EMPTY = new ArrayList<>(0);
    /** 产生式左边,而且必须是非终结符 */
    private final Symbol left;

    /** 这里的 List 希望是不可变的,你们可以自己引入 ImmutableList */
    private final List right;
...
}

表示一个产生式 A -> αβ , left 产生式左边,而且必须是非终结符,right 产生式右部符号集合。

2.1.4 Grammar

public class Grammar {

    // 开始符号
    private final Symbol start;
    /**
     * 这里为什么用 map 类型?
     * S -> aA | bB
     * A -> aaaa
     * A -> abab | ε
     * B -> bbb
     * B -> bababa
     * B -> ε
     * 所以对于同一个左部 Symbol (A) 其实是有多个产生式的,就是产生式租
     * 使用 LinkedHashMap ,因为产生式是要有记录顺序的
     */
    private final LinkedHashMap> productions;
   ...
}

表示一个文法,包含文法开始符号和产生式组,终结符号集合非终结符号集没有包含在这个里面。

2.2 求串首终结符集

2.2.1 FirstSet

package xinhao.ll;

import xinhao.Symbol;

import java.util.HashSet;
import java.util.Set;

/**
 * @author by xinhao  2021/8/14
 * 单个符号的串首终结符集
 */
public class FirstSet {
    /**
     * 这个串首终结符集对应的单个符号,一般都是非终结符。
     * 终结符的串首终结符集就是它自己。
     */
    private final Symbol key;

    /**
     * 这个串首终结符集是否包含 空串
     * 空串ε 不加入到集合中,用hasEpsilon表示这个串首终结符集中有没有空串。
     */
    private boolean hasEpsilon;

    /**
     * 这个字符 key 对应的串首终结符集合
     * 注意这里不包含 ε,ε 我们使用hasEpsilon, 表示是否包含空串
     */
    private Set set = new HashSet<>();

    public FirstSet(Symbol key) {
        this.key = key;
    }

    public void addToSet(Symbol symbol) {
        set.add(symbol);
    }

    public void addAllToSet(Set symbols) {
        set.addAll(symbols);
    }

    public Symbol getKey() {
        return key;
    }

    public boolean isHasEpsilon() {
        return hasEpsilon;
    }

    public void setHasEpsilon(boolean hasEpsilon) {
        this.hasEpsilon = hasEpsilon;
    }

    public Set getSet() {
        return set;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("FIRST(").append(key).append(") = {");
        set.forEach(symbol -> sb.append(symbol).append("  "));
        if (hasEpsilon) sb.append("ε  ");
        sb.append("}");

        return sb.toString();
    }
}

这个类表示单个字符对应的串首终结符集:

  • key : 这个串首终结符集对应的单个符号。
  • hasEpsilon : 这个串首终结符集是否包含 空串。
  • set : 这个字符 key 对应的串首终结符集合,注意里面不包含空串。

2.2.2 FirstsSet

/**
 * @author by xinhao  2021/8/14
 * 文法符号串对应串首终结符集
 */
public class FirstsSet {

    /**
     * 当前的文法符号串
     */
    private final List keys;

    /**
     * 这个串首终结符集是否包含 空串
     */
    private boolean hasEpsilon;

    /**
     * 字符 key 对应的串首的终结符集合
     * 注意这里不包含 ε,ε 我们使用hasEpsilon, 表示是否包含空串
     */
    private Set set = new HashSet<>();
    ......
}

这个和 FirstSet 几乎一模一样,只不过它代表一个文法符号串对应的串首终结符集。

2.2.3 获取所有非终结符的串首终结符集

public class Grammar {
     ....
    /**
     * 该文法所有非终结符对应的串首终结符集
     */
    private Map firstSetMap;

    /**
     * 获取所有非终结符对应的串首终结符集
     * @return
     */
    public Map getFirstSetMap() {
        // 如果有值直接返回,只求值一次
        if (firstSetMap != null) {
            return firstSetMap;
        }
        firstSetMap = new HashMap<>();
        // 遍历文法中所有非终结符,求解对应的串首终结符集
        productions.keySet().forEach(key -> getOrCreateFirstSetByKey(key));
        return firstSetMap;
    }

    /**
     * 通过递归的方式,来求非终结符的串首终结符集
     *
     * @param key
     * @return
     */
    private FirstSet getOrCreateFirstSetByKey(Symbol key) {
        // 说明之前已经求过了,直接返回
        if (firstSetMap.containsKey(key)) {
            return firstSetMap.get(key);
        }
        /**
         * 右部包含 key 非终结符的产生式列表。例如 A -> aAb
         * 因为产生式右部包含key,那么就不能递归调用 getOrCreateFirstSetByKey(key) 方法,这样会形成死循环。
         * 先计算不包含 `A` 的其他产生式,得到一个 firstSet,然后再求包含`A` containsKeyProductionList
         *
         */
        List containsKeyProductionList = new ArrayList<>();
        // 该符号key 对应的串首终结符集
        FirstSet firstSet = new FirstSet(key);
        for (Production production : productions.get(key)) {
            // 该产生式是空串ε
            if (production.isEpsilon()) {
                firstSet.setHasEpsilon(true);
            } else {
                List rightSymbols = production.getRight();
                /**
                 * 如果这个产生式右部包含这个非终结符 key,那么需要特殊处理,
                 * 加入到containsKeyProductionList中。
                 */
                if (rightSymbols.contains(key)) {
                    containsKeyProductionList.add(production);
                    continue;
                }
                Iterator rightIterator = rightSymbols.iterator();
                /**
                 * isAllHasEpsilon 变量表示这个产生式都是非终结符,且这些非终结符的 First集都包含空串ε
                 * 那么此 First(key) 也应该包含空串ε
                 * 这里 isAllHasEpsilon 初始值是 true,因为 production.getRight() 不可能是空集合
                 */
                boolean isAllHasEpsilon = true;
                while (rightIterator.hasNext()) {
                    Symbol currentSymbol = rightIterator.next();
                    // 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
                    if (currentSymbol.isTerminator()) {
                        firstSet.addToSet(currentSymbol);
                        isAllHasEpsilon = false;
                        // 跳出当前 while,不用再遍历这个产生式后面的字符了
                        break;
                    }
                    /**
                     * 如果是非终结符,递归调用 getOrCreateFirstSetByKey 获取这个非终结符对应的串首终结符集。
                     * 但是不允许有间接左递归,即 A -> B; B -> Aa
                     * 这样会出现死循环,就无法求解了。
                     */
                    FirstSet currentFirstSet = getOrCreateFirstSetByKey(currentSymbol);
                    firstSet.addAllToSet(currentFirstSet.getSet());
                    /**
                     * 如果当然 currentSymbol 对应的FirstSet (currentFirstSet) 包含空串,那么就需要考察下一个字符
                     * 例如 对于 S -> ABC 产生式, currentSymbol = A 且对应currentFirstSet包含空串,那么就需要将 B 的First集加入其中
                     * 如果当前字符 currentSymbol 的串首终结符集不包含空串,那么就不用再遍历这个产生式后面的字符了。
                     */
                    if (!currentFirstSet.isHasEpsilon()) {
                        isAllHasEpsilon = false;
                        break;
                    }
                }

                if (isAllHasEpsilon) {
                    firstSet.setHasEpsilon(true);
                }
            }
        }
        /**
         * 为什么分为两次遍历,因为 First(key) 是否包括空串ε,是由不包含key的其他产生式决定的。
         * 所以在遍历包含 key 的产生式的时候,就可以通过 First(key)是否包含空串ε,
         * 来决定是否添加产生式中 key 后面的字符
         */
        for (Production containsKeyProduction : containsKeyProductionList) {
            Iterator rightIterator = containsKeyProduction.getRight().iterator();
            while (rightIterator.hasNext()) {
                Symbol currentSymbol = rightIterator.next();
                // 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
                if (currentSymbol.isTerminator()) {
                    firstSet.addToSet(currentSymbol);
                    break;
                }
                if (currentSymbol.equals(key)) {
                    // 如果不包含空串ε,就直接跳出循环,不用遍历产生式下一个字符了
                    if (!firstSet.isHasEpsilon()) {
                        break;
                    }
                } else {
                    FirstSet currentFirstSet = getOrCreateFirstSetByKey(currentSymbol);
                    firstSet.addAllToSet(currentFirstSet.getSet());
                    if (!currentFirstSet.isHasEpsilon()) {
                        break;
                    }
                }
            }

        }
        firstSetMap.put(key, firstSet);
        return firstSet;
    }
}

其实求解过程很容易理解,就是利用递归的方式,获取非终结符的串首终结符集。

例如 : A -> a | Bb; B -> b | c
求解非终结符 AFIRST(A), 遍历A 的产生式组,到 FIRST(a){a}, FIRST(Bb) 就是递归调用 FIRST(B)

所以特别注意,这种求解算法要求文法一定不能存在直接左递归,否则就会出现循环调用的情况,如 A -> B; B -> Aa

2.2.4 文法符号串的串首终结符集

public abstract class GrammarUtils {
.......
   /**
     * 获取文法符号串对应的串首终结符集
     * 要传递所有非终结符对应的串首终结符集 firstSetMap
     * @param keySymbols
     * @param firstSetMap
     * @return
     */
    public static FirstsSet getFirstsSetByKeys(List keySymbols, Map firstSetMap) {
        // 文法符号串的串首终结符集
        FirstsSet firstsSet = new FirstsSet(keySymbols);
        Iterator rightIterator = keySymbols.iterator();
        /**
         * isAllHasEpsilon 变量表示这个产生式都是非终结符,且这些非终结符的 First集都包含空串ε
         * 那么此 First(key) 也应该包含空串ε
         * 这里 isAllHasEpsilon 初始值是 true,因为 keySymbols 不可能是空集合
         */
        boolean isAllHasEpsilon = true;
        while (rightIterator.hasNext()) {
            Symbol currentSymbol = rightIterator.next();
            // 如果是终结符,那么就不用继续遍历,直接将这个 currentSymbol 添加到 firstSet 中
            if (currentSymbol.isTerminator()) {
                firstsSet.addToSet(currentSymbol);
                isAllHasEpsilon = false;
                break;
            }
            // 如果是非终结符,那么需要判断
            FirstSet currentFirstSet = firstSetMap.get(currentSymbol);
            firstsSet.addAllToSet(currentFirstSet.getSet());
            /**
             * 如果当然 currentSymbol 对应的FirstSet (currentFirstSet) 包含空串,那么就需要考察下一个字符
             * 例如 对于 S -> ABC 产生式, currentSymbol = A 且对应currentFirstSet包含空串,那么就需要将 B 的First集加入其中
             */
            if (!currentFirstSet.isHasEpsilon()) {
                isAllHasEpsilon = false;
                break;
            }
        }

        if (isAllHasEpsilon) {
            firstsSet.setHasEpsilon(true);
        }
        return firstsSet;
    }
}

这个就比较简单了,因为先获取了所有非终结符对应的串首终结符集 firstSetMap, 遍历文法符号串所有的符号,计算它的串首终结符集 。

2.3 求后继符号集

2.3.1 FollowSet

package xinhao.ll;

import xinhao.Symbol;

import java.util.HashSet;
import java.util.Set;

/**
 * @author by xinhao  2021/8/15
 * Follow集
 * 对于任一非终结符A,它的后继符号集就是由文法G推导出来的所有句型,可以出现在非终结符A后边的终结符的集合,记为FOLLOW(A)。
 * 
 * 将$放入FOLLOW(S)中,其中S是开始符号,$是输入右端的结束标记;
 * 如果存在一个产生式A→αBβ,那么FIRST(β)中除ε之外的所有符号都在FOLLOW(B)中;
 * 如果存在一个产生式A→αB,或存在产生式A→αBβ且FIRST(β) 包含ε,那么FOLLOW(A)中的所有符号都在FOLLOW(B)中。
 */
public class FollowSet {

    /**
     * 当前 Follow集对应的字母, 必须是非终结符;
     * 终结符不可能有Follow集
     */
    private final Symbol key;

    /**
     * 非终结符 key 对应的Follow集
     */
    private Set set = new HashSet<>();

    public FollowSet(Symbol key) {
        this.key = key;
    }

    public void addToSet(Symbol symbol) {
        set.add(symbol);
    }

    public void addAllToSet(Set symbols) {
        set.addAll(symbols);
    }

    public Symbol getKey() {
        return key;
    }

    public Set getSet() {
        return set;
    }

    public void setSet(Set set) {
        this.set = set;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Follow(");
        sb.append(key).append(") = {");
        set.forEach(symbol -> sb.append(symbol).append("  "));
        sb.append('}');
        return sb.toString();
    }
}

2.3.2 获取所有非终结符的后继符号集

public class Grammar {
     ....

    /**
     * 该文法对应的 Follow 集
     */
    private Map followSetMap;

        /**
     * 取所有非终结符对应的后继符号集
     * @return
     */
    public  Map getFollowSetMap() {
        if (followSetMap != null) {
            return followSetMap;
        }
        followSetMap = new HashMap<>();
        // 遍历文法中所有非终结符,求解对应的后继符号集
        productions.keySet().forEach(key -> getFollowSetByKey(key));
        return followSetMap;
    }
}

    /**
     * 通过递归的方式,来求非终结符的后继符号集
     * @param key
     * @return
     */
    private FollowSet getOrCreateFollowSetByKey(Symbol key) {
        // 说明之前已经求过了,直接返回
        if (followSetMap.containsKey(key)) {
            return followSetMap.get(key);
        }
        // 该符号key 对应的后继符号集
        FollowSet followSet = new FollowSet(key);
        // 如果是文法开始符号, 要将语法终结符号放入它的 Follow 集中
        if (start.equals(key)) {
            followSet.addToSet(Symbol.END);
        }

        for (Map.Entry> entry : productions.entrySet())  {
            for (Production production : entry.getValue()) {
                /**
                 * 如果此产生式是空串,产生式右部不会包含字符key,遍历下一个产生式
                 * 空串是加入 First 集中,间接对 Follow集产生影响
                 */
                if (production.isEpsilon()) {
                    continue;
                }

                List rightSymbols = production.getRight();
                /**
                 * 判断我们要得到Follow集的 非终结符 key 在不在当前这个产生式中。
                 * 因为要得到非终结符 key 的Follow集,就要看这个产生式中此非终结符 key 后面字符的情况
                 */
                int pos = rightSymbols.indexOf(key);
                // pos == -1, 说明没有找到,那么就从下一个产生式Production寻找
                if (pos == -1) {
                    continue;
                }
                /**
                 * 如果pos == rightSymbols.size() - 1,
                 * 说明非终结符 key就是当前产生式最后一个字符,
                 * 那么就需要将该产生式左部的Follow集加入到此key 的Follow集中。
                 * 即 A -> aB, 因为B 是产生式最后一个非终结符,那么就要将 Follow(A) 加入到 Follow(B) 中
                 */
                if (pos == rightSymbols.size() - 1) {
                    // A -> aA 这种情况下,不需要做其他操作
                    if (key.equals(production.getLeft())) {
                        System.out.println("key:"+key+"   production.getLeft():"+production.getLeft());
                    } else {
                        // 将产生式左部的后继符号集添加到当前符号 key 的后继符号集中
                        // 对于 S -> aA, 要将 Follow(S) 添加到 Follow(A) 中
                        followSet.addAllToSet(getOrCreateFollowSetByKey(production.getLeft()).getSet());
                    }
                    continue;
                }

                // 当前符号 key 之后的文法符合串
                List remainSymbols = rightSymbols.subList(pos + 1, rightSymbols.size());
                // 获取文法符合串对应的串首终结符集
                FirstsSet firstsSet = GrammarUtils.getFirstsSetByKeys(remainSymbols, firstSetMap);
                followSet.addAllToSet(firstsSet.getSet());
                if (firstsSet.isHasEpsilon()) {
                    if (key.equals(production.getLeft())) {
                        System.out.println("key:"+key+"   production.getLeft():"+production.getLeft());
                    } else {
                        // 将产生式左部的后继符号集添加到当前符号 key 的后继符号集中
                        followSet.addAllToSet(getOrCreateFollowSetByKey(production.getLeft()).getSet());
                    }
                }
            }
        }

        followSetMap.put(key, followSet);
        return followSet;
    }

与串首终结符集求解类似,都是通过递归的方式,获取所有非终结符的后继符号集。

2.4 求产生式的可选集

2.4.1 SelectSet

/**
 * @author by xinhao  2021/8/15
 * 可选集 Select集
 * A→aβ : a 是终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→aβ) 就是这个终结符,即{a}。
 * A→ε : 空产生式对应的可选集SELECT(A→ε) 就是A的后继符号集,即 FOLLOW(A)。
 * A→Bβ : B 是非终结符,β 是文法符号串,那么这个产生式的可选集SELECT(A→Bβ) 就是文法符号串Bβ的串首终结符集,即FIRST(Bβ)。
 * 注意,如果`FIRST(Bβ)`包含空串ε,即文法符号串`Bβ`能推导出空串ε,那么还要将`A`的后继符号集添加到产生式对应的可选集中。
 */
public class SelectSet {

    // 这个可选集对应产生式
    private final Production production;

    // 可选的符号集合
    private final Set set;


    public SelectSet(Production production, Set set) {
        this.production = production;
        this.set = set;
    }

    public Production getProduction() {
        return production;
    }

    public Set getSet() {
        return set;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("SelectSet{");
        sb.append("production=").append(production);
        sb.append(", set=").append(set);
        sb.append('}');
        return sb.toString();
    }
}

表示某个产生式对应的可选集。

2.4.2 求文法所有产生式对应的可选集

public abstract class GrammarUtils {
 ......
    /**
     * 求这个文法所有产生式对应的可选集List
     * @param grammar
     * @return
     */
    public static List getAllSelectSetByGrammar(Grammar grammar) {
        // 可选集列表
        List selectSetList = new ArrayList<>();

        // 当前文法的所有非终结符的后继符号集
        Map followSetMap = grammar.getFollowSetMap();
        for (Map.Entry> entry : grammar.getProductions().entrySet())  {
            // 遍历文法中的每个产生式
            for (Production production : entry.getValue()) {
                // 如果产生式是空产生式 A -> ε,那么 FOLLOW(A) 就是这个产生式的可选集
                if (production.isEpsilon()) {
                    FollowSet followSet = followSetMap.get(production.getLeft());
                    selectSetList.add(new SelectSet(production, followSet.getSet()));
                } else {
                    List rightSymbols = production.getRight();
                    // 产生式右部首字符是终结符,那么这个产生式可选集就是这个终结符
                    if (rightSymbols.get(0).isTerminator()) {
                        Set set = new HashSet<>();
                        set.add(rightSymbols.get(0));
                        selectSetList.add(new SelectSet(production, set));
                    }  else {
                        //
                        Set set = new HashSet<>();
                        FirstsSet firstsSet = getFirstsSetByKeys(rightSymbols, grammar.getFirstSetMap());
                        set.addAll(firstsSet.getSet());
                        // 产生式右部的文法符号串能推导出空串ε,那么还要将产生式左部的FOLLOW集添加到这个产生式的可选集中
                        if (firstsSet.isHasEpsilon()) {
                            FollowSet followSet = followSetMap.get(production.getLeft());
                            set.addAll(followSet.getSet());
                        }
                        selectSetList.add(new SelectSet(production, set));
                    }

                }
            }
        }

        return selectSetList;
    }
 ......
}

2.5 获取预测分析表

public abstract class GrammarUtils {
 ......
   /**
     * 将可选集转换成预测分析表
     * @param selectSetList
     * @return
     */
    public static Map> listToSelectTable(List selectSetList) {
        /**
         * 使用Map> 表示一个二维表结构
         * 每一行都是一个非终结符,表中的每一列都是一个终结符,值就是产生式
         */
        Map> table = new HashMap<>();
        for (SelectSet selectSet : selectSetList) {
            Production production = selectSet.getProduction();
            /**
             * 分析表中每一行都是非终结符,也是产生式的左部
             */
            Map map = table.get(production.getLeft());
            if (map == null) {
                map = new HashMap<>();
                table.put(production.getLeft(), map);
            }
            /**
             * 说明当前这个产生式 production, 是非终结符 production.getLeft(), 遇到产生式的可选集字符就能选择。
             */
            for (Symbol symbol : selectSet.getSet()) {
                map.put(symbol, production);
            }
        }

        return table;
    }
 ......
}

2.5 进行预测分析法

public abstract class GrammarUtils {
 ......
 public static List match(Symbol start, Map> table, List matchSymbols) {
        Stack stack = new Stack<>();
        stack.push(start);
        int pos = 0;
        List productions = new ArrayList<>();
        while (!stack.isEmpty()) {
            // 获取当前栈顶字符
            Symbol currentSymbol = stack.pop();
            // 获取待匹配输入字符串当前位置 pos 对应的待匹配字符
            Symbol matchSymbol = matchSymbols.get(pos);
            // 如果匹配成功说明,就让pos自增,匹配下一个字符
            if (currentSymbol.equals(matchSymbol)) {
                pos++;
            } else if (currentSymbol.isTerminator()){
                // 当前符号 currentSymbol 是终结符,还不能匹配成功,那么说明语法匹配错误
                throw new RuntimeException("当前符号:" + currentSymbol + "  待匹配符号:" + matchSymbol);
            } else {
                Map map = table.get(currentSymbol);
                if (map == null) {
                    throw new RuntimeException("当前符号:" + currentSymbol+" 对应的分析表");
                }
                Production production = map.get(matchSymbol);
                if (production == null) {
                    throw new RuntimeException("当前符号:" + currentSymbol+" 对应的分析表");
                }
                // 匹配成功的产生式
                productions.add(production);
                // 将产生式倒序存入栈中
                List right = production.getRight();
                for (int index = right.size() - 1; index >= 0; index--) {
                    stack.push(right.get(index));
                }
            }
        }

        return productions;
    }
 ......
}

2.6 例子

 public static void main(String[] args) {
        Symbol start = Alphabet.getSymbol('E');
        List productions = new ArrayList<>();

        productions.addAll(Production.createList(start, "E+T", "T"));
        productions.addAll(Production.createList(Alphabet.getSymbol('T'), "T*F", "F"));
        productions.addAll(Production.createList(Alphabet.getSymbol('F'), "(E)", "d"));

        Grammar grammar = Grammar.create(start, productions);

        Grammar deepGrammar = GrammarUtils.eliminateDeepLeftRecursion(grammar);
        Map firstSetMap = deepGrammar.getFirstSetMap();
        Map followSetMap = deepGrammar.getFollowSetMap();

        List selectSetList = GrammarUtils.getAllSelectSetByGrammar(deepGrammar);
        Map> table = GrammarUtils.listToSelectTable(selectSetList);


        List matchSymbols = new ArrayList<>();
        matchSymbols.add(Alphabet.getSymbol('d'));
        matchSymbols.add(Alphabet.getSymbol('+'));
        matchSymbols.add(Alphabet.getSymbol('d'));
        matchSymbols.add(Alphabet.getSymbol('*'));
        matchSymbols.add(Alphabet.getSymbol('d'));
        matchSymbols.add(Symbol.END);
        List matchProductionList = GrammarUtils.match(deepGrammar.getStart(), table, matchSymbols);


        System.out.println("原始的文法:");
        System.out.println(grammar);
        System.out.println();

        System.out.println("去除左递归之后的文法:");
        System.out.println(deepGrammar);
        System.out.println();

        System.out.println("文法first集和follow集:");
        deepGrammar.getProductions().keySet().forEach(symbol ->
                System.out.println(firstSetMap.get(symbol) +"\t\t" + followSetMap.get(symbol)));
        System.out.println();


        System.out.println("文法产生式的可选集:");
        selectSetList.forEach(selectSet -> System.out.println(selectSet));
        System.out.println();

        System.out.println("匹配 d+d*d 的产生组:");
        System.out.println(matchProductionList);
    }

输出结果:

原始的文法:
开始符号:E
产生式:
                E->E+T|T
                T->T*F|F
                F->(E)|d


去除左递归之后的文法:
开始符号:E
产生式:
                E->TE'
                E'->+TE'|ε
                T->FT'
                T'->*FT'|ε
                F->(E)|d


文法first集和follow集:
FIRST(E) = {d  (  }     Follow(E) = {$  )  }
FIRST(E') = {+  ε  }        Follow(E') = {$  )  }
FIRST(T) = {d  (  }     Follow(T) = {$  )  +  }
FIRST(T') = {*  ε  }        Follow(T') = {$  )  +  }
FIRST(F) = {d  (  }     Follow(F) = {$  )  *  +  }

文法产生式的可选集:
SelectSet{production=E->TE', set=[d, (]}
SelectSet{production=E'->+TE', set=[+]}
SelectSet{production=E'->ε, set=[$, )]}
SelectSet{production=T->FT', set=[d, (]}
SelectSet{production=T'->*FT', set=[*]}
SelectSet{production=T'->ε, set=[$, ), +]}
SelectSet{production=F->(E), set=[(]}
SelectSet{production=F->d, set=[d]}

匹配 d+d*d 的产生组:
[E->TE', T->FT', F->d, T'->ε, E'->+TE', T->FT', F->d, T'->*FT', F->d, T'->ε, E'->ε]

你可能感兴趣的:(编译原理-First集和Follow集以及预测分析算法实现(java))