LL(1)文法构造FIRST、FOLLOW、分析表并分析

2023-11-11

一、实验目的

学生运用编译原理的知识在实验技能和方法自行设计实验方案并加以实现。

二、使用仪器、器材

计算机一台
操作系统:Windows10
编程软件:Intellij IDEA

三、实验内容及原理

1.实验内容:

输入任意一个正确的文法G[S],都能分析,并得出一个等价的LL(1)文法G[E],求出FIRST集和FOLLOW集,并求出LL(1)分析表。对输入串进行语法分析,判断其是否符合文法G[E]。

2.要求:

(1)输入任意一个正确的文法G[S],都能分析,并得出一个等价的LL(1)文法G[E];
(2)根据该LL(1)文法G[E]的文法规则建立LL(1)分析表;
(3)输出输入串分析过程。

3. 原理:

3.1 FIRST集构造原理:

在这里插入图片描述

3.2 FOLLOW集构造原理:

在这里插入图片描述

这里FOLLOW集的表述难以理解,可以转换成如下表述:
1.首先判断非终结符A是否是文法开始符,如果是,则 # ∈ FOLLOW(A)
2.查看A在哪几个产生式的右部出现
3.查看A是否位于产生式右部某候选式的最右端,
若是,则选用第三条规则
若否,则选用第二条规则且好看该候选项中A之后的符号串能否推导为空串,若能,则再次选用第三条规则

3.3 分析表构造原理:

在这里插入图片描述

一、主程序示意图

在这里插入图片描述

二、扫描子程序算法思想

1.构造FIRST集:
遍历每一个产生式
{
    if(产生式右部第一个字符a为终结符)
    {
        // S➡a...
        a 加入FIRST(S)
    } 
    else
    {
        // S➡A1...
        求FIRST(A1)
        FIRST(A1)\{ε}加入FIRST(S)
        // S➡A1A2...An
        如果FIRST(A1)包含空串,则检查A2...An,将FIRST(Ai)\\{ε}加入FIRST(S),直到其中一个不包含空串
        如果A1到An的FIRST都包含空串,则空串加入FIRST(A1)
    }
}
2.构造FOLLOW集:
遍历每一个产生式右部包含S的产生式
{
    if(S在产生式最右部)
    {
        // E → ...S,使用规则三
        // FOLLOW(E) ∈ FOLLOW(S)
        将FOLLOW(E)加入到FOLLOW(S)
    }
    else
    {
        // E → αSβ,使用规则二
        // 将FIRST(β)\{ε}加入FOLLOW,如果FIRST(β)包含ε,则对β使用规则三
        if(β 属于 Vt)
        {
            // FIRST(β) = {β}
            直接将β加入FOLLOW(E)
        }
        else
        {
            FIRST(β)\{ε}加入FOLLOW(E)
            if(FIRST(β)包含ε)
            {
                FOLLOW(β)加入FOLLOW(E)
            }
        }
    }
}
3.构造分析表:

在这里插入图片描述

for(非终结符A:非终结符集)
{
      获取FIRST(A)
      对于FIRST集的每一个元素
找到对应产生式,加入分析表
          如果FIRST集包含空串,则对于任意b(b∈vt)属于FOLLOW(A),A->空串加入到M[A,b]
}

4.语法分析程序:
在这里插入图片描述

四、实验过程原始记录

1.目录结构

在这里插入图片描述

2.类说明

在这里插入图片描述
该程序包含两个核心类,GrammarProcessUtil和MyGrammarProcessor。GrammarProcessUtil:处理FIRST、FOLLOW和分析表
MyGrammarProcessor:进行语法分析,判断某个句子是否符合语法

3.主要属性说明

/**
 * 保存非终结符在 vnList中的位置
 */
private Map<String,Integer> vnMap = new HashMap<>();

/**
 * 保存终结符在 vtList 中的位置
 */
private Map<String,Integer> vtMap = new HashMap<>();

/**
 * 非终结符集
 */
private List<String> vnList = new ArrayList<>();

/**
 * 终结符集
 */
private List<String> vtList = new ArrayList<>();

/**
 * 分析表
 */
private String[][] table = null;

/**
 * 保存非终结符的产生式
 */
private Map<String,List<String>> productionMap = new HashMap<>();

/**
 * 产生式的分隔符
 */
private final String SEPARATOR = "→";

/**
 * 候选式的分隔符
 */
private final String CANDIDATE_SEPARATOR = "|";

/**
 * 空串
 */
private final String BLANK_STRING = "ε";

/**
 * FIRST集
 */
private Map<String,Set<String>> first = new HashMap<>();

/**
 * FOLLOW集
 */
