编译原理-消除左递归算法(java代码实现)

本篇文章内的源码: 这里

当我们自顶向下的语法分析时,就需要采用最左推导方式。
而这个时候,如果产生式左部和产生式右部首字符一样(即A→Aα),那么推导就可能陷入无限循环。
例如:

文法G
1.  S -> Sa | b

推导
S => Sa => Saa => ... => Sa...a 

因此对于:

  • 含有 A→Aα 形式产生式的文法称为是直接左递归
  • 如果文法中一个非终结符A,存在一步以上的推导,形成了 A =>+ Aα,称为间接左递归

    例如:A→BβB -> Aβ 可以得到 A => Bβ => Aββ

文法中不能包含这两种形式,不然最左推导就没办法进行。

那有人问了,如果产生式右部中间包含和产生式左部相同的字符,允不允许呢?

  • 大多数情况下,是允许的,因为我们采用的是最左推导,只要这个产生式右部在这个非终结符之前有其他终结符(即A→αA),那么就可以确定推导时,用不用这个产生。
  • 如果产生式右部在这个非终结符之前是非终结符(即A→BAβ),那么就要看 B 能不能推导出空串ε 了,如果可以,那么最终还是会推导出 A =>+ Aβ, 也属于间接左递归

一. 算法描述

1.1 消除直接左递归

例如:

文法G
1.  S -> Sa | b

那么该如何改造呢?
其实我们仔细分析一个这组产生式,我们发现它可以进行如下推导:

S => b
或者
S => Sa
  => Saa 
  ... 
  => Sa...a 
  => ba..a

你会惊奇的发现,它能推导出 b 和 (a)* (即由0a或者无数个a生成的文法符号串)。其实就可以改造成:

文法G
1.  S -> bS'
2.  S' -> aA' | ε

S' 就可以表示 (a)* ,因此S'必须有空产生式 S' -> ε,否则就得不到0a的空串。

因此消除直接左递归 算法的一般形式:

  A -> Aα1 | Aα2 | ... | Aαn | β1 | β2 | ... | βm
(其中这里数字表示下标,而不是一个终结符, α 和 β都是文法符号串)
         (αi != ε, 且 βi 不以A开头)
                改造成
A -> β1A' | β2A' | ... | βmA'
A' ->  α1A' | α2A' | ... | αnA' |  ε

1.2 消除间接左递归

例如:

文法G:
1. S -> Aa | b
2. A -> c | Sd

间接左递归
S => Aa
  => Sda
  => Aada
  => Sdada

消除间接左递归的方法就是直接带入消除,即

文法G:
1. S -> Aa | b
2. A -> c | Aad | bd

即将能形成间接左递归产生式中的非终结符,替换成这个非终结符的产生式组。

因此消除间接左递归 算法的一般形式:

按照某个顺序将非终结符进行排序,为A1,A2,...,An
for (从1到n的每个i) {
      for (从1到i-1的每个j) {
            将每个形如Ai -> Ajβ的产生式,用Aj 的产生式组Aj -> α1 | α2 | ... | αk 替换;
            得到产生式 Ai -> Ajβ 替换后的产生式组 Ai -> α1β | α2β | ... | αkβ。
    }
}

这个算法看起来描述很多,其实理解起来很简单:

  • 先将这些非终结符进行排序,然后开始遍历这些非终结符的产生式组
  • 判断当前非终结符Ai的每一个产生式右部首字符,是不是前面已经遍历过的非终结符Aj

    这个就是为什么需要对非终结符进行排序

  • 如果是那么就会形成间接左递归,就要用前面非终结符Aj的产生式组当前非终结符Ai这个产生式中的非终结符Aj,这样当前这个产生式变成了多个产生式了。

二. 算法实现

2.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;

    // 外部不能创建
    Symbol(String label, boolean isTerminator) {
        this.label = label;
        this.isTerminator = isTerminator;
    }

    public String getLabel() {
        return label;
    }

    public boolean isTerminator() {
        return isTerminator;
    }

    // 这里只判断 label 值
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Symbol symbol = (Symbol) o;
        return Objects.equals(label, symbol.label);
    }

    @Override
    public int hashCode() {
        return Objects.hash(label);
    }

    @Override
    public String toString() {
        return this.label;
    }

    @Override
    public int compareTo(Symbol o) {
        return label.compareTo(o.label);
    }
}

用来表示文法中字母表中一个符号,包括终结符和非终结符

