编译原理:语法分析程序【附源码和超详细注释】

news2025/3/15 6:22:08

目录

一 、实验目的

二 、实验内容及步骤

三、程序分析

1. 需求分析

2. 功能分析

1. LL(1)文法功能分析

2. 算符优先文法功能分析

3. 信创分析-主要针对能力提升中国产操作系统上开发内容。

四、源代码

1. LL(1)文法代码

2. 算符优先文法

五、测试结果

1. LL(1)文法:

2. 算符优先文法:


一 、实验目的

(1)通过完成LL(1)和SLR(1)(或者算符优先分析法)语法分析程序,了解自顶向下

和自底向上语法分析的过程。

(2)掌握两种语法分析程序的设计和实现过程。


二 、实验内容及步骤

(1)根据实验二的结果,至少使用以上语法分析中的一种完成语法分析。   

(2)实现一个 LL(1)和SLR(1)(或者算符优先分析法)语法分析程序,可以对用户输入的符号串进行分析。

(3)以上内容2选1完成,要求输出分析过程及分析结果。

   能力提升:在国产操作系统上开发功能模块。


三、程序分析

1. 需求分析

本次实验需要实现 LL(1)语法分析程序和算符优先分析程序,要实现这两个程序,首先要了解这两个文法的分析原理,而程序的需求也就是将文法分析的各个步骤与过程实现出来。下面分别介绍两个文法的分析过程与原理:

  (1)LL(1)语法分析程序需求分析:

              LL(1)语法分析程序的分析步骤为:

              ①判断是否是有左递归和左公因子

       LL(1)文法没有左递归与左公共因子,拿到一个文法首先开始检查有无左递归和左公因子,若有则需要改造该文法才能继续进行分析,若无则直接开始第四步的分析。

②消除左递归:

       左递归包括直接左递归与间接左递归,直接左递归是可以直接推导出由自身作为开头的产生式,如A->A...,间接左递归是间接地推导出由自身作为开头的产生式,如A->B..., B->A...。

       若是直接左递归,则按图3-1的方法直接消除,若是间接左递归,则先将其转换为直接左递归,再按直接左递归的方法进行消除,方法也如图3-1所示:

图3-1 消除左递归方法

            ③提取左公共因子:

      形如A->abb..,A->acc...的规则即含有左公共因子,提取方法是保留公共因子,将剩下的其余部分换成一个新非终结符,再由该新非终结符推导出非公共因子的其余部分,具体过程如图3-2所示:

图3-2 提取左公因子方法

              ④求所有非终结符的FIRST集合:

     FIRST集合即产生式右部的首终结符字符的集合。求非终结符x的FIRST集合,需要找产生式的左部为x的。例如规则A->aBb,若a为终结符,则FIRST(A)={a};若a为非终结符,当a不为空串,则FIRST(A)=FIRST(a)-{ε},若a为空串,则将空串代入,规则变为A->Bb,循环上述过程,FIRST(A)=FIRST(B)。

图3-3 求FIRST集合方法

             ⑤求所有非终结符的FOLLOW集合:

     FOLLOW集合即产生式右部的尾终结符字符的集合。求非终结符x的FOLLOW集合,需要找产生式的右部出现了x的。

     规则1:begin->#...#,即FOLLOW(begin) = { #, ...}

     规则2:A->aB(B后为空),则FOLLOW(B) = FOLLOW(A)

     规则3:A->aBc(B后不为空)

          规则3.1:c是终结符,则FOLLOW(B) = {a}

          规则3.2:c是非终结符,且c不为空串,则FOLLOW(B)=FIRST(B)-{ε}

          规则3.3:c是非终结符,但c为空串,则代入空串到原式,继续判断上述规则。

图3-4 求FOLLOW集合方法

              ⑥求所有产生式的SELECT集合:

     SELECT集合即是输入符号集(即当前读取的符号)与该产生式的右侧符号串能产生的符号的交集。求产生式的SELECT集合有两种情况:

      规则1:A->a,a不为空串ε,则SELECT(A->a)=FIRST(a)

      规则2:A->a,a为空串ε,则SELECT(A->a)=FOLLOW(A)

图3-5 求FOLLOW集合方法

              ⑦求相同左部的所有产生式的SELECT集合的交集:

     SELECT集合即是输入符号集(即当前读取的符号)与该产生式的右侧符号串能产生的符号的交集,求相同左部的所有产生式的SELECT集合的交集可以判断当前文法是否是LL(1)文法。若所有相同左部的产生式的SELECT集合的交集都为空,则是LL(1)文法,否则不是LL(1)文法。

⑧根据SELECT集合构造预测分析表:

     LL(1)的预测分析表由行-终结符,列-非终结符构成,若非终结符x的SELECT集合中包括哪些终结符,则将该产生式的右部写入表格中。


(2)算符优先分析程序需求分析:

              算符优先语法分析程序的分析步骤为:

              ①判断是否是有相邻的非终结符:

       算符优先文法没有相邻的非终结符,拿到一个文法首先开始检查有无相邻的非终结符,若有则该文法不是算符优先文法,若无则可以继续第二步分析。

              ②求所有非终结符的FIRSTVT和LASTVT集合:

       非终结符x的FIRSTVT集合即x从左到右,经过1步或n步能推导出的所有第一个终结符的集合,例如B->b...,B->Cb...。

        非终结符x的FIRSTVT集合即从x从右到左,经过1步或n步能推导出的所有最后一个终结符的集合,例如B->...b,B->...bC。

             ③求算符优先关系:

                    算符共有三种关系,分别为算符等于,算符大于和算符小于,需要注意的是,所有算              符关系都不能调换顺序。

                     关系1:算符等于 a=b,即A->...ab... 或 A->aBb

                     关系2:算符大于 a>b,即A->...Bb...且B->...a或B->...aC,即a∈LASTVT(B)

                     关系3:算符大于 a<b,即A->...aB...且B->b...或B->Cb...,即b∈FIRSTVT(B)

             ④根据算符优先关系构造算符优先分析表:

算符优先分析表由行-终结符,列-终结符构成。

       情况1:终结符a = 终结符b,则将 = 写入表格的a行b列中

       情况2:终结符a > 终结符b,则将 > 写入表格的a行b列中

       情况3:终结符a < 终结符b,则将 < 写入表格的a行b列中


2. 功能分析

1. LL(1)文法功能分析

       系统的整体结构设计与需求分析中的一致,首先从grammer.txt中读取要分析的文法,接着程序通过main函数按顺序调用各种分析函数,处理后再提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。具体的各类主要函数功能如表3-2-1所示:

表3-2-1 LL(1)文法系统主要函数功能表

函数名称

形参含义

功能详解

int main()

主函数

       程序入口,按分析顺序调用各个函数。调用函数对数据进行初始化,打开输入文件并调用函数进行分割与分析等。分析结束后提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。

init()

初始化函数

       对程序所需要的各个集合,如非终结符VN和终结符VT进行初始化。

identifyVnVt

(ArrayList<String> list)

识别终结符与非终结符

文法的产生式集合

1. 识别开始符号:

第一个产生式的左部

2. 识别非终结符:

左部与右部通过[->]进行分割获取,每个产生式的左部即为该文法的非终结符。

3. 识别终结符:

相同左部的不同产生式通过[|]进行分割获取,每一个产生式的每一个右部元素去除掉第二步中识别出的非终结符,即为该文法的终结符。

reformMap()

消除左递归和提取左公共因子