private Map<String,Set<String>> follow = new HashMap<>();
  • vnMap、vnList和vtMap、vtList是用来保存终结符与非终结符信息的。
    其中Map用来保存字符存储在List的下标,List用来保存字符。
  • table是一个二维数组,用来保存分析表,下标可根据Map来获得。
  • productionMap用来保存每个非终结符对应的产生式的候选式(如S→a|Ba|C)则 productionMap中的数据为 键“S”对应的值为[“a”,“Ba”,“C”]
  • first和follow分别为FIRST集与FOLLOW集

4.函数说明

在这里插入图片描述

红框内的函数是主要函数,都是私有函数:

  • initFirst():初始化FIRST集
  • getFirst(): 获取某个非终结符的FIRST集
  • initFollow():初始化FOLLOW集
  • getFollow():获取某个非终结符的FOLLOW集
  • getTable(): 初始化分析表
  • printXXX(): 打印
    绿框内的函数是公开函数,提供给外部使用:
  • get():根据终结符与非终结符返回分析表中的内容
  • getGrammarStart(): 返回文法开始符
    其他函数是私有函数,则是起到一些辅助功能,具体可以查看代码中的注释

5.程序清单

/**
 * 语法分析器工具类,处理FIRST集、FOLLOW集和分析表
 * @Author DELL
 * @create 2020/12/4 15:26
 */
public class GrammarProcessorUtil {

    /**
     * 保存非终结符在 vnList中的位置
     */
    private Map<String,Integer> vnMap = new HashMap<>();

    /**
     * 保存终结符在 vtList 中的位置
     */
    private Map<String,Integer> vtMap = new HashMap<>();

    /**
     * 非终结符集
     */
    private List<String> vnList = new ArrayList<>();

    /**
     * 终结符集
     */
    private List<String> vtList = new ArrayList<>();

    /**
     * 分析表
     */
    private String[][] table = null;

    /**
     * 保存非终结符的产生式
     */
    private Map<String,List<String>> productionMap = new HashMap<>();

    /**
     * 产生式的分隔符
     */
    private final String SEPARATOR = "→";

    /**
     * 候选式的分隔符
     */
    private final String CANDIDATE_SEPARATOR = "|";

    /**
     * 空串
     */
    private final String BLANK_STRING = "ε";

    /**
     * FIRST集
     */
    private Map<String,Set<String>> first = new HashMap<>();

    /**
     * FOLLOW集
     */
    private Map<String,Set<String>> follow = new HashMap<>();

    /**
     * 将路径拼接为类路径
     * @param path
     * @return
     */
    private String getClasspath(String path) {
        return Thread.currentThread().getContextClassLoader().getResource(path).getPath();
    }

    /**
     * 将产生式按照候选式的分隔符进行分割
     * 例:str:"E'a|ε" => ["E'a","ε"]
     *    str:“E'a" => ["E'a"]
     * @param str
     * @return
     */
    private List<String> splitBySeparator(String str) {
        List<String> ans = new ArrayList<>();
        if(!str.contains(CANDIDATE_SEPARATOR)) {
            // 不包含分隔符,不可分割
            ans.add(str);
            return ans;
        } else {
            // 包含分隔符,进行分割
            String[] split = str.split("\\|");
            Collections.addAll(ans,split);
            return ans;
        }
    }

    /**
     * 将字符串分割成一个个字符
     * 注:主要是处理带单引号的字符
     * 例:str:"E'aT" => ["E'","a","T"]
     * @param str
     * @return
     */
    private List<String> getCharacter(String str) {
        List<String> ans = new ArrayList<>();
        int end = str.length();
        for(int start = str.length()-1;start >= 0;start--) {
            if(str.charAt(start) == '\'') {
                continue;
            } else {
                String substring = str.substring(start, end);
                ans.add(substring);
                end = start;
            }
        }
        return ans;
    }

    /**
     * 判断字符串str是否完全包含 regex,返回下标
     * 例:str:"aEb",regex="E" => 1
     *    str:"aE'b",regex="E" => -1
     *    str:"aE'b",regex="E'" => 1
     * @param str
     * @param regex
     * @return
     */
    private int contains(String str,String regex) {
        char[] ch = str.toCharArray();
        if(regex.length() == 2) {
            for (int i = 0; i < ch.length - 1; i++) {
                if (ch[i] == regex.charAt(0) && ch[i + 1] == regex.charAt(1)) {
                    return i;
                }
            }
        } else if(regex.length() == 1) {
            for (int i = 0; i < ch.length; i++) {
                if ((i == ch.length-1 && ch[i] == regex.charAt(0))
                        || (ch[i] == regex.charAt(0) && ch[i + 1] != '\'')) {
                    return i;
                }
            }
        } else {
            throw new UnsupportedOperationException("int contains(String str,String regex):regex长度只支持1或2");
        }
        return -1;
    }