2.2 Alphabet

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

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

    // 初始化
    static {
        for (int index = 0; index <= 9; index++) {
            ALL_SYMBOL.put("" + index, new Symbol("" + index, true));
        }
        // a-z
        for (char ch = 97; ch <= 122; ch++) {
            ALL_SYMBOL.put(String.valueOf(ch), new Symbol(String.valueOf(ch), true));
        }
        // A - Z
        for (char ch = 65; ch <= 90; ch++) {
            ALL_SYMBOL.put(String.valueOf(ch), new Symbol(String.valueOf(ch), false));
        }
        ALL_SYMBOL.put("+", new Symbol("+", true));
        ALL_SYMBOL.put("-", new Symbol("-", true));
        ALL_SYMBOL.put("*", new Symbol("*", true));
        ALL_SYMBOL.put("(", new Symbol("(", true));
        ALL_SYMBOL.put(")", new Symbol(")", true));
    }

    public static Symbol addSymbol(char ch) {
        return addSymbol(ch, true);
    }

    public static Symbol addSymbol(char ch, boolean isTerminator) {
        return addSymbol(String.valueOf(ch), isTerminator);
    }

    public static Symbol addSymbol(String label) {
        return addSymbol(label, true);
    }

    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(char ch) {
        return getSymbol(String.valueOf(ch));
    }

    public static Symbol getSymbol(String label) {
        return ALL_SYMBOL.get(label);
    }
}

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

2.3 Production

/**
 * @author by xinhao  2021/8/13
 * 文法中的产生式
 */
public class Production {
    public static final List EMPTY = new ArrayList<>(0);
    /** 产生式左边,而且必须是非终结符 */
    private final Symbol left;

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

    /** 表示这个产生式不是左递归产生式 */
    private final boolean isLeftEliminate;
   ....
}

用来表示文法中的一个产生式,例如 A -> bB

2.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.5 消除直接左递归算法

public class Production {
 ......
    public boolean isLeftEliminate() {
        if (right.isEmpty()) {
            return false;
        }
        // 产生式左部 与 产生式第一个字母相等,表示它是一个直接递归的产生式。
        return left.equals(right.get(0));
    }
} 


    /**
     * 消除直接左递归
     * 就是将这种格式  P -> Pa|Pb|Pc|Pd|e|f|g|h
     * 转成:
     * P -> eP'|fP'|gP'|hP'
     * p'-> aP'|bP'|cP'|dP'|ε
     *
     */
    public static Grammar eliminateDeepLeftRecursion(Grammar grammar) {
        List productions = new ArrayList<>();
        for (Map.Entry> entry : grammar.getProductions().entrySet()) {
            // 不包含直接左递归的 产生式集合
            List noLeftEliminateProductions = new ArrayList<>();
            // 包含直接左递归的 产生式集合; 即 A -> Aa 这种形式
            List leftEliminateProductions = new ArrayList<>();
            for (Production production : entry.getValue()) {
                if (production.isLeftEliminate()) {
                    leftEliminateProductions.add(production);
                } else {
                    noLeftEliminateProductions.add(production);
                }
            }

            // 表示这个 entry.getKey() 对应的产生式组 List 没有直接左递归产生式
            if (leftEliminateProductions.isEmpty()) {
                // 不用做任何修改,直接将 noLeftEliminateProductions 添加到 productions
                productions.addAll(noLeftEliminateProductions);
            } else {
                // 需要进行转换了, 即 A 变成 A'
                Symbol newKey = Alphabet.addSymbol(entry.getKey().getLabel() + "'", false);
                // 将原来的 P -> e|f|g|h
                // 变成    P -> eP'|fP'|gP'|hP'
                for (Production production : noLeftEliminateProductions) {
                    // 创建新的产生式右部  e 变成 eP' 或者  f 变成 fP'
                    List newRight = production.addSymbolToRight(newKey);
                    // 这个产生式的 left 还是 原来的
                    productions.add(Production.create(entry.getKey(), newRight));
                }

                // 将原来的 P -> Pa|Pb|Pc|Pd
                // 变成    P -> aP'|bP'|cP'|dP'|ε
                for (Production production : leftEliminateProductions) {
                    // 创建新的产生式右部  Pa 变成 aP' 或者  Pd 变成 dP'
                    List newRight = production.addSymbolToRightAndRemoveFirst(newKey);
                    // 这个产生式的左部,就必须是新的 key 了
                    productions.add(Production.create(newKey, newRight));
                }

                // 最后对这个新左部,必须添加 空产生式
                productions.add(Production.createEmpty(newKey));
            }
        }
        return Grammar.create(grammar.getStart(), productions);
    }