1. 产生式右部不为左递归:

    保持左部不变,右部的最后面添加一个[左部+[']]的字符。

2. 产生式右部为左递归:

左递归标志设为true,左部替换为[左部+[']]的字符,右部保留除了第一个字符的剩下元素,再添加[左部+[']]的字符。

3. 左递归标志为true:

更新产生式的键值对map,添加[左部+[']]作为新的非终结符VN。右部不递归的产生式右部元素再添加一个空串ε。

4. 提取左公共因子:

     左部保持不变,右部保留公共因子,在右部剩余部分删除,再添加为[左部+[']]。

     添加新产生式左部为[左部+[']],右部为刚刚删除的非公共部分的其余部分。

getFirst()

求FIRST集合

1. 产生式第一个符号是终结符

     加入FIRST集合

2. 产生式第一个符号是非终结符

      递归查找该非终结符的 FIRST 集合,直到找到终结符或空串ϵ。

getFollow()

求FOLLOW集合

1. 开始符号start的FOLLOW集合:

       直接加入[#]

2. 非终结符后直接跟一个终结符:

     该终结符加入非终结符的FOLLOW集合。

3. ...非终结符1非终结符2...

     则FOLLOW(非终结符1) = FIRST(非终结符2)

4. 非终结符1->...非终结符2:

     则FOLLOW(非终结符2) = FOLLOW(非终结符1)


图3-2-1 LL(1)文法系统功能流程图

图3-2-2 识别VNVT函数功能流程图

图3-2-3 消除左递归函数功能流程图

图3-2-4 求FIRST集合函数功能流程图


2. 算符优先文法功能分析

       系统的整体结构设计与需求分析中的一致,首先从grammer.txt中读取要分析的文法,接着程序通过main函数按顺序调用各种分析函数,处理后再提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。具体的各类主要函数功能如表3-2-1所示:

表3-2-1 算符优先文法系统主要函数功能表

函数名称

形参含义

功能详解

int main()

主函数

       程序入口,按分析顺序调用各个函数。调用函数获取终结符与非终结符,分割产生式等。分析结束后提示用户输入要分析的字符串,并构造输入字符串分析表打印到控制台。

getFirstVT(Character s, Set<Character> fvt)

计算FIRSTVT集合

当前要分析的左部文法符号

1. P->a...,产生式以终结符开头:

       终结符加入FIRSTVT集合。

2. P->Qa...,即先以非终结符开头,紧跟终结符

       终结符加入FIRSTVT集合。

3. P->Q...,即以非终结符开头   

       该非终结符的FIRSTVT集合加入P的FIRSTVT集合。

getLastVT(Character s, Set<Character> lvt)

计算LASTVT集合

当前要分析的左部文法符号

1. P->....aW,即先以非终结符结尾,倒数第二位是终结符

       终结符入LASTVT集合。

2. P->aQ |,即先以非终结符结尾,前面是终结符,后面有分隔符:

       终结符入LASTVT集合,递归调用计算函数。

3. P->....Q,即以非终结符结尾:

       该非终结符LASTVT集合加入P的LASTVT集合。

4. P->....Q |,即以非终结符结尾后面有分隔符:

       非终结符LASTVT集合加入P的LASTVT集合,递归调用计算函数。

constructMatrix()

构造算符优先矩阵

1. P->...ab...,即当前字符后面有一个终结符字符,并且不是竖线('|'):  

       将当前字符和后面的字符作为键,算符等于号('=')作为值,存入matrix中。

2. P->....aBc,即当前字符后面有两个字符,并且第二个字符也是终结符,并且不是竖线('|')

       将当前字符和第二个字符作为键,将算符等于号('=')作为值,存入matrix中。

3. P-> +T, 当前字符后面有一个非终结

       该字符的FIRSTVT集合中的每个元素与当前字符作为键,将小于号('<')作为值,存入matrix中。

4. P->E+,即当前字符前面为非终结符,且不是起始符号

       该字符的lastVt集合中的每个元素与当前字符作为键,将大于号('>')作为值,存入matrix中。            

图3-2-5 算符优先文法系统功能流程图

图3-2-6 求FIRSTVT集合功能流程图

图3-2-7 求LASTVT集合功能流程图


3. 信创分析-主要针对能力提升中国产操作系统上开发内容。

1. 国产操作系统:

名称:麒麟操作系统

系统版本:20.03 (LTS-SP3)

内核: Linux 4.19.90

CPU:ARM 架构的 Kunpeng 920 处理器

桌面环境:UKUI

当前用户:root


2. 编译器:

名称:IntelliJ IDEA

版本:Community Edition 2018.3.6

图3-2-1 编译器配置

3. 开发步骤:

  (1)安装idea

     ①在JetBrains官方网站下载IntelliJ IDEA,选择linux版本。

由于该操作系统的java版本太低,因此安装太高级的idea会无法使用,要安装较低版本的如2018.3.6。

     ②提取下载的压缩文件,输入命令:tar -zxvf ideaIC-2018.3.6.tar.gz

       这里的[ideaIC-2018.3.6]应该换成自己下载的版本。

     ③给脚本执行权限,输入命令:

     cd idea-IC-183.6156.11

     sudo chmod a=+rx bin/idea.sh

     bin/idea.sh

这里的[idea-IC-183.6156.11]依然应该换成自己下载的版本。

     ④安装idea,一直next就可以。


(2)打开idea

    ①打开解压出来的文件

    ②在该文件目录下打开终端

    ③在终端输入命令:bin/idea.sh

    ④成功打开idea


3)在idea中运行程序

在国产操作系统上运行程序的结果和Windows上是一样的,这里就不演示了。


4. 开发中遇到的问题:

(1)无法安装vscode

     原本开发消除注释的程序是用c++语言的写的,因此在国产操作系统上安装vscode。但由于该国产操作系统与其他操作系统的命令有部分不一样,例如apt-get命令不知为何使用不了,导致在诸如ubuntu操作系统上的安装流程,在麒麟操作系统上并不适用,无法成功安装。因此我将c++代码改为了java代码,并成功安装了idea进行编译。

(2)无法安装高版本的idea

       一开始我直接安装了最高版本的idea,但在解压后安装idea时发现无法安装。

经查阅资料,发现是由于操作系统本身的java环境太低,与高版本的idea不适配,因为不同版本的idea有对应支持的java范围。因此我通过试验了几个版本的idea,找到2018版本的idea可以在操作系统上成功运行。


四、源代码

1. LL(1)文法代码

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;

public class TetsLab3LL1 {
    // 文法定义文件
    public static final String PATH = "grammer.txt";
    // 从文件中读取得到的产生式集合
    public static ArrayList<String> produceList;
    // 文法开始符号
    private static String START;
    // 非终结符集合 终结符集合
    private static HashSet<String> VN, VT;
    // 产生式集合 key:产生式左边 value:产生式右边(含多条)
    private static HashMap<String, ArrayList<ArrayList<String>>> MAP;
    // "|" 分开的单条产生式对应的FIRST集合,用于构建LL1分析表
    private static HashMap<String, String> oneLeftFirst;
    // 总体的FIRST、FOLLOW集合
    private static HashMap<String, HashSet<String>> FIRST, FOLLOW;
    // LL1分析表的数组,用于输出
    private static String[][] FOCAST_TABLE;
    // LL1分析表的map,用于快速查找
    private static HashMap<String, String> preMap;

    public static void main(String[] args) {
        // 1. 初始化
        init();

        // 2. 将文件中的符号分类,并以key-value形式存于MAP中
            /* readFile(): 读取文件的内容,把文件的每一行存为一个字符串,放入ArrayList<String>中
             类似[
                    "S -> A B | C D",
                    "A -> a | ε",
                    "B -> b"
                ]
            */
        produceList = readFile(new File(PATH));
        identifyVnVt(produceList);

        // 3. 消除左递归
        reformMap();

        // 4. 求FIRST集合
        getFirst();

        // 5. 求FOLLOW集合
        getFollow();

        if (isLL1()) {
            // 6. 构建预测分析表
            preForm();

            System.out.println("请输入要分析的单词串:");
            Scanner in = new Scanner(System.in);
            printAutoPre(in.nextLine());

            in.close();
        }
    }



    // 1. 初始化集合
    private static void init() {
        VN = new HashSet<>(); // 非终结符
        VT = new HashSet<>(); // 终结符
        MAP = new HashMap<>(); // 产生式集合,内含多条产生式
        FIRST = new HashMap<>(); // FIRST集合
        FOLLOW = new HashMap<>(); // FOLLOW集合
        oneLeftFirst = new HashMap<>(); // 单条产生式的FIRST集合
        preMap = new HashMap<>(); // 预测分析表的map
    }



    // 2. 将语法的字符进行分类,分为开始符号(非终结符)和终结符等
        /*
          该方法接受一个语法列表,识别出非终结符(VN)和终结符(VT),将它们分别存储在集合VN和VT中,同时构建产生式的映射MAP
        */
    private static void identifyVnVt(ArrayList<String> list) { // 传入文法的产生式集合
        START = String.valueOf(list.get(0).charAt(0)); // 文法的开始符号:文法中的第一个产生式的第一个字符

        for (String oneline : list) { // 处理文法list中的每个产生式oneline

            // 1. 分割该非终结符的产生式的左部和右部
                // 用 "→" 分割产生式 vnvt[0]是文法的左边(非终结符) vnvt[1]是文法的右边(终结符)
            String[] vnvt = oneline.split("→");

            // 2. 处理产生式左部
            String left = vnvt[0].trim(); // 产生式的左边 并消除左右空格
            VN.add(left); // 将产生式的左边添加到非终结符集合

            // 3. 处理产生式各个右部
                // 每个左部对应一个mapValue
                // 例如[
                //      [“A”,"a"], 代表A->Aa
                //      ["b"] 代表A->b
                //     ]
            ArrayList<ArrayList<String>> mapValue = new ArrayList<>();
            ArrayList<String> right = new ArrayList<>(); // 存储当前左部的其中一个右部,需要用[|]分割出来存入right

            for (int j = 0; j < vnvt[1].length(); j++) { // 遍历产生式的右部的每一个字符
                if (vnvt[1].charAt(j) == '|') { // 用 “|” 分割产生式的右部
                    // 把每个右部传入VT终结符集合,把某个左部对应的所有右部集合加入mapValue
                    // (这次移入会把终结符和非终结符一起移入,非终结符会在后续删掉)
                    addRightToVTAndMapValue(right, mapValue);
                    right = new ArrayList<>(); // 清空right
                    continue;
                }
                // 如果产生式某字符的左边含有中文或英文的单引号,则视为一个整体常量
                // 如果当前字符后面紧跟一个单引号(' 或 ’),则将这两个字符视为一个整体,并将它们添加到 right 中。
                // 如果不是,则只添加当前字符。
//                if (j + 1 < vnvt[1].length() && (vnvt[1].charAt(j + 1) == '\'' || vnvt[1].charAt(j + 1) == '’')) {
//                    right.add(new StringBuilder().append(vnvt[1].charAt(j)).append(vnvt[1].charAt(j + 1)).toString());
//                    j++;
//                }
                else { // 如果没有遇到分割符[|]
                    right.add(String.valueOf(vnvt[1].charAt(j))); // 将字符添加到right
                }
            }
            addRightToVTAndMapValue(right, mapValue); // 最后一次遍历不会遇到[|],再add一次

            // 4. 把处理好的左部及对应的各个右部添加到MAP
                // 以左部非终结符left作为键,以各个右部形成的列表作为值
                // 例如A->Aa|b,最终map里就是key-A,value-[["A","a"],["b"]]
            MAP.put(left, mapValue);
        }
        VT.removeAll(VN); // 循环结束后,找到所有的非终结符了,再从终结符集合中移除非终结符

        // 打印Vn非终结符、Vt终结符
        System.out.println("\n非终结符集:\t{" + String.join("、", VN) + "}");
        System.out.println("终结符集:\t{" + String.join("、", VT) + "}\n");
    }

    // 2.1 从文法文件中读取语法,并将文法的每一行存放到list集合中
    public static ArrayList<String> readFile(File file) {
        System.out.println("分析的文法为:");
        ArrayList<String> result = new ArrayList<>(); // 要返回的存储了文件每一行的string集合

        try {
            BufferedReader br = new BufferedReader(new FileReader(file)); // 保存文件内容到缓存区
            String s; // 当前读取的一行

            while ((s = br.readLine()) != null) { // 逐行读入文件每行
                System.out.println("\t" + s);
                result.add(s.trim()); // 把当前行消除空格并存入result
            }

            br.close();
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }

        return result;
    }

    // 2.2 添加 终结符VT 和 映射值mapValue
    private static void addRightToVTAndMapValue(ArrayList<String> right, ArrayList<ArrayList<String>> mapValue) {
        VT.addAll(right); // VT终结符集合 加入right右部集合中的每个元素(这次移入会把终结符和非终结符一起移入,非终结符会在后续删掉)
        mapValue.add(new ArrayList<>(right)); // mapVlue列表加入right集合 并且这个集合是新new的 修改right集合不会影响mapVaue列表
    }



    // 3. 消除左递归
    private static void reformMap() {
        boolean isEditMap = false; // 标志MAP是否被修改
        Set<String> keys = new HashSet<>(MAP.keySet()); // 保存MAP中所有去重的key【即非终结符】
        Iterator<String> it = keys.iterator(); // keys的迭代器
        ArrayList<String> nullRight = new ArrayList<>(); // 空串右部
        nullRight.add("ε"); // ε是空串

        while (it.hasNext()) { // 还有下一个非终结符
            String left = it.next(); // 当前产生式的左部非终结符1
            boolean isReflag = false; // 标志是否有左递归
            ArrayList<ArrayList<String>> rightList = MAP.get(left); // 产生式原本的右部列表,例如[ ["S","a","c"], ["b"] ],用来遍历
            ArrayList<String> noReRightElement = new ArrayList<>(); // 不递归的产生式的右部元素
            ArrayList<ArrayList<String>> yesReRightList = new ArrayList<>(); // 递归的产生式的右部列表

            // 消除直接左递归
            for (ArrayList<String> strings : rightList) { // 遍历当前产生式的所有右部列表,strings例如["S","a","c","b"]
                ArrayList<String> yesReRightElement = new ArrayList<>(); // 递归的产生式的右部元素

                if (strings.get(0).equals(left)) { // 该右部是左递归的:右部的第一个字符是左部,例如["S","a","c"]中的"S"
                    isReflag = true; // 设置左递归标志为true

                    for (int j = 1; j < strings.size(); j++) { // 继续遍历剩下的右部
                        yesReRightElement.add(strings.get(j)); // 递归的产生式的右部元素:加入除了左部(第一个符)的所有右部,如["a","c"]
                    }
                    yesReRightElement.add(left + "'"); // 递归的产生式的右部元素:继续加入产生式左部的[']版。如["a","c","S'"]
                    yesReRightList.add(yesReRightElement); // 递归的产生式的右部列表:放入递归的产生式的新右部[["a","c","S'"]]
                }
                else { // 该右部不是左递归的
                    noReRightElement.addAll(strings); // 不递归的产生式的右部元素:加入原产生式的右部例如["b"]
                    noReRightElement.add(left + "'"); // 不递归的产生式的右部元素:继续加入左部的[']版,例如["b","S'"]
                }
            }

            if (isReflag) { // 如果存在左递归,则更新MAP

                isEditMap = true; // 更新MAP的标志为true

                yesReRightList.add(nullRight); // 递归的产生式的右部列表:继续加入空串右部元素["ε"], 例如[["a","c","S'"],["ε"]]
                MAP.put(left + "'", yesReRightList); // 加入递归的产生式,左部是原左部加['],右边是递归的产生式的右部列表 map如S'->acS'| ε

                VN.add(left + "'"); // 加入新的非终结符到VN集合
                VT.add("ε"); // 加入新的[ε]空串到VT集合

                ArrayList<ArrayList<String>> newLeftOld = new ArrayList<>(); // 存放不递归的新产生式 如S->bS'
                newLeftOld.add(noReRightElement); // 存放旧的产生式
                MAP.put(left, newLeftOld); // 更新原产生式
            }
        }

        // 如果文法被修改,则输出修改后的文法
        if (isEditMap) {
            System.out.println("消除左递归后的文法:");
            Set<String> kSet = new HashSet<>(MAP.keySet()); // 找到文法的所有非终结符

            for (String k : kSet) { // 遍历该文法每个非终结符
                ArrayList<ArrayList<String>> rightList = MAP.get(k); // 找到该非终结符对应的右部列表 如[["a","c","S'"],["ε"]]

                System.out.print("\t" + k + "→");

                for (int i = 0; i < rightList.size(); i++) { // 遍历右部列表 如[["a","c","S'"],["ε"]]
                    System.out.print(String.join("", // 把该右部列表的第i个右部元素拼接为一个字符串 如["a","c","S'"]拼接为["acS'"]
                            rightList.get(i).toArray(new String[rightList.get(i).size()])));

                    if (i + 1 < rightList.size()) { // 该右部列表还有下一个右部元素
                        System.out.print("|"); // 用[|]分隔
                    }
                }

                System.out.println();
            }
        }

//        MAP.toString(); // 输出新MAP

    }




    // 5. 求FIRST集合
    /*
        遇到终结符:加入FIRST集合
        遇到非终结符:递归,求该非终结符的FIRST集合,即为该终结符的FIRST集合
     */
    private static void getFirst() {
        System.out.println("\n该文法的FIRST集合:");

        for (String vn : VN) { // 遍历每个非终结符
            HashSet<String> FIRST_List = new HashSet<>(); // 当前非终结符vn的FIRST集合,包括该vn的各个产生式各自的FIRST集合
            ArrayList<ArrayList<String>> rightList = MAP.get(vn); // 当前非终结符vn对应的右部列表 如[["a","c","S'"], ["ε"]]
            // System.out.println(key+":");

            for (ArrayList<String> rightListElement : rightList) { // 遍历每个右部列表的每个右部元素(产生式) 如["a","c","S'"]
                // 当前非终结符的当前产生式的FIRST集合 即组成当前非终结符vn的FIRST集合的一部分
                HashSet<String> FIRST_List_One = new HashSet<>();

                // 将右部列表中的单个元素拼接成字符串 如["a","c","S'"]拼接为"acS"
                String oneRightElement = String.join("",
                        rightListElement.toArray(new String[rightListElement.size()]));
                // System.out.println("oneLeft: "+oneLeft);

                if (VT.contains(rightListElement.get(0))) { // 如果产生式第一个符号是终结符 则该符号是FIRST集合的符号
                    FIRST_List.add(rightListElement.get(0)); // 将该符号加入该产生式的FIRST集合
                    FIRST_List_One.add(rightListElement.get(0));
                    oneLeftFirst.put(vn + "$" + rightListElement.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
                }
                else { // 如果产生式第一个符号是非终结符
                    // 标记产生式中的符号是否为非终结符,是则检查下一个字符
                    boolean[] isVn = new boolean[rightListElement.size()];
                    isVn[0] = true; // 第一个为非终结符
                    int p = 0;

                    while (isVn[p]) { // 循环检查产生式中的每个符号 直到找到终结符或者空串(ε)。

                        if (VT.contains(rightListElement.get(p))) { // 1. 如果遇到终结符
                            FIRST_List.add(rightListElement.get(p)); // 则把该终结符加入当前非终结符的FIRST集合中
                            FIRST_List_One.add(rightListElement.get(p)); // 把该终结符加入当前产生式的FIRST集合中
                            oneLeftFirst.put(vn + "$" + rightListElement.get(p), vn + "→" + oneRightElement); // 添加到预测分析表中
                            break; // 找到终结符了,退出循环
                        }

                        // 2. 如果不是终结符
                        String vnGo = rightListElement.get(p); // 当前符号是非终结符,转换为找该非终结符的FIRST集合
                        Stack<String> stack = new Stack<>();
                        stack.push(vnGo); // 将当前非终结符放入栈中

                        while (!stack.isEmpty()) { // 如果栈不为空:是非终结符 转换为找该终结符的FIRST集合
                            ArrayList<ArrayList<String>> listGo = MAP.get(stack.pop()); // 获取该非终结符vnGo的右部
                            for (ArrayList<String> listGoCell : listGo) { // 遍历该非终结符vnGo的右部的每个元素
                                if (VT.contains(listGoCell.get(0))) { // 如果第一个字符是终结符 可能1:空 可能2:终结符

                                    if (listGoCell.get(0).equals("ε")) { // 可能1:空 可以查询下一个字符
                                        if (!vn.equals(START)) { // 如果当前的终结符不是开始符号(开始符号不能推出空)
                                            FIRST_List.add(listGoCell.get(0)); // 把空串加入FIRST集合
                                            FIRST_List_One.add(listGoCell.get(0));
                                            oneLeftFirst.put(vn + "$" + listGoCell.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
                                        }
                                        if (p + 1 < isVn.length) { // 继续处理下一个字符
                                            isVn[p + 1] = true;
                                        }
                                    }
                                    else { // 可能2:终结符 加入对应的FIRST集合
                                        FIRST_List.add(listGoCell.get(0));
                                        FIRST_List_One.add(listGoCell.get(0));
                                        oneLeftFirst.put(vn + "$" + listGoCell.get(0), vn + "→" + oneRightElement); // 添加到预测分析表中
                                    }

                                }
                                else { // 不是终结符号,入栈,再转换为找该非终结符的FIRST集合 以此递归
                                    stack.push(listGoCell.get(0));
                                }
                            }
                        }
                        p++;
                        if (p > isVn.length - 1)
                            break;
                    }
                }

                // 保存当前非终结符vn的产生式oneRightElement的FIRST元素到总FIRST集合中
                // key为当前产生式 如A->acS value为当前产生式的FIRST集合
                FIRST.put(vn + "→" + oneRightElement, FIRST_List_One);
            }

            FIRST.put(vn, FIRST_List); // 添加到总的FIRST集合中
            // 输出key的FIRST集合
            System.out.println(
                    "\tFIRST(" + vn + ")={" + String.join("、",
                            FIRST_List.toArray(new String[FIRST_List.size()])) + "}");
        }
    }

    // 求FOLLOW集合
    private static void getFollow() {
        System.out.println("\n该文法的FOLLOW集合:");
        // 遍历非终结符集合的迭代器
        Iterator<String> it = VN.iterator();
        // 创建一个HashMap keyFollow 用于存储每个非终结符的FOLLOW集合
        HashMap<String, HashSet<String>> keyFollow = new HashMap<>();

        // 存放特殊组合 如A->...B 或者 A->...Bε(结尾是非终结符/结尾是空串)
        ArrayList<HashMap<String, String>> vn_VnList = new ArrayList<>();

        // 存放vn_VnList的左边和右边
        HashSet<String> vn_VnListLeft = new HashSet<>();
        HashSet<String> vn_VnListRight = new HashSet<>();

        // 开始符号加入#,初始化keyFollow
        keyFollow.put(START, new HashSet() {
            private static final long serialVersionUID = 1L;
            {
                add("#");
            }
        });

        // 遍历非终结符集合
        while (it.hasNext()) { // 还有非终结符
            String currentVN = it.next(); // key为当前的非终结符
            ArrayList<ArrayList<String>> rightList = MAP.get(currentVN); // rightList为当前非终结符的右部 如[["a","c","S'"], ["ε"]]
            ArrayList<String> rightListOne; // rightListOne为当前非终结符的右部的其中一个右部元素 如["a","c","S'"]

            // 如果key的FOLLOW集合不存在,则初始化
            if (!keyFollow.containsKey(currentVN)) {
                keyFollow.put(currentVN, new HashSet<>());
            }
//            keyFollow.toString();

            // 遍历该非终结符的所有产生式
            for (ArrayList<String> strings : rightList) {
                rightListOne = strings; // rightListOne为当前非终结符的右部的其中一个右部元素 如["a","c","S'"]

                // (1) 如果非终结符号的后面找到了一个终结符
                for (int j = 1; j < rightListOne.size(); j++) {
                    HashSet<String> set = new HashSet<>(); // 当前产生式的FOLLOW集合
                    if (VT.contains(rightListOne.get(j))) { // 如果找到一个终结符
                        set.add(rightListOne.get(j)); // 把该终结符加入到当前产生式的FOLLOW集合
                        if (keyFollow.containsKey(rightListOne.get(j - 1)))
                            set.addAll(keyFollow.get(rightListOne.get(j - 1))); // 把上一个非终结符的FOLLOW集合加入当前的FOLLOW集合
                        keyFollow.put(rightListOne.get(j - 1), set); // 把当前产生式的FOLLOW集合加入到前一个非终结符的FOLLOW集合
                    }
                }

                // (2) 找...非终结符1非终结符2...组合
                    // 则FOLLOW(非终结符1) = FIRST(非终结符2)
                for (int j = 0; j < rightListOne.size() - 1; j++) {
                    HashSet<String> set = new HashSet<>();
                    if (VN.contains(rightListOne.get(j)) && VN.contains(rightListOne.get(j + 1))) {
                        set.addAll(FIRST.get(rightListOne.get(j + 1)));
                        set.remove("ε");

                        if (keyFollow.containsKey(rightListOne.get(j)))
                            set.addAll(keyFollow.get(rightListOne.get(j)));
                        keyFollow.put(rightListOne.get(j), set);
                    }
                }

                // (3) A->...B 或者 A->...Bε(可以有n个ε)的组合存起来
                for (int j = 0; j < rightListOne.size(); j++) {
                    HashMap<String, String> vn_Vn;
                    if (VN.contains(rightListOne.get(j)) && !rightListOne.get(j).equals(currentVN)) {// 是VN且A不等于B
                        boolean isAllNull = false;// 标记VN后是否为空
                        if (j + 1 < rightListOne.size())// 即A->...Bε(可以有n个ε)
                            for (int k = j + 1; k < rightListOne.size(); k++) {
                                if ((FIRST.containsKey(rightListOne.get(k)) && FIRST.get(rightListOne.get(k)).contains("ε"))) {// 如果其后面的都是VN且其FIRST中包含ε
                                    isAllNull = true;
                                } else {
                                    isAllNull = false;
                                    break;
                                }
                            }
                        // 如果是最后一个为VN,即A->...B
                        if (j == rightListOne.size() - 1) {
                            isAllNull = true;
                        }
                        if (isAllNull) {
                            vn_VnListLeft.add(currentVN);
                            vn_VnListRight.add(rightListOne.get(j));

                            // 往vn_VnList中添加,分存在和不存在两种情况
                            boolean isHaveAdd = false;
                            for (int x = 0; x < vn_VnList.size(); x++) {
                                HashMap<String, String> vn_VnListCell = vn_VnList.get(x);
                                if (!vn_VnListCell.containsKey(currentVN)) {
                                    vn_VnListCell.put(currentVN, rightListOne.get(j));
                                    vn_VnList.set(x, vn_VnListCell);
                                    isHaveAdd = true;
                                    break;
                                } else {
                                    // 去重
                                    if (vn_VnListCell.get(currentVN).equals(rightListOne.get(j))) {
                                        isHaveAdd = true;
                                        break;
                                    }
                                    continue;
                                }
                            }
                            if (!isHaveAdd) {// 如果没有添加,表示是新的组合
                                vn_Vn = new HashMap<>();
                                vn_Vn.put(currentVN, rightListOne.get(j));
                                vn_VnList.add(vn_Vn);
                            }
                        }
                    }
                }
            }
        }

        keyFollow.toString();

        // (4) vn_VnListLeft减去vn_VnListRight,剩下的就是入口产生式,
        vn_VnListLeft.removeAll(vn_VnListRight);

        // 用栈或者队列都行
        Queue<String> keyQueue = new LinkedList<>(vn_VnListLeft);

        // 处理 vn_VnListLeft 的每个元素
        while (!keyQueue.isEmpty()) {
            String keyLeft = keyQueue.poll();
            for (int t = 0; t < vn_VnList.size(); t++) {
                HashMap<String, String> vn_VnListCell = vn_VnList.get(t);
                if (vn_VnListCell.containsKey(keyLeft)) {
                    HashSet<String> set = new HashSet<>();
                    // 原来的FOLLOW加上左边的FOLLOW
                    if (keyFollow.containsKey(keyLeft))
                        set.addAll(keyFollow.get(keyLeft));
                    if (keyFollow.containsKey(vn_VnListCell.get(keyLeft)))
                        set.addAll(keyFollow.get(vn_VnListCell.get(keyLeft)));
                    keyFollow.put(vn_VnListCell.get(keyLeft), set);
                    keyQueue.add(vn_VnListCell.get(keyLeft));

                    // 移除已处理的组合
                    vn_VnListCell.remove(keyLeft);
                    vn_VnList.set(t, vn_VnListCell);
                }
            }
        }

        // 此时 keyFollow 为完整的FOLLOW集
        FOLLOW = keyFollow;

        // 打印FOLLOW集合
        for (String key : keyFollow.keySet()) {
            HashSet<String> f = keyFollow.get(key);
            System.out.println("\tFOLLOW(" + key + ")={" + String.join("、", f.toArray(new String[f.size()])) + "}");
        }
    }

    /*
      判断是不是LL(1)文法
       1. 无左递归:文法不能直接或间接地包含左递归。
       2. SELECT集合的交集为空:对于某个非终结符 A,如果 A 有两个产生式 A -> α 和 A -> β,那么必须满足:
         (1)如果 α 和 β 都能推导出空串(即包含 ε),则 FIRST(α) 与 FOLLOW(A) 必须没有交集。
         (2)如果 α 和 β 不都能推导出空串,则 FIRST(α) 和 FIRST(β) 必须没有交集。
    */
    private static boolean isLL1() {
        System.out.println("\n判断是否是LL(1)文法:");
        boolean flag = true; // 是否是LL(1)文法的标志

        for (String key : VN) { // 遍历非终结符
            ArrayList<ArrayList<String>> list = MAP.get(key);// 对于每一个非终结符 key,获取其产生式列表 list
            if (list.size() > 1) { // 如果该终结符的产生式有两个以上,则判断SELECT集合的交集
                for (int i = 0; i < list.size(); i++) {
                    String a = String.join("", list.get(i));
                    for (int j = i + 1; j < list.size(); j++) {
                        String b = String.join("", list.get(j));

                        // 对于每一对产生式,如果其中一个是空产生式(ε),则要求该非终结符对应的 FIRST 集合和 FOLLOW 集合的交集为空
                        if (a.equals("ε") || b.equals("ε")) {
                            // (1)若a=ε,则判断FOLLOW(A) ∩ FIRST(b) = φ 相当于FOLLOW(Key) ∩ FIRST(b) = φ
                            // (2)若b=ε, 则判断FIRST(a) ∩ FOLLOW(B)=φ 相当于FIRST(a) ∩ FOLLOW(Key) = φ
                            Set<String> retainSet = new HashSet<>(FIRST.get(key));
                            if (FOLLOW.get(key) != null) {
                                retainSet.retainAll(FOLLOW.get(key)); // 求A的FOLLOW集合与FIRST集合的交集
                            }

                            if (!retainSet.isEmpty()) { // 如果交集不为空,说明存在冲突
                                flag = false;// 不是LL(1)文法,输出FIRST(a)FOLLOW(a)的交集
                                printIntersectionSet("\tFIRST(" + key + ") ∩ FOLLOW(" + key + ")", retainSet);
                                break;
                            } else {
                                System.out.println("\tFIRST(" + key + ") ∩ FOLLOW(" + key + ") = φ");
                            }//如果交集不为空,说明存在冲突,输出交集并将标志 flag 设置为 false
                        }
                        else { // (2)如果a和b都不为空 则要FIRST(a) ∩ FIRST(b) = φ
                            Set<String> retainSet = new HashSet<>(FIRST.get(key + "→" + a));
                            retainSet.retainAll(FIRST.get(key + "→" + b));
                            if (!retainSet.isEmpty()) { // 1. 如果FIRST(a)和FIRST(b)的交集不为空
                                flag = false;// 不是LL(1)文法,输出FIRST(a)FIRST(b)的交集
                                printIntersectionSet("\tFIRST(" + a + ") ∩ FIRST(" + b + ")", retainSet);
                                break;
                            }
                            else { // 2. 如果FIRST(a)和FIRST(b)的交集为空
                                System.out.println("\tFIRST(" + a + ") ∩ FIRST(" + b + ") = φ");
                            }
                        }
                    }
                }
            }
        }
        if (flag)
            System.out.println("\t是LL(1)文法,继续分析!");
        else
            System.out.println("\t不是LL(1)文法,退出分析!");
        return flag;
    }


    /*
      输出
    */
    private static void printIntersectionSet(String prefix, Set<String> set) {
        System.out.println(prefix + " = {" + String.join("、", set) + "}");
    }


    /*
      构建预测分析表FORM
      遍历非终结符和终结符集合,利用 oneLeftFirst 和 FOLLOW 集合填充预测分析表
    */
    private static void preForm() {
        HashSet<String> setVT = new HashSet<>(VT); // 终结符集setVT
        setVT.remove("ε"); // 去除空串 "ε"
        FOCAST_TABLE = new String[VN.size() + 1][setVT.size() + 2]; // 预测分析表:行是非终结符,列是终结符的二维数组
        Iterator<String> itVn = VN.iterator(); // 非终结符集VN的迭代器
        Iterator<String> itVt = setVT.iterator(); // 终结符集VT的迭代器

        // (1)第一个循环遍历 FORM,根据迭代器填充第一行和第一列
        // 同时填充不是空串的,即FIRST集合
        for (int i = 0; i < FOCAST_TABLE.length; i++) {
            for (int j = 0; j < FOCAST_TABLE[0].length; j++) {
                if (i == 0 && j > 0) { // 第一行 填充终结符VT
                    if (itVt.hasNext()) {
                        FOCAST_TABLE[i][j] = itVt.next();
                    }
                    if (j == FOCAST_TABLE[0].length - 1) // 最后一列 加入 [#]
                        FOCAST_TABLE[i][j] = "#";
                }
                if (j == 0 && i > 0) { // 第一列 填充非终结符VN
                    if (itVn.hasNext())
                        FOCAST_TABLE[i][j] = itVn.next();
                }
                if (i > 0 && j > 0) { // 不是第一行和第一列,且不是空串(则填充FIRST集合)
                    String oneLeftKey = FOCAST_TABLE[i][0] + "$" + FOCAST_TABLE[0][j];// 作为key查找其First集合
                    FOCAST_TABLE[i][j] = oneLeftFirst.get(oneLeftKey);
                }
            }
        }

        // (2)第二个循环 处理空串的情况 填充 FOLLOW 集合
        for (int i = 1; i < FOCAST_TABLE.length; i++) {
            String oneLeftKey = FOCAST_TABLE[i][0] + "$ε"; // 非终结符+"$ε"

            if (oneLeftFirst.containsKey(oneLeftKey)) { // 如果有空产生式 则填充FOLLOW集合
                HashSet<String> follows = FOLLOW.get(FOCAST_TABLE[i][0]); // 获取空产生式的FOLLOW集合
                for (String vt : follows) { // 遍历FOLLOW集合中的元素(终结符)
                    for (int j = 1; j < FOCAST_TABLE.length; j++)
                        for (int k = 1; k < FOCAST_TABLE[0].length; k++) {
                            if (FOCAST_TABLE[j][0].equals(FOCAST_TABLE[i][0]) && FOCAST_TABLE[0][k].equals(vt)) { // 找到要填充的行、列
                                FOCAST_TABLE[j][k] = oneLeftFirst.get(oneLeftKey); // 对应位置填写当前key的产生式
                            }
                        }
                }
            }
        }
        // 打印预测表
        printForm();
        //将预测分析表的数据存储到Map中,以便于快速查找
        buildPreMap();
    }

    // 打印预测表
    private static void printForm() {
        System.out.println("\n该文法的预测分析表为:");
        for (String[] line : FOCAST_TABLE) {
            for (String column : line) {
                System.out.print((column != null ? column : " ") + "\t"); // 若不为空则输出当前行所在的列的内容
            }
            System.out.println();
        }
        System.out.println();
    }

    // 根据FROM建立Map
    private static void buildPreMap() {
        for (int i = 1; i < FOCAST_TABLE.length; i++) {
            for (int j = 1; j < FOCAST_TABLE[0].length; j++) {
                if (FOCAST_TABLE[i][j] != null) {
                    String[] tmp = FOCAST_TABLE[i][j].split("→");
                    preMap.put(FOCAST_TABLE[i][0] + FOCAST_TABLE[0][j], tmp[1]);
                }
            }
        }
    }

    // 根据LL(1)分析表来分析输入串
    public static void printAutoPre(String str) {
        System.out.println(str + "的分析过程:");
        Deque<String> queue = new LinkedList<>(); // 队列queue 用于存储单词串的分割

        for (int i = 0; i < str.length(); i++) {
            StringBuilder t = new StringBuilder().append(str.charAt(i));
            if (i + 1 < str.length() && (str.charAt(i + 1) == '\'' || str.charAt(i + 1) == '’')) {
                t.append(str.charAt(i + 1)); // 将输入的单词串按照一定规则分割成单个符号,并存储在队列中,同时在符号后面的字符是 ' 或 ’ 时,将其合并。
                i++;
            }
            queue.offer(t.toString());
        }
        queue.offer("#"); // 将结束符 # 加入队列末尾。

        // 分析栈
        Deque<String> stack = new LinkedList<>();
        stack.push("#"); // "#"开始
        stack.push(START); // 初态为开始符号

        boolean isSuccess = false; // 分析成功的标志
        int step = 1; // 步骤数初始为1

        while (!stack.isEmpty()) { // 进行循环分析,直到分析栈为空
            String left = stack.peek();
            String right = queue.peek(); // 检查分析栈栈顶 left 和队列头部 right

            // (1)分析成功
            if (left.equals(right) && right.equals("#")) { // 栈和输入串相等,且输入串为#
                isSuccess = true;
                System.out.println((step++) + "\t#\t#\t" + "分析成功");
                break; // 结束循环
            }

            // (2)规约
            if (left.equals(right)) { // 栈顶和当前符号匹配,均为终结符
                System.out.println((step++) + "\t" + toString(stack) + "\t" + toString(queue) + "\t\"" + left + "\"\t匹配");
                stack.pop(); // 出栈
                queue.poll(); // 出队列
                continue; // 继续
            }

            // (3)产生式推导
            if (preMap.containsKey(left + right)) {
                System.out.println((step++) + "\t" + toString(stack) + "\t" + toString(queue) + "\t产生式" + left + "→"
                        + preMap.get(left + right));
                stack.pop();

                String tmp = preMap.get(left + right); // 从预测分析表 preMap 中查找对应产生式,输出相应信息,将产生式逆序入栈
                for (int i = tmp.length() - 1; i >= 0; i--) { // 逆序进栈
                    StringBuilder t = new StringBuilder();

                    if (tmp.charAt(i) == '\'' || tmp.charAt(i) == '’') {
                        t.append(tmp.charAt(i-1)).append(tmp.charAt(i));
                        i--;
                    }
                    else {
                        t.append(tmp.charAt(i));
                    }

                    if (!t.toString().equals("ε")) { // 不为空串则入栈
                        stack.push(t.toString());
                    }
                }
                continue;
            }

            //(4)其他情况:不规约,无推导,不成功,则直接退出
            break;
        }

        if (!isSuccess) { // 如果没有成功就中途退出
            System.out.println((step++) + "\t#\t#\t" + "分析失败");
        }

    }

    // 将集合的转化为字符串
    private static String toString(Collection<String> collection) {
        return String.join("", collection.toArray(new String[0]));
    }
}

2. 算符优先文法

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.*;
import java.util.stream.Collectors;

public class TestLab3SFYX {
    //FIRSTVT集合
    private static Map<Character, Set<Character>> firstVt = new HashMap<>();
    //LASTVT集合
    private static Map<Character, Set<Character>> lastVt = new HashMap<>();
    //输入的文法
    private static List<String> input = new ArrayList<>();
    //终结符
    private static Set<Character> End = new LinkedHashSet<>();
    //非终结符
    private static Set<Character> NoEnd = new LinkedHashSet<>();
    //算符矩阵
    private static Map<String, Character> matrix = new HashMap<>();
    //输入流
    private static Scanner in = new Scanner(System.in);
    //文法的左右分割一一对应
    private static Map<Character, List<String>> produce = new HashMap<>();

    // TODO 主方法
    public static void main(String[] args) {
        String path = "grammer2.txt";
        int flag = 1;
        while (flag != 0) {
            input = readFile(new File(path));
            if (isOperator()) {
                System.out.println("此文法是算符文法!");
                flag = 0;
            } else {
                System.out.println("此文法不是算符文法!请重新输入:");
                input.clear();
            }
        }
        //获取终结符
        getVT();
        //获取非终结符
        getVN();
        //分离文法
        getProduce();
        //计算并显示FirstVT、LastVT集合
        DisplayFirstVT_LastVT();
        //构造算符优先矩阵并打印
        constructMatrix();
        //分析句子过程
        analysisProcess();
    }

    //TODO 读取文法文件
    public static ArrayList<String> readFile(File file) {
        System.out.println("从文件读入的文法为:");// 输出读取的文法内容
        ArrayList<String> result = new ArrayList<>();
        try {
            BufferedReader br = new BufferedReader(new FileReader(file));
            String s;
            while ((s = br.readLine()) != null) {
                System.out.println("\t" + s);
                result.add(s.trim());
            }
            br.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());// 如果发生异常,输出错误信息
        }
        return result;
    }

    // TODO 判断是否是算符文法
    /**
     * 判断其是不是算符文法
     * 遍历每个产生式,检查是否有连续的非终结符相连,如果有,则不是算符文法
     * 如果没有连个连续非终结符号相连的就是算符优先文法,返回判断结果
     */
    private static boolean isOperator() {
        int i;
        for (i = 0; i < input.size(); i++) {
            for (int j = 0; j < input.get(i).length() - 1; j++) {
                String str = input.get(i);
                if (str.charAt(j) >= 65 && str.charAt(j) <= 90) {
                    if ((str.charAt(j + 1) >= 65 && str.charAt(j + 1) <= 90)) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    // TODO 求终结符
    /**
     * 获取文法中的终结符
     * 遍历每个产生式,将终结符添加到End集合中
     */
    private static void getVT() {
        for (String temp : input) {
            for (int j = 2; j < temp.length(); j++) {
                if (temp.charAt(j) < 65 || temp.charAt(j) > 90 && temp.charAt(j) != '|') {
                    End.add(temp.charAt(j));
                }
            }
        }
        End.add('#');
    }
    // TODO 求非终结符
    /**
     * 获取文法中的非终结符
     * 遍历每个产生式,将非终结符添加到NoEnd集合中
     */
    private static void getVN() {
        for (String temp : input) {
            for (int j = 2; j < temp.length(); j++) {
                if (temp.charAt(j) >= 65 && temp.charAt(j) <= 90) {
                    NoEnd.add(temp.charAt(j));
                }
            }
        }
    }

    //TODO 分离文法
    /**
     * 分离文法,将每一行的文法分离成左右两部分
     * 将左部分作为键,右部分作为值存入produce集合中
     * 将每一行的文法分离,如E->E+T|T分离成E和E+T,T
     */
    private static void getProduce() {
        for (String s : input) {
            List<String> list = new ArrayList<>();
            String str = s;
            StringBuffer a = new StringBuffer();
            for (int j = 2; j < str.length(); j++) {
                if (str.charAt(j) != '|') {
                    a.append(str.charAt(j));
                } else {
                    list.add(a.toString());
                    a.delete(0, a.length());//清空a
                }
            }
            list.add(a.toString());
            produce.put(str.charAt(0), list);
        }
    }
    // TODO 计算firstVt集合
    /**
     *  遍历文法,根据文法规则计算FirstVT集合,结果存储在fvt参数中
     * 获得firstVt集合
     */
    private static void getFirstVT(Character s, Set<Character> fvt) {
        String str = null;
        int i = 0;
        for (i = 0; i < input.size(); i++) {
            if (input.get(i).charAt(0) == s) {
                str = input.get(i);
            }
        }
        if (str == null)
            return;
        //logger.info("求firstVt集合:" + str);
        boolean flag = true;
        for (i = 2; i < str.length(); i++){
            if (str.charAt(i) < 65 || str.charAt(i) > 90){
                //P->a.....,即以终结符开头,该终结符入Firstvt
                if ((String.valueOf(str.charAt(i - 1)).equals("→")) || str.charAt(i - 1) == '|') {
                    fvt.add(str.charAt(i));
                    flag = false;
                }
            }
            if (str.charAt(i) == '|')
                flag = true;
            //P->Qa....,即先以非终结符开头,紧跟终结符,则终结符入Firstvt
            if (str.charAt(i) >= 65 && str.charAt(i) <= 90){
                int index = i + 1;
                if (index < str.length()){
                    if ((str.charAt(index) < 65 || str.charAt(index) > 90)
                            && End.contains(str.charAt(index)) && flag){
                        fvt.add(str.charAt(index));
                    }
                }
            }
            //若有P->Q.....,即以非终结符开头,该非终结符的Firstvt加入P的Firstvt
            if ((str.charAt(i - 1) == '|' || String.valueOf(str.charAt(i-1)).equals("→"))
                    && str.charAt(i) >= 65 && str.charAt(i) <= 90) {
                //E->E
                if (str.charAt(i) == str.charAt(0)) {
                    continue;
                }
                //递归
                //E->P
                //logger.info("firstVt集合进入递归");
                getFirstVT(str.charAt(i), fvt);
            }

        }
    }
    // TODO 计算lastVt集合
    /**
     * 遍历文法,根据文法规则计算LastVT集合,结果存储在lvt参数中
     * 获得lastVt集合
     */
    private static void getLastVT(Character s, Set<Character> lvt) {
        String str = null;
        int i;
        for (i = 0; i < input.size(); i++) {
            if (input.get(i).charAt(0) == s) {
                str = input.get(i);
            }
        }
        if (str == null)
            return;
        for (i = 2; i < str.length(); i++) {
            if (str.charAt(i) < 65 || str.charAt(i) > 90) {
                //P->....aW,即先以非终结符结尾,前面是终结符,则终结符入Lastvt,Q处于产生式最后一位的情况
                if (i == str.length() - 1 ||
                        (i == str.length() - 2 &&
                                str.charAt(i + 1) >= 65 && str.charAt(i + 1) <= 90 &&
                                str.charAt(i) != '|' && !String.valueOf(str.charAt(i)).equals("→")
                        )
                ) {
                    lvt.add(str.charAt(i));
                }
                if (i < str.length() - 2) {
                    //P->aQ | ,即先以非终结符结尾,前面是终结符,则终结符入Lastvt
                    if (str.charAt(i + 1) == '|' ||
                            (str.charAt(i + 2) == '|' && str.charAt(i + 1) >= 65 && str.charAt(i + 1) <= 90)
                    ) {
                        lvt.add(str.charAt(i));
                    }
                }
            } else {
                //P->....Q,即以非终结符结尾,该非终结符的Lastvt入P的Lastvt
                if (i == str.length() - 1) {
                    if (str.charAt(i) == str.charAt(0)) {
                        continue;
                    }
                    getLastVT(str.charAt(i), lvt);
                } else if (str.charAt(i + 1) == '|') {
                    if (str.charAt(i) == str.charAt(0)) {
                        continue;
                    }
                    //P->....Q,即以非终结符结尾,该非终结符的Lastvt入P的Lastvt
                    getLastVT(str.charAt(i), lvt);
                }
            }
        }
    }

    /**
     * 显示FirstVT集合和LastVT集合
     * 遍历文法,计算并显示每个非终结符的FirstVT和LastVT集合
     */
    //TODO 显示firstVt集合和lastVt集合----一次优化
    private static void DisplayFirstVT_LastVT() {
        for (String value : input) {
            Set<Character> fvt = new HashSet<>();
            getFirstVT(value.charAt(0), fvt);
            firstVt.put(value.charAt(0), fvt);

            Set<Character> lvt = new HashSet<>();
            getLastVT(value.charAt(0), lvt);
            lastVt.put(value.charAt(0), lvt);
        }

        displaySet("firstVt", firstVt);
        displaySet("lastVt", lastVt);
    }
    //输出
    private static void displaySet(String setName, Map<Character, Set<Character>> set) {
        System.out.printf("%s集合如下:\n", setName);
        for (Map.Entry<Character, Set<Character>> entry : set.entrySet()) {
            String valueString = String.join(",", entry.getValue().stream().map(String::valueOf).collect(Collectors.toList()));
            System.out.printf("%s(%s): {%s}\n", setName, entry.getKey(), valueString);
        }
    }
    /**
     * 错误
     */
    public static void partError() {
        matrix.put(")(", 'b');
        matrix.put("((", 'b');
        matrix.put("(#", 'a');
    }

    //TODO 构造算符优先矩阵并打印
    /**
     * 构造算符优先矩阵并打印
     * 用Map<String,Character>存,String中存优先变得行值和列值,
     * Character表示String中所存行列的大小关系如"++"表示行为'+',列为'+'的时候,关系为Character中的值
     * 遍历文法,根据文法规则构造算符优先矩阵
     * 结果存储在matrix集合中
     */
    private static void constructMatrix() {
        for (int i = 0; i < input.size(); i++) {
            String str = input.get(i);
            for (int j = 2; j < input.get(i).length(); j++) {
                //每个产生式的字符,判断是否为终结符并且不是竖线('|')
                if ((str.charAt(j) < 65 || str.charAt(j) > 90) && (str.charAt(j) != '|')) {
                    //当前字符后面有一个字符,并且该字符也是终结符,并且不是竖线('|'),将当前字符和后面的字符作为键,
                    // 将等于号('=')作为值,存入matrix中。P->...ab....
                    if (j < str.length() - 1
                            && (str.charAt(j + 1) < 65 || str.charAt(j + 1) > 90)
                            && (str.charAt(j + 1) != '|')
                    ) {
                        String temp = str.charAt(j) + "" + str.charAt(j + 1);
                        matrix.put(temp, '=');
                    } else {
                        //当前字符后面有两个字符,并且第二个字符也是非终结符,并且不是竖线('|'),将当前字符和第二个字符作为键,
                        //将等于号('=')作为值,存入matrix中。
                        //如 P->....aBc
                        if (j < str.length() - 2
                                && (str.charAt(j + 2) < 65 || str.charAt(j + 2) > 90)
                                && (str.charAt(j + 2) != '|')
                        ) {
                            matrix.put(str.charAt(j) + "" + str.charAt(j + 2), '=');
                        }
                    }
                    //当前字符后面有一个字符,并且该字符是非终结符,将该字符的firstVt集合中的每个元素与当前字符作为键,
                    // 将小于号('<')作为值,存入matrix中。  如 +T 则有 + < FirstV(T)
                    if (j < str.length() - 1 && str.charAt(j + 1) >= 65 && str.charAt(j + 1) <= 90) {
                        Set<Character> coll = firstVt.get(str.charAt(j + 1));
                        for (Character value : coll) {
                            //logger.info("创建矩阵测试2:"+str.charAt(j+1) + ":" + value);
                            matrix.put(str.charAt(j) + "" + value, '<');
                        }
                    }
                    //当前字符前面不是产生式的起始符号,并且当前字符前面的字符是非终结符,将该字符的lastVt集合中的每个元素与当前字符作为键,
                    // 将大于号('>')作为值,存入matrix中。  如 E+ 则有 LastV(E) > +
                    if (j - 1 != 1 && str.charAt(j - 1) >= 65 && str.charAt(j - 1) <= 90) {
                        Set<Character> coll = lastVt.get(str.charAt(j - 1));
                        for (Character value : coll) {
                            matrix.put(value + "" + str.charAt(j), '>');
                        }
                    }
                }
            }
        }
        //起始符号的firstVt集合中的每个元素与'#'作为键,将小于号('<')作为值,存入matrix中。
        Set<Character> coll = firstVt.get(input.get(0).charAt(0));
        for (Character value : coll) {
            matrix.put('#' + "" + value, '<');
        }
        //起始符号的lastVt集合中的每个元素与'#'作为键,将大于号('>')作为值,存入matrix中。
        Set<Character> coll1 = lastVt.get(input.get(0).charAt(0));
        for (Character value : coll1) {
            matrix.put(value + "" + '#', '>');
        }
        //处理部分错误情况
        partError();
        //遍历所有的终结符,并在matrix中找到对应的优先关系。
        //如果没有找到对应的关系,则将键值对存入matrix中,其中值为' '。
        for (Character value : End) {
            for (Character value1 : End) {
                matrix.putIfAbsent(value + "" + value1, ' ');
            }
        }
        //将"##"作为键,将等于号('=')作为值,存入matrix中。
        matrix.put("##", '=');

        getVT();
        //打印构造的算符优先矩阵
        printMatrix();
    }

    /**
     * 打印算符优先矩阵
     * 遍历终结符,输出构造的算符优先矩阵
     */
    private static void printMatrix(){
        System.out.println("\n构造的算符优先关系表如下:");
        int kong = 0;
        for (Character value : End) {
            if (kong == 0) {
                System.out.print("   ");
            }
            kong++;
            System.out.print(value);
            if (kong != End.size()) {
                System.out.print("  ");
            }
        }
        System.out.println();
        for (Character value : End) {
            System.out.print(value);
            for (Character value1 : End) {
                Character ch = matrix.get(value + "" + value1);
                if (ch != null) {
                    System.out.print("  " + ch);
                } else {
                    System.out.print("  " + " ");
                }
            }
            System.out.println();
        }
    }


    /**
     * 判断其是不是终结符
     */
    private static boolean isEnd(Character ch) {
        for (Character value : End) {
            if (value.equals(ch)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断其是不是非终结符
     */
    private static boolean isNoEnd(Character ch) {
        for (Character value : NoEnd) {
            if (value.equals(ch)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据产生式右部分返回左边
     */
    private static char retLeft(String str) {
        char ch;
        // 遍历文法的产生式
        for (Map.Entry<Character, List<String>> map : produce.entrySet()) {
            ch = map.getKey();
            // 遍历每个产生式的右部
            for (String value : map.getValue()) {
                // 检查右部的长度是否与给定的字符串相同
                if (value.length() != str.length()) {
                    continue;
                }
                int i;
                for (i = 0; i < str.length(); i++) {// 比较每个字符
                    if (str.charAt(i) >= 65 && str.charAt(i) <= 90) {// 如果是非终结符
                        if (value.charAt(i) >= 65 && value.charAt(i) <= 90) {// 都是非终结符,继续比较
                        } else {
                            break;// 产生式与给定字符串不匹配,跳出循环
                        }
                    } else {
                        // 如果是终结符,直接比较字符
                        if (value.charAt(i) != str.charAt(i)) {
                            break; // 产生式与给定字符串不匹配,跳出循环
                        }
                    }
                }
                if (i == str.length()) {
                    return ch;// 如果遍历完成,说明匹配成功,返回左部非终结符
                }
            }
        }
        return 0;// 没有匹配的产生式,返回0
    }

    //TODO 将字符数组转换成字符串
    /**
     * 将字符数组转换成字符串
     * @param list  字符列表
     * @return      对应的字符串
     */
    public static String replaceToString(List<Character> list) {
        StringBuffer a = new StringBuffer();
        for (Character value : list) {
            if (value != ',' && value != '[' && value != ']') {
                a.append(value);
            }
        }
        return a.toString();
    }

    //TODO 分析过程
    /**
     * 算符优先分析过程
     * 使用一个符号栈,用它寄存终结符和非终结符,k代表符号栈的深度
     * 在正常情况下,算法工作完毕时,符号栈S应呈现:#N
     */
    public static void analysisProcess() {
        int status = 0;// 操作类型标志,0表示移进,1表示规约
        int count = 0;// 计数器,记录分析步骤
        int k = 0;//符号栈深度的指针,用于标记在符号栈中的当前位置
        int j = 0;// 回溯指针,用于在规约过程中找到前一个终结符
        int step = 0;//计步器,用于记录当前处理到句子的哪个字符
        String gui = null;//字符串,用于存储规约时的显示信息
        System.out.println("请输入要分析的句子");
        //输入的句子,需要被分析的字符串
        String analyze;
        analyze = in.nextLine();
        if (analyze.charAt(analyze.length() - 1) != '#') {
            analyze = analyze + "#";
        }
        /* 符号栈的初始化,符号栈用于存储待处理的符号,初始时栈底为'#' */
        //符号栈,用于存储待处理的符号。它是一个字符的列表,可以存储输入字符串中的字符和非终结符。
        List<Character> listStack = new ArrayList<>();
        //格式化字符串"%-8s%-20s%-8s%-10s%-8s\n"中的每个%-Ns表示一个左对齐的字符串,其中N是该字符串的最小宽度
        System.out.printf("%-8s%-20s%-8s%-10s%-8s\n", "步骤", "栈", "a读入", "剩余串", "操作");
        //栈底 #
        listStack.add('#');
        //当前正在处理的字符
        char a = analyze.charAt(step++);


        /* 分析过程主体 */
        do {
            if (status == 0) {
                if (count != 0) {
                    System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", "移进", count,
                            replaceToString(listStack), a, analyze.substring(step)
                    );
                } else {
                    System.out.printf("%-8d %-20s %-8c %-10s", count,
                            replaceToString(listStack), a, analyze.substring(step)
                    );
                }
            } else {
                System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", gui, count,
                        replaceToString(listStack), a, analyze.substring(step)
                );
            }
            char ch = listStack.get(k);
            if (isEnd(ch)) {
                j = k;
            } else if (j >= 1) {
                j = k - 1;
            }
            char temp;
            if (matrix.get(listStack.get(j) + "" + a) != null) {
                //规约
                //检查符号栈顶的字符和当前输入字符之间的优先关系。如果栈顶的符号优先级高,那么执行规约操作
                while (matrix.get(listStack.get(j) + "" + a).equals('>')) {
                    if (listStack.size() == 2 && a == '#') {
                        //#E这种情况
                        break;
                    }



                    StringBuffer judge = new StringBuffer();
                    do {
                        temp = listStack.get(j);
                        if (isEnd(listStack.get(j - 1))) {
                            j = j - 1;
                        } else {
                            j = j - 2;
                        }
                    } while (!matrix.get(listStack.get(j) + "" + temp).equals('<'));

                    for (int i = j + 1; i < listStack.size(); i++) {
                        judge.append(listStack.get(i));
                    }
                    int te = listStack.size();
                    for (int t = j + 1; t < te; t++) {
                        listStack.remove(j + 1);
                    }
                    //将符号栈中的一些字符替换为一个非终结符
                    char res = retLeft(judge.toString());
                    if (res != 0) {
                        count++;
                        k = j + 1;
                        listStack.add(res);
                        status = 1;
                        gui = "用" + res + "->" + judge + "规约";
                        if (status == 0) {
                            System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", "移进", count,
                                    replaceToString(listStack), a, analyze.substring(step)
                            );
                        } else {
                            System.out.printf("%-8s\n%-8d %-20s %-8c %-10s", gui, count,
                                    replaceToString(listStack), a, analyze.substring(step)
                            );
                        }
                    }
                }
            }
            //移进---将当前输入字符'a'添加到符号栈中。
            if (matrix.get(listStack.get(j) + "" + a).equals('<')
                    || matrix.get(listStack.get(j) + "" + a).equals('=')
            ) {
                count++;
                k++;
                status = 0;
                listStack.add(a);
            }else{
                //输入字符'a'和符号栈顶的字符不能形成有效的优先关系,那么输出错误信息并退出程序。
                switch (matrix.get(listStack.get(j) + "" + a)){
                    case 'a':
                        System.out.print("非法左括号! ");
                        return;
                    case 'b':
                        System.out.print("缺少运算符! ");
                        return;
                    case 'c':
                        System.out.print("缺少表达式! ");
                        return;
                    default:
                        break;
                }
            }
            if (listStack.size() == 2 && a == '#') {
                break;
            }
            if (step < analyze.length()) {
                a = analyze.charAt(step++);
            } else {
                break;
            }
        } while (listStack.size() != 2 || a != '#');
        System.out.printf("%-8s\n", "分析成功");
    }
}

五、测试结果

1. LL(1)文法:

(1)输入文件:

图5-1-1 LL(1)文法的输入文件

(2)分析结果:

图5-1-2 LL(1)文法的分析结果


2. 算符优先文法:

(1)输入文件:

图5-1-4 算符优先文法的输入文件

(2)分析结果:

图5-1-5 算符优先文法的分析结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2315290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Flask和OpenCV 实现树莓派与客户端的视频流传输与显示

使用 Python 和 OpenCV 实现树莓派与客户端的视频流传输与显示 在计算机视觉和物联网领域&#xff0c;经常需要将树莓派作为视频流服务器&#xff0c;通过网络将摄像头画面传输到客户端进行处理和显示。本文将详细介绍如何利用picamera2库、Flask 框架以及 OpenCV 库&#xff…

fs的proxy_media模式失效

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 在fs的使用过程中&#xff0c;某些场景只需要对rtp媒体做透传&#xff0c;又不需要任何处理。 在fs1.6的版本中&#xff0c;我们可以使用proxy_media来代理媒体的转发&#xff0c;媒体的协商由AB路端对端处理&#xff…

Linux 命名管道

文章目录 &#x1f680; 深入理解命名管道&#xff08;FIFO&#xff09;及其C实现一、命名管道核心特性1.1 &#x1f9e9; 基本概念 二、&#x1f4bb; 代码实现解析2.1 &#x1f4c1; 公共头文件&#xff08;common.hpp&#xff09;2.2 &#x1f5a5;️ 服务器端&#xff08;s…

HDU 学数数导致的

题目解析 首先&#xff0c;数对是有序的&#xff0c;<1,2>和<2,1>被视为不同的两组数字。 其次&#xff0c;数对<p,q>的p和q可以相等。 子序列为 p 0 p q&#xff0c;观察到&#xff0c;中间要出现一个0。那么&#xff0c;我们只需要找到第一个 p 满足与前…

软件/硬件I2C读写MPU6050

MPU6050简介 6轴&#xff1a;3轴加速度&#xff0c;3轴角速度 9轴&#xff1a;3轴加速度&#xff0c;3轴角速度和3轴磁场强度 10轴&#xff1a;3轴加速度&#xff0c;3轴角速度和3轴磁场强度和一个气压强度 加速度计具有静态稳定性&#xff0c;不具有动态稳定性 欧拉角&…

Android中的Wifi框架系列

Android wifi框架图 Android WIFI系统引入了wpa_supplicant&#xff0c;它的整个WIFI系统以wpa_supplicant为核心来定义上层接口和下层驱动接口。 Android WIFI主要分为六大层&#xff0c;分别是WiFi Settings层&#xff0c;Wifi Framework层&#xff0c;Wifi JNI 层&#xff…

【含文档+PPT+源码】基于Python的图书管理系统的设计与实现

项目介绍 本课程演示的是一款基于Python的图书管理系统的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料 带你从零开始部署运行本套系统 该项目附…

开源工具利器:Mermaid助力知识图谱可视化与分享

在现代 web 开发中&#xff0c;可视化工具对于展示流程、结构和数据关系至关重要。Mermaid 是一款强大的 JavaScript 工具&#xff0c;它使用基于 Markdown 的语法来呈现可定制的图表、图表和可视化。对于展示流程、结构和数据关系至关重要。通过简单的文本描述&#xff0c;你可…

茂捷M1001电感式编码器芯片TSSOP28管脚,国产电感式编码器IC

简述&#xff1a; M1001 电感式编码器芯片是一款专为高精度位置检测而设计的芯片产品&#xff0c;采用先进的电感技术&#xff0c;能够精确测量旋转物体的位置和角度。芯片具有 SIN/COS、模拟、PWM、SENT、SPI、I2C等多种角度输出功能&#xff0c;具有高分辨率、宽工作温度范围…

LeetCode-跳跃游戏 II

方法一&#xff1a;反向查找出发位置 我们的目标是到达数组的最后一个位置&#xff0c;因此我们可以考虑最后一步跳跃前所在的位置&#xff0c;该位置通过跳跃能够到达最后一个位置。 如果有多个位置通过跳跃都能够到达最后一个位置&#xff0c;那么我们应该如何进行选择呢&a…

数据结构——双向链表dlist

前言&#xff1a;大家好&#x1f60d;&#xff0c;本文主要介绍了数据结构——双向链表dlist 一 双向链表定义 1. 双向链表的节点结构 二 双向链表操作 2.1 定义 2.2 初始化 2.3 插入 2.3.1 头插 2.3.2 尾插 2.3.3 按位置插 2.4 删除 2.4.1 头删 2.4.2 尾删 2.4.3 按…

IDEA 一键完成:打包 + 推送 + 部署docker镜像

1、本方案要解决场景&#xff1f; 想直接通过本地 IDEA 将最新的代码部署到远程服务器上。 2、本方案适用于什么样的项目&#xff1f; 项目是一个 Spring Boot 的 Java 项目。项目用 maven 进行管理。项目的运行基于 docker 容器&#xff08;即项目将被打成 docker image&am…

图像分类数据集

《动手学深度学习》-3.5-学习笔记 # 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式&#xff0c; # 并除以255使得所有像素的数值均在0&#xff5e;1之间 trans transforms.ToTensor()#用于将图像数据从 PIL 图像格式&#xff08;Python Imaging Library&#xff…

设计模式之美

UML建模 统一建模语言&#xff08;UML&#xff09;是用来设计软件的可视化建模语言。它的语言特点是简单 统一 图形化 能表达软件设计中的动态与静态信息。 UML的分类 动态结构图&#xff1a; 类图 对象图 组件图 部署图 动态行为图&#xff1a; 状态图 活动图 时序图 协作…

2025-03-15 学习记录--C/C++-PTA 练习3-4 统计字符

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; 一、题目描述 ⭐️ 练习3-4 统计字符 本题要求编写程序&#xff0c;输入10个字符&#xff0c;统计其中英文字母、空格或回车、…

802.11标准

系列文章目录 文章目录 系列文章目录一、相关知识二、使用步骤1.802.11修正比较2.802.11ac 三、杂记 一、相关知识 跳频扩频&#xff1a;射频信号可分为窄带信号和扩频信号。如果射频信号的带宽大于承载数据所需的带宽&#xff0c;该信号就属于扩频信号。跳频扩频(FHSS)是一种…

母婴商城系统Springboot设计与实现

项目概述 《母婴商城系统Springboot》是一款基于Springboot框架开发的母婴类电商平台&#xff0c;旨在为母婴产品提供高效、便捷的在线购物体验。该系统功能全面&#xff0c;涵盖用户管理、商品分类、商品信息、商品资讯等核心模块&#xff0c;适合母婴电商企业或个人开发者快…

C#通过API接口返回流式响应内容---分块编码方式

1、背景 上一篇文章《C#通过API接口返回流式响应内容—SSE方式》阐述了通过SSE&#xff08;Server Send Event&#xff09;方式&#xff0c;由服务器端推送数据到浏览器。本篇是通过分块编码的方式实现 2、效果 3、具体代码 3.1 API端实现 [HttpGet] public async Task Chu…

游戏引擎学习第158天

回顾和今天的计划 我们在这里会实时编码一个完整的游戏&#xff0c;没有使用引擎或库&#xff0c;一切都由我们自己做所有的编程工作&#xff0c;游戏中的每一部分&#xff0c;无论需要做什么&#xff0c;我们都亲自实现&#xff0c;并展示如何完成这些任务。今天&#xff0c;…

[新能源]新能源汽车快充与慢充说明

接口示意图 慢充接口为交流充电口&#xff08;七孔&#xff09;&#xff0c;快充接口为直流充电口&#xff08;九孔&#xff09;。 引脚说明 上图给的是充电口的引脚图&#xff0c;充电枪的为镜像的。 慢充接口引脚说明 快充接口引脚说明 充电流程 慢充示意图 慢充&…