    public GrammarProcessorUtil(String path) throws IOException {
        // 读取文件,保存数据
        BufferedReader reader = new BufferedReader(new FileReader(getClasspath(path)));
        String curLine = null;
        while((curLine = reader.readLine()) != null) {
            // 产生式左部 split[0],右部:split[1]
            String[] split = curLine.split(SEPARATOR);
            String left = split[0];
            String right = split[1];
            // 处理产生式左部,保存产生式右部
            vnMap.put(left,vnList.size());
            vnList.add(left);
            productionMap.put(left,splitBySeparator(right));
        }
        reader.close();
        // 获取非终结符集
        for(String vn:vnList) {
            // 获取非终结符产生式的右部的候选式
            List<String> vnRight = productionMap.get(vn);
            for(String right:vnRight) {
                // 将一个个候选式拆分成字符(处理类似 E → S'a的情况)
                List<String> characters = getCharacter(right);
                for(String c:characters) {
                    // 不是非终结符 且 未曾加入到过 终结符集
                    if(!vnMap.containsKey(c) && !vtMap.containsKey(c)
                            && !CANDIDATE_SEPARATOR.equals(c)) {
                        vtMap.put(c,vtList.size());
                        vtList.add(c);
                    }
                }
            }
        }
        // 打印终结符集 与 非终结符集
        printVtAndVn();
        // 获取FIRST集
        initFirst();
        // 打印FIRST集
        printFirst();
        // 获取FOLLOW集
        initFollow();
        // 打印FOLLOW集
        printFollow();
        // 获取分析表
        getTable();
        // 打印分析表
        printTable();
    }

    /**
     * 初始化FIRST集
     */
    private void initFirst() {
        for(String vn:vnList) {
            getFirst(vn);
        }
    }

    /**
     * 获取FIRTS集
     * @param vn
     * @return
     */
    private Set<String> getFirst(String vn) {
        if(first.containsKey(vn)) return first.get(vn);
        Set<String> set = new HashSet<>();
        // 遍历产生式的右部的候选式
        List<String> vnRight = productionMap.get(vn);
        for(String right:vnRight) {
            int index = 0;
            // 候选式的第一个字符
            String firstCharacter = String.valueOf(right.charAt(index++));
            if (vtMap.containsKey(firstCharacter)) {
                // 满足 E→a...,a加入FIRST集
                set.add(firstCharacter);
            } else if (vnMap.containsKey(firstCharacter)) {
                // 满足 E→S...,S∈vt,FIRST(S)\{ε}加入FIRST(E)
                Set<String> s = getFirst(firstCharacter);
                // FIRST(S)是否包含空串
                boolean hasBlankString = false;
                for(String str:s) {
                    if(!BLANK_STRING.equals(str)) {
                        set.add(str);
                    } else {
                        hasBlankString = true;
                    }
                }
                // 记录该产生式空串个数
                int blankStringNumber = hasBlankString ? 1 : 0;
                // E→S1S2S3... FIRST(S1)包含空串,则检查下一个字符
                while(hasBlankString && index < right.length()
                        && vnMap.containsKey(String.valueOf(right.charAt(index)))) {
                    Set<String> nextFirst = getFirst(String.valueOf(right.charAt(index)));
                    for(String str:nextFirst) {
                        if(!BLANK_STRING.equals(str)) {
                            set.add(str);
                        } else {
                            blankStringNumber++;
                            hasBlankString = true;
                        }
                    }
                    index++;
                }
                // E→S1S2...Sn中的每个字符都可以推导出空串,空串加入FIRST集
                if(blankStringNumber == right.length()) {
                    set.add(BLANK_STRING);
                }

            }
        }
        first.put(vn,set);
        return set;
    }

    /**
     * 初始化FOLLOW集
     */
    private void initFollow() {
        for(String vn:vnList) {
            getFollow(vn);
        }
    }

