美文网首页
编译原理-First集和Follow集以及预测分析算法实现(ja

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

作者: wo883721 | 来源:发表于2021-08-21 16:58 被阅读0次

    本篇文章内的源码: 这里

    一. 概念

    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<Symbol> {
       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<String, Symbol> 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<Symbol> EMPTY = new ArrayList<>(0);
        /** 产生式左边,而且必须是非终结符 */
        private final Symbol left;
    
        /** 这里的 List<Symbol> 希望是不可变的,你们可以自己引入 ImmutableList */
        private final List<Symbol> 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<Symbol, List<Production>> 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<Symbol> set = new HashSet<>();
    
        public FirstSet(Symbol key) {
            this.key = key;
        }
    
        public void addToSet(Symbol symbol) {
            set.add(symbol);
        }
    
        public void addAllToSet(Set<Symbol> symbols) {
            set.addAll(symbols);
        }
    
        public Symbol getKey() {
            return key;
        }
    
        public boolean isHasEpsilon() {
            return hasEpsilon;
        }
    
        public void setHasEpsilon(boolean hasEpsilon) {
            this.hasEpsilon = hasEpsilon;
        }
    
        public Set<Symbol> getSet() {
            return set;
        }
    
        public void setSet(Set<Symbol> 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<Symbol> keys;
    
        /**
         * 这个串首终结符集是否包含 空串
         */
        private boolean hasEpsilon;
    
        /**
         * 字符 key 对应的串首的终结符集合
         * 注意这里不包含 ε,ε 我们使用hasEpsilon, 表示是否包含空串
         */
        private Set<Symbol> set = new HashSet<>();
        ......
    }
    

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

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

    public class Grammar {
         ....
        /**
         * 该文法所有非终结符对应的串首终结符集
         */
        private Map<Symbol, FirstSet> firstSetMap;
    
        /**
         * 获取所有非终结符对应的串首终结符集
         * @return
         */
        public Map<Symbol, FirstSet> 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<Production> containsKeyProductionList = new ArrayList<>();
            // 该符号key 对应的串首终结符集
            FirstSet firstSet = new FirstSet(key);
            for (Production production : productions.get(key)) {
                // 该产生式是空串ε
                if (production.isEpsilon()) {
                    firstSet.setHasEpsilon(true);
                } else {
                    List<Symbol> rightSymbols = production.getRight();
                    /**
                     * 如果这个产生式右部包含这个非终结符 key,那么需要特殊处理,
                     * 加入到containsKeyProductionList中。
                     */
                    if (rightSymbols.contains(key)) {
                        containsKeyProductionList.add(production);
                        continue;
                    }
                    Iterator<Symbol> 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<Symbol> 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<Symbol> keySymbols, Map<Symbol, FirstSet> firstSetMap) {
            // 文法符号串的串首终结符集
            FirstsSet firstsSet = new FirstsSet(keySymbols);
            Iterator<Symbol> 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<Symbol> set = new HashSet<>();
    
        public FollowSet(Symbol key) {
            this.key = key;
        }
    
        public void addToSet(Symbol symbol) {
            set.add(symbol);
        }
    
        public void addAllToSet(Set<Symbol> symbols) {
            set.addAll(symbols);
        }
    
        public Symbol getKey() {
            return key;
        }
    
        public Set<Symbol> getSet() {
            return set;
        }
    
        public void setSet(Set<Symbol> 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<Symbol, FollowSet> followSetMap;
    
            /**
         * 取所有非终结符对应的后继符号集
         * @return
         */
        public  Map<Symbol, FollowSet> 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<Symbol, List<Production>> entry : productions.entrySet())  {
                for (Production production : entry.getValue()) {
                    /**
                     * 如果此产生式是空串,产生式右部不会包含字符key,遍历下一个产生式
                     * 空串是加入 First 集中,间接对 Follow集产生影响
                     */
                    if (production.isEpsilon()) {
                        continue;
                    }
    
                    List<Symbol> 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<Symbol> 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<Symbol> set;
    
    
        public SelectSet(Production production, Set<Symbol> set) {
            this.production = production;
            this.set = set;
        }
    
        public Production getProduction() {
            return production;
        }
    
        public Set<Symbol> 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<SelectSet>
         * @param grammar
         * @return
         */
        public static List<SelectSet> getAllSelectSetByGrammar(Grammar grammar) {
            // 可选集列表
            List<SelectSet> selectSetList = new ArrayList<>();
    
            // 当前文法的所有非终结符的后继符号集
            Map<Symbol, FollowSet> followSetMap = grammar.getFollowSetMap();
            for (Map.Entry<Symbol, List<Production>> 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<Symbol> rightSymbols = production.getRight();
                        // 产生式右部首字符是终结符,那么这个产生式可选集就是这个终结符
                        if (rightSymbols.get(0).isTerminator()) {
                            Set<Symbol> set = new HashSet<>();
                            set.add(rightSymbols.get(0));
                            selectSetList.add(new SelectSet(production, set));
                        }  else {
                            //
                            Set<Symbol> 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<Symbol, Map<Symbol, Production>> listToSelectTable(List<SelectSet> selectSetList) {
            /**
             * 使用Map<Symbol, Map<Symbol, Production>> 表示一个二维表结构
             * 每一行都是一个非终结符,表中的每一列都是一个终结符,值就是产生式
             */
            Map<Symbol, Map<Symbol, Production>> table = new HashMap<>();
            for (SelectSet selectSet : selectSetList) {
                Production production = selectSet.getProduction();
                /**
                 * 分析表中每一行都是非终结符,也是产生式的左部
                 */
                Map<Symbol, Production> 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<Production> match(Symbol start, Map<Symbol, Map<Symbol, Production>> table, List<Symbol> matchSymbols) {
            Stack<Symbol> stack = new Stack<>();
            stack.push(start);
            int pos = 0;
            List<Production> 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<Symbol, Production> 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<Symbol> 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<Production> 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<Symbol, FirstSet> firstSetMap = deepGrammar.getFirstSetMap();
            Map<Symbol, FollowSet> followSetMap = deepGrammar.getFollowSetMap();
    
            List<SelectSet> selectSetList = GrammarUtils.getAllSelectSetByGrammar(deepGrammar);
            Map<Symbol, Map<Symbol, Production>> table = GrammarUtils.listToSelectTable(selectSetList);
    
    
            List<Symbol> 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<Production> 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集以及预测分析算法实现(ja

          本文链接:https://www.haomeiwen.com/subject/clrfiltx.html