其实算法很简单,就是遍历文法中,每一个非终结符对应的产生式组,查看是否包含直接左递归的形式,如果包含就将它变成两个产生式组:

P -> Pa|Pb|Pc|Pd|e|f|g|h
变成:
P -> eP'|fP'|gP'|hP'
 p'-> aP'|bP'|cP'|dP'|ε

2.6 消除间接左递归算法

    /**
     * 消除间接左递归
     * 如同 S -> Aa|b   A -> Ac|Sd
     * 会产生  S ==> Aa ==> Sda
     * 消除间接左递归变成
     * S -> Aa|b
     * A -> Ac|Aad|bd
     */
    public static Grammar eliminateIndirectLeftRecursion(Grammar grammar) {
        // 记录之前遍历的 产生式
        LinkedHashMap> preProductionsMap = new LinkedHashMap<>();
        for (Map.Entry> entry : grammar.getProductions().entrySet()) {

            List entryProductions = new ArrayList<>();
            for (Production production : entry.getValue()) {
                // 得到产生式右部第一个字符,例如 A -> Sd 其中 S 就是第一个字符
                Symbol symbol = production.getRightFirst();
                // 这个产生式右部第一个字符之前出现过,例如 S -> Aa A -> Sd ,这个 S 就在之前出现过,
                // 那么就产生了间接左递归,那么就需要做转换了。就使用 S 的产生式组,替换它在A的中产生式。
                // symbol = null 没有关系,因为我们不会将 null 存入 preProductionsMap 集合中
                if (preProductionsMap.containsKey(symbol)) {
                    // 将 symbol 添加到 leftEliminateSet 集合中,表示它是间接左递归
                    List eliminateList = preProductionsMap.get(symbol);
                    for (Production eliminateProduction : eliminateList) {
                        // 例如 S -> Aa|b   A -> Ac|Sd,
                        // 这里 eliminateList 就是 [Aa, b], 要用它替换产生式[Sd],变成 [Aad, bd]
                        entryProductions.add(Production.create(production.getLeft(),
                                production.replaceRightFirst(eliminateProduction)));
                    }
                } else {
                    // 不是间接左递归,那么就直接添加
                    entryProductions.add(production);
                }
            }
            preProductionsMap.put(entry.getKey(), entryProductions);
        }
        return Grammar.create(grammar.getStart(), preProductionsMap);
    }

算法其实也很简单,按照一定顺序,遍历文法中非终结符对应的产生式组,如果发现有产生式右部的首字符与之前遍历过的非终结符相同,说明有间接左递归现象,那么就要这个非终结符对应的产生式组替换这个非终结符。

但是注意这里判断是否有间接左递归,只是靠产生式右部的首字符与之前遍历过的非终结符相同;其实这个是不完整的。

  • 例如有三个产生式 A -> B B -> CA C -> ε,那么非终结符 B 是可以推导出 B => CA => A 的,所以这种情况也属于间接左递归。
  • 所以判断间接左递归,不止要看首字符,还要包括产生式右部包含前面出现的字符(即 B -> αA),那么文法符号串 α的串首终结符集FIRST(α) 是否包含空串 ε。这个算法以后再介绍。

2.7 例子

public static void main(String[] args) {
        Symbol start = Alphabet.getSymbol('S');
        List productions = new ArrayList<>();
        productions.addAll(Production.createList(start, "Aa", "b", "ε"));
        productions.addAll(Production.createList(Alphabet.getSymbol('A'), "Ac", "Sd"));
        Grammar grammar = Grammar.create(start, productions);
        Grammar indirectGrammar =  GrammarUtils.eliminateIndirectLeftRecursion(grammar);
        Grammar deepGrammar =  GrammarUtils.eliminateDeepLeftRecursion(indirectGrammar);
        System.out.println(grammar);
        System.out.println(indirectGrammar);
        System.out.println(deepGrammar);
}

运行结果:

开始符号:S
产生式:
                S->Aa|b|ε
                A->Ac|Sd

开始符号:S
产生式:
                S->Aa|b|ε
                A->Ac|Aad|bd|d

开始符号:S
产生式:
                S->Aa|b|ε
                A->bdA'|dA'
                A'->cA'|adA'|ε

你可能感兴趣的:(编译原理-消除左递归算法(java代码实现))