    /**
     * 获取FOLLOW集
     * @param vn
     * @return
     */
    private Set<String> getFollow(String vn) {
        // FOLLOW(vn)已经存在,直接返回
        if(follow.containsKey(vn) && !follow.get(vn).isEmpty()) return follow.get(vn);
        Set<String> set = new HashSet<>();
        // 文法开始符,#加入FOLLOW集
        if(vnList.get(0).equals(vn)) {
            set.add("#");
        }
        // 查看vn在哪条产生式的右部出现过
        for(String left:vnList) {
            // 自己的FOLLOW集不用管
            if(left.equals(vn)) {
                continue;
            }
            // 获取vn对应的产生式
            List<String> vnRight = productionMap.get(left);
            for(String item:vnRight) {
                int index = contains(item,vn);
                // 在产生式右部出现过
                if(index != -1) {
                    // 这样做是为了识别 E'
                    if(index + vn.length() == item.length()) {
                        // left→...S S出现在产生式最右部,使用规则三,将FOLLOW(left)加入FOLLOW(S)
                        Set<String> itemFollow = getFollow(left);
                        set.addAll(itemFollow);
                    } else {
                        // 否则使用规则二
                        // 获取next,这里要处理E'
                        String next = null;
                        int e1 = index + vn.length();
                        if(e1 + 1< item.length() && item.charAt(e1+1) == '\'') {
                            // SE',下一个字符带单引号
                            next = item.substring(e1,e1+2);
                        } else {
                            // SE,下一个字符不带单引号
                            next = item.substring(e1,e1+1);
                        }
                        if(vtMap.containsKey(next)) {
                            // 如果next是终结符,则FIRST(next) = {next},直接加入
                            set.add(next);
                        } else {
                            // FIRST(next)\{ε}加入FOLLOW(vn)
                            Set<String> first = getFirst(next);
                            boolean hasBlankString = first.contains(BLANK_STRING);
                            for(String s:first) {
                                if(!BLANK_STRING.equals(s)) {
                                    set.add(s);
                                }
                            }
                            // FIRST(next)含有ε,使用规则三
                            if(hasBlankString && !next.equals(vn)) {
                                Set<String> follow = getFollow(next);
                                set.addAll(follow);
                            }
                        }
                    }
                }
            }
        }
        follow.put(vn,set);
        return set;
    }

    /**
     * 将vn与vt对应的产生式存入分析表
     * @param vn
     * @param vt
     * @param production
     */
    private void fillTable(String vn,String vt,String production) {
        if(table == null) {
            throw new IllegalStateException("调用fillTable前请确保table已初始化!");
        }
        // 获取vn与vt分别在vnList与vtList的下标
        int vnIndex = vnMap.getOrDefault(vn,-1);
        int vtIndex = -1;
        if("#".equals(vt)) {
            vtIndex = table[0].length - 1;
        } else {
            vtIndex = vtMap.getOrDefault(vt, -1);
        }
        if(vnIndex == -1 || vtIndex == -1) {
            throw new IllegalArgumentException("fillTable(vn="+vn+",vt="+vt+",production="+production+")不合法");
        }
        // 拼接产生式
        String trueProduction = vn+SEPARATOR+production;
        // 存入分析表
        table[vnIndex][vtIndex] = trueProduction;
    }

    /**
     * 返回vn对应的产生式,且产生式中包含vt
     * 例:E→iESS'|a,vn:E,vt:i => iESS'
     * @param vn
     * @param vt
     * @return
     */
    private String findProduction(String vn,String vt) {
        List<String> list = productionMap.get(vn);
        for(String p:list) {
            if(p.startsWith(vt)) {
                return p;
            }
        }
        return null;
    }

    /**
     * 获取分析表
     * 前提:FIRST集与FOLLOW集已构造完成
     * @return
     */
    private String[][] getTable() {
        if(table != null) return table;
        table = new String[vnList.size()][vtList.size()+1];
        // 遍历非终结符
        for(String vn:vnList) {
            // 获取对应非终结符的FIRST集
            Set<String> vnFirst = first.get(vn);
            // 对于FIRST集的每一个元素,找到对应产生式,加入分析表
            for(String vt:vnFirst) {
                if(!BLANK_STRING.equals(vt)) {
                    String production = null;
                    if (productionMap.get(vn).size() == 1) {
                        production = productionMap.get(vn).get(0);
                    } else {
                        production = findProduction(vn, vt);
                    }
                    fillTable(vn, vt, production);
                }
            }
            // FIRST集包含空串,则
            if(vnFirst.contains(BLANK_STRING)) {
                Set<String> vnFollow = follow.get(vn);
                for(String vt:vnFollow) {
                    fillTable(vn, vt, BLANK_STRING);
                }
            }
        }
        return table;
    }

    /**
     * 根据非终结符与终结符获取分析表中的产生式
     * @param vn
     * @param vt
     * @return
     */
    public String get(String vn,String vt) {
        if(table == null) {
            throw new IllegalStateException("调用fillTable前请确保table已初始化!");
        }
        int vnIndex = vnMap.getOrDefault(vn,-1);
        int vtIndex = -1;
        if("#".equals(vt)) {
            vtIndex = table[0].length - 1;
        } else {
            vtIndex = vtMap.getOrDefault(vt, -1);
        }
        if(vnIndex == -1 || vtIndex == -1) {
            return null;
        }
        return table[vnIndex][vtIndex];
    }

    /**
     * 获取文法开始符
     * @return
     */
    public String getGrammarStart() {
        return vnList.get(0);
    }

    /**
     * 检查vt是否为终结符
     * @param vt
     * @return
     */
    public boolean containsVt(String vt) {
        if("#".equals(vt)) return false;
        return vtMap.containsKey(vt);
    }

    private void printVtAndVn() {
        System.out.println("终结符:");
        for(String vt:vtList) {
            System.out.println(vt);
        }
        System.out.println("非终结符:");
        for(String vn:vnList) {
            System.out.println(vn);
        }
    }

    private void printFirst() {
        for(String vn:vnList) {
            Set<String> strings = first.get(vn);
            StringBuilder sb = new StringBuilder();
            sb.append("FIRST(");
            sb.append(vn);
            sb.append(") = {");
            for(String s:strings) {
                sb.append(s).append(",");
            }
            sb.deleteCharAt(sb.length()-1);
            sb.append("}");
            System.out.println(sb.toString());
        }
    }

    private void printFollow() {
        for(String vn:vnList) {
            Set<String> strings = follow.get(vn);
            StringBuilder sb = new StringBuilder();
            sb.append("FOLLOW(");
            sb.append(vn);
            sb.append(") = {");
            for(String s:strings) {
                sb.append(s).append(",");
            }
            sb.deleteCharAt(sb.length()-1);
            sb.append("}");
            System.out.println(sb.toString());
        }
    }

    private void printTable() {
        int blankIndex = vtMap.getOrDefault(BLANK_STRING,-1);
        for(int i = 0;i < vtList.size();i++) {
            if(i != blankIndex) {
                System.out.print("\t" + vtList.get(i));
            }
        }
        System.out.println("\t#");
        for(int i = 0;i < table.length;i++) {
            System.out.print(vnList.get(i)+"\t");
            int j = 0;
            for(j = 0;j < table[0].length - 1;j++) {
                if(j != blankIndex) {
                    System.out.print("\t" + table[i][j]);
                }
            }
            System.out.println("\t"+table[i][j]);
        }
    }

}



/**
 * 定义语法分析器的行为
 * @Author DELL
 * @create 2020/12/4 15:17
 */
public interface GrammarProcessor {
    boolean check(String str);
}

/**
 * @Author DELL
 * @create 2020/12/4 15:23
 */
public class MyGrammarProcessor implements GrammarProcessor {


    private GrammarProcessorUtil util;

    public MyGrammarProcessor(String path) throws IOException {
        util = new GrammarProcessorUtil(path);
    }

    /**
     * 根据一个非终结符与终结符获取产生式
     * @param vn
     * @param vt
     * @return
     */
    public String get(String vn,String vt) {
        //vt = vt.toUpperCase();
        return util.get(vn,vt);
    }

    /**
     * 检查某个串是否符合 i+i 的语法
     * @param str
     * @return
     */
    @Override
    public boolean check(String str) {
        printTitle(str);
        // 初始时 # 与 文法开始符压栈
        Deque<String> stack = new LinkedList<>();
        stack.push("#");
        stack.push(util.getGrammarStart());
        // 输入串的下一个位置
        int i = 0;
        // 输入串当前指向的字符
        String cur = String.valueOf(str.charAt(i++));
        // 栈顶符号
        String top = null;
        do{
            // 打印
            print(stack,str,i,cur);
            top = stack.pop();
            if(top.equals("#") && cur.equals("#")) break;
            // 栈顶符号 ∈ Vt,判断栈顶与输入串当前字符是否相等
            if(util.containsVt(top)) {
                // x == a != "#",则字符串指针移动
                if(top.equalsIgnoreCase(cur)) {
                    if(!"#".equals(cur)) {
                        cur = "#";
                        System.out.println();
                        if(i < str.length()) {
                            cur = String.valueOf(str.charAt(i));
                            i++;
                        }
                    }
                }
                else {
                    return false;
                }

            } else {

                // 根据栈顶查表
                String t = get(top,cur);
                // 找不到,识别失败
                if(t == null) {
                    return false;
                }
                // 输出产生式
                System.out.println(padWhitespaceRight(t,8));
                // 获取产生式的右部
                String sentence = t.split("→")[1];
                // 将产生式的右部逆序压栈
                int end = sentence.length();
                for(int start = sentence.length()-1;start >= 0;start--) {
                    if(sentence.charAt(start) == '\'') {
                        continue;
                    } else {
                        String substring = sentence.substring(start, end);
                        // ε不压栈
                        if(!"ε".equals(substring)) {
                            stack.push(substring);
                            end = start;
                        }
                    }
                }
            }
        } while(!"#".equals(top));
        System.out.println();
        return true;
    }

    private void printTitle(String str) {
        System.out.println(str+"解析流程:");
        int len = str.length() + 3;
        String[] titles = new String[] {"符号栈","当前输入符号","输入串","所用产生式"};
        for(String s:titles) {
            System.out.print(padWhitespaceRight(s,len));
        }
        System.out.println();
    }

    private void print(Deque<String> stack, String str, int i, String cur) {
        int len = str.length() + 5;
        StringBuilder sb = new StringBuilder();
        // 拼接栈内的符号
        Iterator<String> iterator = stack.descendingIterator();
        while(iterator.hasNext()) {
            sb.append(iterator.next());
        }
        // 输出栈
        System.out.print(padWhitespaceRight(sb.toString(),len));
        // 输出当前输入符号
        System.out.print(padWhitespaceRight(cur,len));
        // 输出输入串
        if(i < str.length()) {
            System.out.print(padWhitespaceRight(str.substring(i-1),len));
        } else {
            System.out.print(padWhitespaceRight("#",len));
        }
    }

    public static String padWhitespaceLeft(String s, int len) {
        return String.format("%1$" + len + "s", s);
    }

    public static String padWhitespaceRight(String s, int len) {
        return String.format("%1$-" + len + "s", s);
    }
}

/**
 * @Author DELL
 * @create 2020/12/4 16:13
 */
public class GrammarTest {

    @Test
    public void test01() throws IOException {
        MyGrammarProcessor grammarProcessor = new MyGrammarProcessor("grammar.txt");

        List<String> tests = new ArrayList<>();
        tests.add("i+i*i#");
        tests.add("i+i#");
        tests.add("i*i#");
        tests.add("(i+i)*i#");
        tests.add("(i*i)+(i)#");


        for(String test:tests) {
            System.out.println(grammarProcessor.check(test));
            System.out.println();
        }
        List<String> err = new ArrayList<>();
        err.add("i&i#");
        err.add("i/(i*i)#");
        for(String test:err) {
            System.out.println(grammarProcessor.check(test));
            System.out.println();
        }
    }

    @Test
    public void test02() throws IOException {
        MyGrammarProcessor grammarProcessor = new MyGrammarProcessor("grammar02.txt");
        System.out.println(grammarProcessor.check("i=e#"));
        System.out.println();
        System.out.println(grammarProcessor.check("i:i=e#"));
        System.out.println();
        System.out.println(grammarProcessor.check("i-i=e#"));
        System.out.println();
    }
}

五、实验结果与分析

1.第一组文法:

在这里插入图片描述
其对应的输出的终结符、非终结符、FIRST、FOLLOW、分析表如下:


在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.第二组文法:

在这里插入图片描述
其对应的输出的终结符、非终结符、FIRST、FOLLOW、分析表如下:

在这里插入图片描述

六、实验分析及心得

分析:根据实验结果显示,对于文件中的一个LL(1)文法,该程序能够正常识别出终结符与非终结符,并且推出FIRST集、FOLLOW集,并使用FIRST、FOLLOW求得分析表,最终能够实现对于给定的句子判断是否符合文法,其中解析时能够正确输出符号栈、当前输入符号、输入串和所使用的产生式,达到了预期的目的。同时对于文法中的带单引号的字符(E→TE’),该程序也做了一定识别。

心得:
1.通过实验,将LL(1)文法的一系列流程通过程序实现出来,使得我对LL(1)文法的理解更加深刻,同时也锻炼了自己的编程能力。
2.在对编译原理的认知方面,我觉得对于编译原理中给定的方法,如果我们能够从程序的角度来看问题的话,会容易理解一些。例如FIRST集和FOLLOW集的构造方法中就会涉及到一些递归操作。
3.带单引号的字符的文法的识别,无论是在构造分析表还是在识别句子的过程中,这一点都带来了一定的困难,但最终都通过一定的方法克服了。

代码已上传至 gitee

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LL(1)文法构造FIRST、FOLLOW、分析表并分析 的相关文章

  • 解决MySQL报错:1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'informat

    一 问题描述 新安装的MySQL5 7 22 或 8 0 11 在 Navicat 上执行任一查询操作时 遇到报错 Err 1055 Expression 1 of ORDER BY clause is not in GROUP BY cl
  • 大家都在讨论华为OD?它到底怎么样

    一 华为od是什么 华为OD Outsourcing Developer 是华为和外企德科联合招聘项目的简称 目前华为大多数是OD招聘 OD模式也是华为提出的一种新的用工形式 OD项目过程中也会有部分优秀员工转为正编 不失作为进入大厂的一块
  • Java集成第三方OCR识别——文档篇

    Java快速集成OCR文字识别 相关文章 简介 官方文档 Web 配置操作 第一步 成为百度AI开放平台的开发者 第二步 开通文字识别服务 1 领取免费测试资源 2 创建应用 第三步 使用文字识别服务 1 添加OCR依赖 2 下载相关工具包
  • C++中换行endl和\n的区别

    转载自 http www sjyhome com c endl n html 在C 中 在显示的形式上 cout lt
  • .NET正则基础——.NET正则类及方法应用[转载]

    1 概述 初学正则时 对于Regex类不熟悉 遇到问题不知道该用哪种方法解决 本文结合一些正则应用的典型应用场景 介绍一下Regex类的基本应用 这里重点进行 NET类的介绍 对于正则的运用 不做深入探讨 正则的应用最终都是进行模式的匹配
  • python分析b站_用 Python 抓取 bilibili 弹幕并分析!

    时隔一年 嵩哥带来他的新作 雨幕 他依旧认真创作 追求高品质 作品在发表之前已听了五百遍以上 如此高品质的音乐 大家如何评价呢 通过哔哩哔哩上的视频弹幕 感受一下 01 实现思路 首先 利用哔哩哔哩的弹幕接口 把数据保存到本地 接着 对数据
  • HTML中下拉框的简单介绍<Select><option>

    1 下拉框的使用 在很多地方能见到下拉框的使用 最常用的就是在填写地址的时候 用户自己选择地址 2 效果演示 3 代码演示 下拉框主要用到
  • 浅谈cuda5.0新功能——warpshuffle

    warpshuffle 的具体定义可以在cuda C programming guide中被找到 但是这一功能只能被sm30或者更高的显卡支持 具体原因涉及到了kepler和fermi之间的差别 kepler在一个时钟周期内可以执行32个线
  • mybatis---设置typeAliasesPackage支持**通配符匹配

    设置typeAliasesPackage支持 通配符匹配 mybatis的typeAliasesPackage属性的作用是 搜索指定包别名 配置了以后xml文件中的resultType和parameterType就不需要指定全类名com e
  • 快速成长的秘诀|如何实现自我认知升级?

    一 写在开始 精英人数的增长速度持续加快后 很多人开始焦虑 我也焦虑 深知要走出焦虑不容易 我想把走出焦虑快速成长的认知和方法写成文章分享给更多人 做成PPT给更多人面对面分享 快速成长总共三篇 分别是 完成自己的认知升级 自我成长的方法
  • Network Password Recovery工具查看windows凭据隐藏密码

    查看windows凭据密码 方法一 使用重装系统工具里面自带的修改密码工具来修改或者清除密码 方法二 查看windows凭据密码 这里居然看不了 需要用到 nirsoft 公司做的免费工具 Network Password Recovery
  • switch_to

    理论部分请参考 深入理解Linux 内核 第三章 1 switch to 宏 define switch to prev next last do last switch to prev task thread info prev task
  • C/C++ --- 全局变量初始化总结

    注意 本文所说的全局变量指的是 variables with static storage 措词来自 c 的语言标准文档 什么时候初始化 根据 C 标准 全局变量的初始化要在 main 函数执行前完成 常识无疑 但是这个说法有点含糊 mai
  • 就业管理系统【软件建模与分析UML课设】

    觉得好记得点赞 关注我哦 界面设计如何不重要 重在画图 概 述 1 1系统目标 建设集就业管理办公自动化 毕业生与用人单位信息管理 就业部门形象化宣传为一体的综合性管理系统 组建一个具备人才管理 人才交流等功能的综合性信息系统 使整个人才交
  • mysql MHA集群安装

    一 主机规划 IP Hostname Master Slave Manager Node Data Node 10 22 83 42 node1 Master Data Node 10 22 83 26 node2 Slave Data N
  • 如何使用Egret制作游戏?

    好的 下面是使用Egret制作游戏的详细教程 一 前期准备 1 安装Egret Wing开发环境 可以在官网下载 https www egret com products wing html 2 安装Egret Engine 可以在官网下载
  • BES2300Z USB mode 讲解

    hello 在BES的蓝牙中有一些芯片是支持USB mode 在使用的过程中 在BT mode 和 USB mode 中只能有一种模式存在 排版会有点乱 请谅解 下面来讲解下BES2300Z 在USB mode 下打开的方法 遇到的一些问题
  • robotframework安装与详解

    Robot Framework 以下简称rf 是一款python编写的功能自动化测试框架 具备良好的可扩展性 支持关键字驱动 可以同时测试多种类型的客户端或者接口 可以进行分布式测试执行 主要用于轮次很多的验收测试和验收测试驱动开发 ATD

随机推荐

  • Python requests ip代理爬虫报错 HTTPSConnectionPool(host=‘xxxxx‘, port=443) Max retries exceed

    本人系统 macOS10 15 6 Catalina 场景 使用Python requests 包 ip代理池爬取网站数据 出现报错 HTTPSConnectionPool host xxxxx port 443 Max retries e
  • JDBC实现

    JDBC编程步骤如下 1 Load the Driver 加载驱动 1 注冊驱动有三种方式 1 Class forName com mysql jdbc Driver 推荐这样的方式 不会对详细的驱动类产生依赖 2 DriverManage
  • 菜鸟学习篇--Vuecli4.0 Vant ui组件样式无效果

    新建了个vue项目 按需引入vant组件的时候 发现页面不出样式效果 解决办法是 第一步 在bable config js文件中加入 plugins import libraryName vant libraryDirectory es s
  • git需要掌握的基础知识

    Git的简介 Git 是一款免费的 开源的 分布式的版本控制系统 旨在快速高效地处理无论规模大小的任何软件工程 每一个 Git克隆 都是一个完整的文件库 含有全部历史记录和修订追踪能力 不依赖于网络连接或中心服务器 其最大特色就是 分支 及
  • 7 个隐藏的 Blender 技巧将改善您的工作流程

    谁不喜欢秘密技巧 因为 Blender 是一个全面的 多功能的工具 所以有很多隐藏的复活节彩蛋 隐藏在可见表面之下的时尚工具和功能 对于今天的文章中 让我们来找出了最好的秘诀Blender技巧以提高您的工作流程与效率 1 轻松选择集合中的所
  • Spring + MyBatis

    MyBatis ORM 对象 关系映射 完成对象数据到关系型数据映射的机制称为对象 关系映射 1 MyBatis是一个ORM框架 也是一个持久层框架 MyBatis封装了JDBC 将数据库中的表数据自动封装到对象中 这样就可以以面向对象的方
  • 快手小店怎么引流?快手怎么做店铺引流?

    短视频的内容丰富 更能吸引广大用户 因为它可以让用户得到视觉的享受等等 就像快手平台 以短视频迅速攻占用户的心 尤其是三四线城市的用户 非常喜欢在快手上分享自己的生活和见闻 现在就连淘宝也想借助快手平台为自己的店铺进行引流 快速实现产品的变
  • IDEA常用插件之注解插件

    文章目录 注解插件 JavaDoc插件 安装 修改配置 生成文档加入自己信息 Easy JavaDoc 安装插件 在线安装 离线安装 中文名自动转英文 加注释 默认快捷键 可通过IDEA快捷键设置修改 注解插件 JavaDoc插件 安装 修
  • 数据库SQL语句期末总复习

    文章目录 SQL的分类 DDL数据定义语言 数据库的定义与撤销 基本表的定义与维护 索引的建立与删除 DQL数据查询语言 单表查询 查询结果排序 分组查询 连接查询 嵌套查询 集合查询 DML数据操作语言 插入数据 更新数据 删除数据 视图
  • 创建HttpPost和HttpGet请求

    1 创建工具类 HttpClient import com alibaba fastjson JSON import org apache http HttpStatus import org apache http client conf
  • react 函数组件父组件调用子组件方法

    react 函数组件父组件调用子组件方法 父组件利用ref对子组件做标记 通过调用子组件方法更改子组件状态 也可以调用子组件方法 首先在父组件中 使用useRef创建一个ref import LogModal from logModal i
  • vue单选框选中_vue radio单选框,获取当前项(每一项)的value值操作

    前言 本文使用了lable关联选中 实际使用中如果不需要 直接将循环语句 v for 写在 input标签上就可以 1 使用v for循环的radio单选框 01 需要注意的是 这是使用的是 change 事件 而不是 click 点击事件
  • NSSCTF刷题

    web NSSCTF 2022 Spring Recruit babyphp
  • UEFI基本概念

    TianoCore UEFI EDK2 UEFI Unified Extensible Firmware Interface 用来取代BIOS TianoCore 一个社区 支持UEFI的开源实现 EDK2 一种UEFI的开发环境 UEFI
  • AI大模型有哪些?国内的

    今年相信大家都被ChatGPT刷过屏 因为它太好用了 问它一个问题 它就能回答 可以帮助我们写各种文字 甚至写代码 对于我们的工作有着很大的帮助 国内这半年对于AI这个行业也出现了很多的公司以及产品概念 各家大厂也在抓紧研发 和GPT一样的
  • 读写锁 share_mutex

    实现一个Windows下的共享锁 读写锁 一 作者 tyc611 cublog cn 2008 11 18 在Windows Vista Server 2008之前 Windows没有提供共享锁 通俗称为读写锁 只能靠自己实现 但从Wind
  • java基础面试题系列(71 - 80)

    20200714 by 1z 请你说明HashMap 和 HashTable的区别 1 是否同步 HashMap是非同步的 HashTable是同步的 2 继承体系 HashTable继承自Dictionary HashMap继承自Abst
  • Linux下多进程通信(signal,pipe)

    操作系统实验导航 实验一 银行家算法 https blog csdn net weixin 46291251 article details 115384510 实验二 多级队列调度和多级反馈队列调度算法 https blog csdn n
  • GpuMat ROI

    在引用GpuMat数据的ROI时 需要保证该数据在Gpu 内存中存储是连续的 使用gpu createContinuous创建连续空间 cuda GpuMat dst pyr laplace tmp dst pyr laplace gpu
  • LL(1)文法构造FIRST、FOLLOW、分析表并分析

    一 实验目的 学生运用编译原理的知识在实验技能和方法自行设计实验方案并加以实现 二 使用仪器 器材 计算机一台 操作系统 Windows10 编程软件 Intellij IDEA 三 实验内容及原理 1 实验内容 输入任意一个正确的文法G