编译原理(复习)

编译原理


一、第一章

1、什么是编译程序

翻译程序是指能够把一种语言程序(源语言程序)转换成另一种功能等价语言程序(目标语言程序)

编译程序是一种翻译程序,其源程序是高级语言,目标语言程序是低级语言。通常是一次性翻译方式

解释程序也是一种翻译程序,它与编译程序的区别是立即执行源程序,通常是逐句翻译执行

2、高级语言处理过程

在这里插入图片描述

3、编译程序过程

在这里插入图片描述

1、词法分析

任务:对构成源程序的字符串进行扫描和分解,识别出单词(如标识符等)符号
输入:源程序
输出:单词符号序列

2、语法分析

任务:根据语言的语法规则对单词符号串(符号序列)进行语法分析,识别出各类语法短语(可表示成语法树的语法单位),判断输入串在语法上是否正确
输入:单词序列
输出:语法分析后的单词序列

3、语义分析

任务:按语义规则对语法分析器归约出的语法单位进行语义分析,审查有无语义错误,为代码生成阶段收集类型信息,并进行类型审查和违背语言规范的报错处理

4、中间代码生成

任务:将语义分析得到的源程序变成一种结构简单,含义明确,容易生成,容易译成目标代码的内在代码形式
常用的中间代码形式是四元式(算符,运算对象1,运算对象2,结果)

5、代码优化

任务:对中间代码或目标代码进行变换改造等优化处理,使生成的代码更高效

6、目标代码生成

任务:将语义分析结果或中间代码生成特定机器上的绝对或可重定位的指令代码或汇编指令代码


二、第二章

1、语言概述

1、语言是由句子组成的集合,是由一组符号斤构成的集合
2、程序设计语言——所有该语言的程序的全体

  • 语法——表示构成语言句子的各个记号之间的组合规律
  • 语义——表示各个记号的特定含义。(各个记号和记号所表示的对象之间的关系)
  • 语用——表示在各个记号所出现的行为中,它们的来源、使用和影响

文法是一些规则的有限集合,它是以有穷规则集来刻划无穷句子集合的工具

2、文法及产生式

产生式——设VN ,VT 分别是非空有限的非终结符号集和终结符集。V = VN ∪ VT ,VN ∩ VT = ∅ 。所谓产生式是一个序偶对(α ,β),其中 α ∈ V+,β ∈ V*,通常表示为:α → β 或 α ::= β,产生式也叫规则、重写规则、生成式

文法 G 是一个四元组,G = (VN,VT,P,S)。其中,VN,VT 分别是非空有限的非终结符集和终结符集,且 VN ∩ VT = ∅;P为产生式集;S ∈ VN 为文法的开始符号(或识别符),它是一个非终结符,至少要在一条规则中作为左部出现。

3、文法分类

1、 0 型文法

​ 如果文法 G 的产生式 α → β,其中 α ∈ (VN ∪ VT)* ,且至少含有一非终结符号,β ∈ (VN ∪ VT)*。则称 G 为 0 型文法。0 型文法又称为短语结构文法

2、1 型文法

​ 如果 0 型文法 G 的产生式 α → β满足 |β| > |α|,仅当 β = ∅ 时例外。则称此文法 G 是 1 型文法。1 型文法也称为上下文有关文法

3、2 型文法

​ 一个 1 型文法 G,如果满足 α ∈ VN,则称为 2 型文法。或者说 2 型文法的产生式形如 A → β。2 型文法也称为上下文无关文法

4、3 型文法

​ 一个 2 型文法 G,如果产生式全都是形如 A → a 或 A → aB,其中:A、B ∈ VN,α ∈ VT,则 G 为 3 型文法
​ 3 型文法也称正则文法正规文法右线性文法。它对应有穷自动机

4、上下文无关文法与语法树

语法树也称推导树,给定文法G=(VN,VT,P,S),对于文法 G 的任意一个句型都存在一个相应的语法树,它满足:

  • ① 树中每一个结点都有一个标记,此标记是 V = VN ∪ VT 中的一个符号
  • ② 根的标记是S
  • ③ 若树的一结点 A 至少有一个子孙,则 A ∈ VN
  • ④ 如结点 A 的子孙结点从左到右次序为 B1,B2 …… Bn,则必有产生式 A → B1B2…Bn

同一语法树可以表示对同一句型不同的推导过程
​ 如果每一步 α → β 中,都是对 α 中最左(右)非终结符进行替换,则称之为最左(右)推导最右推导称为规范推导,由规范推导所得的句型称为规范句型
​ 同一语法树最多对应唯一的最左(右)推导
​ 一个句型有可能对应着不同的语法树
​ 如果一个文法存在某个句子对应着两棵不同的语法树,则称此文法是二义的
不存在算法判断一个 2 型文法是否是二义的
​ 实际应用中,常找出一组无二义性的充分条件来构造无二义性文法

5、句型的分析

​ 句型的分析就是识别一个符号串是否为某文法的一个句型
​ 分析算法分为两大类:自顶向下分析自底向上分析

  • 自顶向下分析:由根向下逐步建立语法树,是一个逐步推导的过程
  • 自底向上分析:自底向上构造语法树,是一个逐步规约的过程
1、自顶向上分析

​ 令 G[S] 是一文法,αβδ 是文法 G 的一个句型,如果有: S → αAδ,且 A → β,则称 β 是句型 αAδ 相对于非终结符 A 的短语,如有A → β,则称 β 是 αβδ 相对于非终结符 A 或相对于规则 A → β 的直接短语(也叫简单短语)
​ 一个句型的最左直接短语称为该句型的句柄
注意:作为短语的 β 必须是需要分析的句型 αβδ 的一部分

例题
在这里插入图片描述

文法实用性说明

  • 在实用中,文法中不得含有害规则或多余规则——形如 U → U 的规则为有害规则
  • 不可达规则或不可终止规则为多余规则——形如:G[S]: S → Ab|Cd A → b D → e C → Ca
  • 限制使用 ε 规则:即形如 A → ε 的规则,它会使有关文法的证明和讨论变得复杂

三、词法分析

1、词法分析程序设计

​ 通常将词法分析程序(扫描器)设计为子程序形式,当语法分析程序需要单词时,则调用该子程序。
扫描器的输出格式单词通常可分为5类:

  • 基本字(关键字、保留字):具有特殊含义的标识符,不作他用,有分隔语法的作用
  • 标识符:表示各种名字
  • 常量:整型、实型、布尔型、字符型
  • 运算符:算术、逻辑、关系运算符
  • 界符:包括 .;():, 等,有时也把运算符称作界符

2、扫描器的设计

​ 把扫描器当作语法分析的一个过程,当语法分析需要一个单词时,便调用扫描器。
​ 扫描器从初态出发,当识别一个单词后便进入终态,同时送出二元组
​ 扫描后输出二元组形式的单词序列(二元组:(单词种别,单词自身值或指针))

3、单词描述的工具

正规文法描述的语言是 VT* 上的正规集,绝大部分程序设计语言的单词都能用正规文法来描述

​ 正规式也称正规表达式,是表示正规集的工具,递归定义如下:

  • ① ε 和 ∅ 都是字母表 Σ 上的正规式,表示正规集为 { ε } 和 ∅
  • ② ∀ a ∈ ∑ ,a 是 ∑ 的正规式,表示正规集 { a }
  • ③ 假定 e1、e2 都是 ∑ 上的正规式,对应正规集为 L(e1)、L(e2),则 (e1)、e1|e2、e1.e2、e1* 也是正规式,分别表示的正规集为L(e1),L(e1) ∩ L(e2),L(e1).L(e2) 和 L(e1)*
  • ④ 仅有有限次运用上述步骤产生的表达式才是工上的正规式,仅由这些正规式产生的字集才是 ∑ 上的正规集

​ 设 r,s,t 为正规式,则正规式服从如下的代数规则:

  • ① r|s=s|r ('或’满足交换律)
  • ② rl(slt) = (rls)|t ('或’满足结合律)
  • ③ r(st) = (rs)t ('连接’满足结合律)
  • ④ r(s|t) = rs|rt (分配律)
  • ⑤ rε = εr=r (ε是连接的恒等元素)

​ 注意:rs ≠ sr r(st) ≠ rs | rt

正规式和正规文法的转换

将 ∑ = VT 上的正规式 r 换成正规文法 G = (VN,VT,P,S)方法如下:
① 令S → r
② 对形如 A→ xy 的产生式,可分解成A → xB,B → y
③ 对形如 A → x*y 的产生式,可分解成为 A → xA,A → y
④ 对形如 A → xly 的产生式,可分解为 A → x,A → y
反复利用上述规则,直至所有产生式中最多只含一个终结符

将正规文法 G = (VN,VT,P,S) 换成 ∑ → VT 上的正规式 r 的转换规则:
① 将产生式 A → xB,B → y 合写为 A = xy
② 将产生式 A → xA,A → y 合写为 A =x*y
③ 将产生式 A → x,A → y 合写为 A = xy
反复利用上述规则,直至只剩下一个由开始符定义的产生式,且该产生式右部不含一个非终结符

4、有穷自动机

​ 有穷自动机(Finite Automata):可准确的识别正规集。可分为确定的有穷自动机(DFA)和不确定的有穷自动机(NFA)

1、确定的有穷自动机

DFA M = (K,∑,f,S,Z),其中:
① K 是一有穷状态集
② ∑ 是一有穷字母表,称输入符号字母表
③ f 是转换函数,是在 K x ∑ → K 上的映射。如 f(ki,a) = kj
④ S 是唯一的一个初态
⑤ Z ⊂ K,是一终态集,终态也称结束态或可接受态

​ DFA 的确定性表现在状态转换函数是一个单值函数,即 f(k,a) 唯一确定一个后继状态

2、不确定有穷自动机

NFA M = (K,∑,f,S,Z),其中:
① K 是一有穷状态集
② ∑ 是一有穷字母表,称输入符号字母表
③ f 是转换函数,是在 K x ∑ → K 的子集上的映射
④ S 是初态集
⑤ Z ⊂ K,是一终态集,终态也称结束态或可接受态

有穷自动机 M 所接受的所有符号串集称为此自动机可接受的语言L(M)
对于每个 NFA M,必存在一个DFA M1,使得 L(M) = L(M1)
对于任何两个有穷自动机 M1 和 M2,若 L(M1)=L(M2),则称有穷自动机 M1 和 M2 等价

3、NFA 到 DFA 的转换

ε 闭包:ε-closure(I) 表示状态集 I 中任何状态 A 经任意条 ε 弧而能到达的状态集
状态集 I 的 a 弧转换,表示为 move(I,a),J 是所有那些从 I 中任何状态 A 经一条 a 弧而到达的状态全体

代码

4、DFA 的简化

​ 一个 DFA 是化简(最小化)了的,即是说,它没有多余状态且其状态中没有相互等价的。多余状态是指从自动机的开始状态出发,任何输入串也不可达的状态。
​ 在 DFA 中,两个状态 s 和 t 等价的条件是
​ ① 一致性条件:状态 s 和 t 必须同时为可接受态不可接受态
​ ② 蔓延性条件:对于所有输入符号,状态 s 和 t 必须转换到等价的状态里

​ NFA 化为 DFA 称为确定化,一般用造表法子集法

对 DFA 最小化的本质:

消除多余状态、合并等价状态
​ DFA 最小化过程:
​ ① 用分割法将不含多余状态的 DFA 分成一些不相交的子集,使得任何两个不同的子集中的状态都是可区别的,而相同子集中状态是等价
​ ② 分割时,首先将 DFA 状态分成终态子集非终态子集,再根据输出弧所达到状态的性质逐步细分

5、正规式和 NFA 的等价性

​ 对于 ∑ 上的每个 NFA M,可以构造一个相应的 ∑ 上的正规式 R,使得 L® = L(M)
​ 对于 ∑ 上的每个正规式 R,可以构造一个相应的 ∑ 上的 NFA M,使得 L® = L(M)

1、为 NFA M 构造相应的正规式 R

​ 首先引入两个新结点 x,y;x 经 ε 弧到所有初态,所有终态经 ε 弧到y
​ 再用如下消解规则消去除 x,y 外的所有结点,最后 x,y 间弧上的标记串就是所求正规式
在这里插入图片描述

2、由正规式 R 构造相应的 NFA M

​ 过程主要与 NFA M 构造相应的正规式 R 相反,遵循以下规则:
在这里插入图片描述

6、构造正规文法 G 等价的 NFA M 过程

​ ① 为 G 中每个非终结符生成 M 一个状态,开始符为初态
​ ② 增加一新状态 Z,做为 NFA M 的终态
​ ③ 对形如 A → aB 或 A → B 的产生式,构造 M 的转换函数 f(A,a) = B 或 f(A,ε) = B
​ ④ 对形如 A → a 的产生式,构造 M 的转换函数 f(A,a) = Z

例题
在这里插入图片描述

7、构造 NFA M 等价的正规文法 G 过程

​ ① 对转换函数 f(A,a) = B 或 f(A,a) = B ,改成形如 A → aB 或 A → B 的产生式
​ ② 对能识别终态 Z,增加一个产生式 Z → ε

例题
在这里插入图片描述


四、自顶向下语法分析方法

1、分析方法

  • 自顶向下
    • 确定的自顶向下分析
      • 递归下降法
      • 预测分析法
    • 不确定(带回溯)的自顶向下分析
  • 自底向上
    • 算符优先
    • LR 分析
      • LR(0) 分析、SLR(1) 分析
      • LR(1) 分析、LALR(0) 分析

2、常用文法

  • LL 文法和 LR 文法都是 CFG 的子集(无二义性)

    ​ 可用不同的文法来描述同一语言

  • 对于不同的文法,可用不同的分析方法

    • LL 文法——递归下降分析法、预测分析法
    • LR 文法——LR 分析法
  • LL 文法多用于编译的手工实现

  • LR 文法多用于编译的自动生成

3、自顶向下分析方法

基本思想(面向目标):寻找输入符号串最左推导,试图根据当前输入单词判断使用哪个产生式
基本过程:从根开始,按与最左推导相对应的顺序,构造输入符号串的分析树
产生式(候选式)的选择与回溯(Backtracking):当要进行关于某个语法变量的推导时,希望能够根据当前符号确定候选式

1、FIRST 集合

定义:对于 ∀α, β ∈ (VT ∪ VN )* ,定义 α 的首符号集为:FIRST(a) = {a I α →(*) a β,a ∈ VT }

计算文法符号的FIRST

在这里插入图片描述

计算符号串的FIRST
在这里插入图片描述

2、FOLLOW 集合

定义:对于 ∀A ∈ VN 定义 A 的后续符号集:FOLLOW(A) = { a |S → μAβ, a ∈ VT,且 a ∈ FIRST(β),μ ∈ VT*,β ∈ V+ };若 β →() ε ,则 # ∈ FOLLOW(A)。或:FOLLOW(A) = { a | S →( * ) … Aa …,a ∈ VT };若有S →()…A,则规定 # ∈ FOLLOW(A)

计算 FOLLOW
在这里插入图片描述

3、SELECT 集合

在这里插入图片描述

4、LL(1) 文法

LL(1) 文法表示了自顶向下方法能够处理的一类文法,第一个 L 表示从左向右扫描输入符号串,第二个 L 表示生成最左推导,1 表示读入一个符号可确定下一步推导

文法 G 是 LL(1) 文法的充要条件,对于 G 的每个非终结符 A 的任何两个不同的候选式 A → a|β,满足:SELECT(A → a ) ∩ SELECT(A → β) = ∅,其中,a、β 不能同时 →(*) ε

5、LL(1) 文法的判别

判定原则: 文法 G 是 LL(1) 文法的充要条件

判别步骤:

  • 画出各非终结符能否推导出 ε 的情况表
  • 用定义法或关系图法计算 FIRST、FOLLOW 集;
  • 计算各规则的 SELECT 集
  • 根据同一个左部的规则其 SELECT 集是否相交来判断给定文法是否为 LL(1) 文法

代码

6、非 LL(1) 文法

​ 非 LL(1) 文法具有不确定性

解决办法

  • 1、采用回朔

    ​ 算法过于复杂

  • 2、改写文法

    ​ 将非 LL(1) 文法改写为等价的 LL(1) 文法(可通过消除左递归提取左公共因子的方法将非 LL(1) 文法转换成 LL(1) 文法)

1、提取左公因子

​ 将形如 A → αβ1 l αβ2 |….| αβn | γ1 | γ2 |……| γn 的规则改写为:A → αA’ l γ1 l γ2 l……l γm 和 A’ → β1| β2 |….| βn

注意:通过提取左公共因子和引进新非终结符,作变换后,可能使原来某些规则无用,必须消除掉

2、消除左递归

  • 1、直接左递归的消除方法:将 A →Aa | β 替换为:A → βA’ 和 A’ → aA’ | ε
  • 2、间接左递归的消除:先将间接左递归化为直接左递归,再消除

7、不确定的自顶向下分析思想

  • 1、由于相同左部产生式的右部 FIRST 的交集不为空而引起回溯
  • 2、由于相同左部非终结符的右部存在能推出空的产生式,且该非终结符的 FOLLOW 集合含有其他产生式右部 FIRST 集的元素
  • 3、由于文法含有左递归而回溯

​ 对于 LL(1) 型文法,可以使用预测分析法。因为预测分析法只适用于 LL(1) 文法,所以在使用预测分析法时,必须先将文法化为 LL(1) 型文法。
​ 预测分析器由三部分组成:
​ ① 预测分析表(矩阵)
​ ② 先进后出栈——用来存放分析过程的语法符号
​ ③ 预测分析程序


五、自底向上语法分析方法

最左推导——每次推导都施加在句型的最左边的语法变量上——与最右归约对应
最右推导——每次推导都施加在句型的最右边的语法变量上——与最左归约(规范归约)对应,得规范句型
​ 如果 S →(*) αAβ 且 A →(+) γ,则称 γ 是句型 a γ β 的相对于变量 A 的短语
​ 如果 S →( *) αAβ and A → γ,则称 γ 是句型 α γ β 的相对于 A → γ 的直接(简单)短语
​ 最左直接短语叫做句柄(Handle)

1、自底向上分析思想

​ 从输入串出发,反复利用产生式进行归约,如果最后能得到文法的开始符号,则输入串是句子,否则输入串有语法错误。

2、自底向上核心

​ 寻找句型中的“句柄”进行归约,用不同的方法寻找句柄,就可获得不同的分析方法。

1、简单优先分析法

思想:按所有文法符号(包括非终结符和终结符)之间的优先关系来确定句柄。
优先关系
X ≡ Y:表示 X 和 Y 的优先关系相等
X ≮ Y:表示 X 比 Y 的优先性小
X ≯ Y:表示 X 比 Y 的优先性大

1、优先关系的定义
  • X ≡ Y:当且仅当 G 中存在规则 A →…XY…

    ​ 即:XY 并列出现

  • X ≮ Y:当且仅当 G 中存在规则 A →…XB… 且 B →(+)Y…

    ​ 即:X 与非终结符 B 并列(位于最前的 Y 经过至少一步归约后得到的 B )

  • X ≯ Y:当且仅当 G 中存在规则 A →… BD… 且 B →(+)…X 和 D →(+) Y…

    ​ 即:非终结符 B 与 D 并列(位于最后的 X 经过至少一步归约后得到 B,位于最前的 Y 经过 0 步或多步归约后得到 D )。

注意:

对于符号 S 和 # 作特殊处理,定义 # 优先级 ≮ 所有与之相邻符号。所有与之相邻符号优先级 ≯ #
引入产生式 S’ → #S# 来判断与 # 相邻符号与 # 的优先关系,且约定 # ≡ #。
在这里插入图片描述

例题:
在这里插入图片描述

2、简单文法优先

1、定义

​ 任意两符号间最多只有一种优先关系成立,在文法中任意两产生式没有相同的右部。

2、分析步骤

​ 1、初始化:将 # 入栈
​ 2、将输入符号串 a1a2a3…an# 依次入栈,直到栈顶符号优先级高于下一个待输入符
​ 3、以栈顶符号 ai 为句柄尾,往下找句柄头 ak,(当ak-1 ≮ ak,时,终止查找),并将句柄归约为一个非终结符,将句柄出栈,非终结符入栈;若无句柄可归约则报错
​ 4、、重复(1)和(2)
​ 5、当栈中只剩 S 和 # 时,分析成功

3、优缺点

​ 准确、规范,但分析效率低,实用价值不大。

2、算符优先分析法

1、算符文法定义

​ 如果文法 G = (VN, VT,P,S)中不存在形如 A → …BC… 的产生式,其中 B、C 为非终结符,则称之为算符文法 (OG)。即:如果文法 G 中不存在具有相邻非终结符的产生式,则称为算符文法

2、算符之间的优先关系

设 G = (VN,VT,P,S) 为 OG,则定义:
a ≡ b <=> A→ …ab… ∈ P 或者 A → …aBb… ∈ P
a ≮ b <=> A→ …aB… ∈ P 且 (B →(+) b… 或者 B →(+) Cb…)
a ≯ b <=> A→ …Bb… ∈ P 且 (B →(+) …a 或者 B→(+) …aC)

3、算符优先文法定义

​ 设 G = (VN,VT,P,S) 为 OG,如果 ∀a,b ∈ VT,a ≡ b, a ≮ b, a ≯ b 至多有一个成立,则称之为算符优先文法 (OPG)
​ 在无 ε 产生式的算符文法 G 中,如果任意两个终结符之间至多有一种优先关系,则称为算符优先文法

4、直接构造优先矩阵

对语法变量 A 定义:
​ 1、FIRSTVT(A) = { b |A →(+) b… 或者 A →(+) Cb… }
​ 2、LASTVT(A) = { b | A →(+) …b或者 A →(+) …bC }

则对产生式 A → X1X2.….Xn 按如下方法构造各文法符号对之间的优先关系后填入算符优先关系表:

​ 如果XiXi+1 ∈ VTVT 则:Xi ≡ Xi+1
​ 如果XiXi+1Xi+2 ∈ VTVNVT 则:Xi ≡ Xi+2
​ 如果XiXi+1 ∈ VTVN 则:∀a ∈ FIRSTVT(Xi+1),Xi ≮ a
​ 如果XiXi+1 ∈ VNVT 则:∀a ∈ LASTVT(Xi),a ≯ Xi+1

例题
在这里插入图片描述

5、构造优先矩阵

优先矩阵的构造算法基于两条原则

  • (a) 若有规则 A→ a… 或 A→Ba… 则 a ∈ FIRSTVT(A)

  • (b) 若 a∈ FIRSTVT(B) 且有规则 A → B… 则 a ∈ FIRSTVT(A)

求 FIRSTVT 集的算法步骤
先设立一个布尔数组 F[A,a],其值为真时,当且仅当 a ∈ FIRSTVT(A),则:

  • 第1步:按原则 (a) 对每个数组元素 F[A,a] 置初值,并对 F[A,a] 初值为真的符号对 (A,a) 入栈
  • 第2步:处理栈:当栈不空时,弹出栈项元素 (B,a),再按原则 (b),若 F[A,a] 为假,则使其变为真,再让 (A,a) 入栈
  • 第3步:重复至栈空为止
    在这里插入图片描述
6、算符优先分析

​ 算符优先分析和规范归约的核心均为寻找句柄
​ 规范归约的句柄是“最左直接短语
​ 算符优先分析的句柄是“最左素短语

  • 素短语

​ S →(*) aAβ and A →(+) γ,γ 至少含一个终结符,且不含更小的含终结符的短语,则称 γ 是句型 a γ β 的相对于变量 A 的素短语,句型至少含一个终结符不含其它素短语的短语

  • 最左素短语

​ 最左边的素短语称为最左素短语

​ 设句型的一般形式为 #N1a1N2a2…Nnan#,其中:Ni ∈ VN ∪ { ε },ai ∈ VT,则它的最左素短语是满足下列条件的最左子串 NiaiNi+1ai+1…NjajNj+1 其中:ai-1≮ aiai ≡ ai+1 ≡…≡ aj-1 ≡ ajaj ≯ aj+1

7、算符优先分析法的实现

​ 设单元 a 中存放当前输入符,S 为一个符号栈,
​ (1)、将当前输入符存放到 a 中,将 # 入符号栈
​ (2)、将栈顶第一个终结符 b 与 a 比较
​ 如果 b ≡ a,而 b == # 且栈中只剩一个非终结符时,则成功;否则 a 入栈
​ 如果 b ≮ a,则 a 入栈
​ 如果 b ≯ a,在栈顶寻找最左素短语,并将最左素短语归约为一个非终结符;如果文法中找不到相应规则,则出错
​ (3)、重复⑵至成功或失败
在这里插入图片描述

算符优先分析优缺点

优点:
效率高,(原因,只考虑终结符之间的优先关系确定句柄,而与非终结符无关)

缺点:
由于去掉了单非终结符之间的归约(如:T → F),有可能将错误的句子识别为正确的


六、LR 分析法

1、算符优先分析法存在的问题:

强调算符之间的优先关系的唯一性,这使得它的适应面比较窄;算法在发现最左素短语的尾时,需要返回来寻找对应的最左素短语头

2、LR(k) 分析法

​ L:从左到右扫描输入符号
​ R:最右推导对应的最左归约(反序完成最右推导)
​ k:超前读入 k 个符号,用以确定归约所用的规则

3、LR 分析器工作示意图
在这里插入图片描述

4、LR 分析表

​ 动作表 Action[s,a];转移表 Goto[s,X]
在这里插入图片描述

约定
sn:将符号 a、状态 n 压入栈
rn:用第 n 个产生式进行归约
LR(0)、SLR(1)、LR(1) 将以不同的原则构造这张分析表

1、 LR(0) 分析法

1、活前缀和可规前缀

定义:在这里插入图片描述
,是文法 G 的拓广文法 G’ (只出现在规则的左部) 中的一个规范推导,符号串 γ 是 aβ 的前缀,则称 γ 是 G 的一个活前缀。也就是说 γ 是规范句型 αβω 的前缀,但它的右端不超过该句型句柄的末端

说明
1、LR 分析时,把 αβ 的前缀的前缀 W 列出在符号栈中,一旦出现 αβ 即说明句柄形成,则用规则 A → β 归约
2、规范规约所得到的规范句型的活前缀是出现在分析栈中的符号串,所以,活前缀中不会出现句柄之后的任何字符

2、可归前缀算法

​ 设文法 G 是一个 2 型文法,对于非终结符A,定义集合 LC(A) = { a | S’ →(* R) αAβ ,α ∈ V*,β ∈ VT* }
​ LC(A) 即 A 左边所出现的符号串集。LC(A) 为所有不包括句柄的活前缀。若有产生式 A j→ γ,即 LC(A).γ 为一可归前缀。也可以将LC(A) 写成 [A]

推论
若文法 G 中有产生式 B → αAβ,则有 LC(B).α ⊆ LC(A)

例题
在这里插入图片描述

3、LR(0) 分析表构造

1、LR(0) 项目

​ 在文法 G 中每个产生式的右部适当位置添加一个圆点构成项目。特别的,对 A → ε 有项目为 A→**·**

​ 圆点左边代表该用产生式归约时句柄中已识别过的部分,右边为未识别过的部份。

2、项目的分类

​ 移进项目:形如 A→ α**.aB
​ 待约项目:形如 A一 α
.
​ 归约项目:形如 A→ αBβ
.**
​ 接受项目:形如 S’→ α**.**

3、项目集规范族

​ 项目的集合称项目集。识别活前缀的 DFA 项目集(状态)的全体称项目集规范族。

4、项目集的闭包与闭包之间的转移

1、项目集的闭包 I 的求法

​ 1、Ⅰ的项目均在 CLOSURE(I) 中
​ 2、若 A → α.Bβ ∈ CLOSURE(I),则每条形如 B → **.**γ 的项目也 ∈ CLOSURE(I)
​ 3、重复 2 直到 CLOSURE(I) 中不出现新项目为止

2、后继项目和核

​ A → α**.Xβ 的后继项目是 A → αX.β
新状态的初始项目称为核。所有圆点不在规则右部最左位置 (S’→
.**S 除外) 的项目都为核。

3、闭包之间的转移

​ go(I,X) = CLOSURE(J) = { A → αX**.β | A → α.**Xβ 属于 I },其中 J 为 I 状态中圆点从 X 前移到 X 后形成的项目集状态。
在这里插入图片描述

5、LR(0) 文法

​ 1、如果Ⅰ中至少含两个归约项目,则称Ⅰ有归约—归约冲突(Reduce/Reduce Conflict)
​ 2、如果Ⅰ中既含归约项目,又含移进项目,则称Ⅰ有移进—归约冲突(Shift/ReduceConflict)
​ 3、如果Ⅰ既没有归约—归约冲突,又没有移进—归约冲突,则称Ⅰ是相容的(Consistent),否则称Ⅰ是不相容的
​ 4、对文法 G,如果 ∀Ⅰ∈ C,都是相容的,则称 G 为 LR(0) 文法

2、SLR(1) 分析法

​ 文法有移进归约冲突,不能用 LR(0) 分析,所以引进了 SLR(1) 分析法

​ 对含有移进—归约和归约—归约冲突的项目集:I = X→α**.bβ,A → γ.,B → δ.** } 若所有含有 A 和 B 的句型中,
满足:FOLLOW(A) ∩ FOLLOW(B) = Φ
​ FOLLOW(A) ∩ { b } = FOLLOW(B) ∩ { b } = Φ

​ 在状态 I 中面临输入符 a 的动作可由下面规定决策:
​ 1、若 a = b,则移进
​ 2、若 a ∈ FOLLOW(A),则用 A → γ**.** 归约,若 a ∈ FOLLOW(B),则用 B → δ**.** 归约
​ 3、此外报错
类似,可推出含多个移进项目和归约项目的一般情况。能用上述方法解决冲突的文法称为 SLR(1) 文法

本质:先求出所有非终结符的 FOLLOW 集,当面临输入符号属于欲归约项目左部的非终结符的 FOLLOW 集时,使用该规则归约。

例题
在这里插入图片描述

SRL(1) 分析的特点和局限性

  • SLR(1)文法

    ​ 如果 G 的 SLR(1) 分析表无冲突则称 G 为 SLR (1) 文法。

  • 特点

    ​ SLR(1) 描述能力强于 LR(0)
    1、还考虑Follow集中的符号
    2、LR(0) 仅考虑规则的首符号

  • 局限性
    如果 SLR(1) 分析表仍有冲突(移进归约冲突或归约归约冲突),则说明该文法不是 SLR(1) 文法;这说明仅使用 LR(0) 项目集和 FOLLOW 集还不足以分析这种文法

3、LR(1) 分析法

​ LR(0) 不考虑后继符(搜索符),SLR(1) 仅在分析时考虑后继符(搜索符),因此,对后继符(搜索符)所含信息量的利用有限
​ 希望在构造状态时就考虑后继符(搜索符)的作用;考虑用产生式 A → α 归约时,不同状态里对 A 会要求考虑不同的后继符号

1、LR(k) 项目

  • 定义LR (K) 项目:对于 A → α**.**β,a1a2…ak,有:

    • 归约项目:[ A → a**.**,a1a2…ak]

    • 移进项目:[ A → α**.**aβ,a1a2….ak]

    • 待约项目:[ A → a**.**Bβ,a1a2….ak]

      利用 LR(k) 项目进行 LR(k) 分析,当 k = 1 时,为 LR(1) 项目,相应的分析叫 LR(1) 分析。

2、闭包/状态的计算

​ closure(I) 的计算:
​ 1)核/初始项目:A → α.B$,a
​ 2)同时考虑可能出现的后继符:b ∈ FIRST(βa)
注意:当 β →(*) ε 时,有 b = a,称 b 为继承的后继符,否则,称 b 为自生的后继符

例题
在这里插入图片描述

结论:

如果文法 G 的 LR(1) 分析表无冲突则称 G 为 LR(1) 文法,则能用 LR(1) 方法分析成功。


代码


NFA 转 DFA

import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

/**
 * @author :风雨之都
 * @date :Created 2022/3/15 22:36
 */

public class NFAToDFA {

    private static String startStateNode;
    private static ArrayList<String> endStateNodes = new ArrayList<>();
    private static boolean select = true;

    public static void main(String[] args) {
        NFAToDFA nfaToDFA = new NFAToDFA();
        nfaToDFA.NFAToDFA();
    }

    /**
     * 将NFA图转化为DFA状态图
     */
    private void NFAToDFA(){
        selectStartAndEndStateInputWay();
        // 获取 NFA(不确定有穷自动机) 的状态图
        ArrayList<String> NFA = getNFAStateDiagram();
        // 将NFA状态图的集合转化为hashMap存储
        HashMap<String, HashMap<String, String>> arrayToHashMapOfNFA = arrayToHashMapOfNFA(NFA);
        if(select){
            // 获取NFA的初态节点
            startStateNode = getStartStateNode(NFA);
            if(startStateNode == null){
                return;
            }
            // 获取NFA的终态节点集合
            endStateNodes = getEndStateNodes(NFA);
            if(endStateNodes.size() == 0){
                System.out.println("终态节点识别失败,请检查输入!");
                return;
            }
        }
        // 获取NFA的输入字符集合
        ArrayList<String> chars = getNFAInputChar(NFA);
        // 获取T0~Tn的子集以及DFA的状态图
        HashMap<String, Object> nfaSonArray = getNFASonArray(arrayToHashMapOfNFA, startStateNode, chars);
        // 获取T0~Tn的子集
        ArrayList<ArrayList<String>> tArrays = (ArrayList<ArrayList<String>>)nfaSonArray.get("TArrays");
        // 获取DFA的关系状图
        ArrayList<String> ttArray = (ArrayList<String>)nfaSonArray.get("TTArray");
        // 获取DFA的初态节点
        String dfaStartNode = getDFAStartNode(startStateNode, tArrays);
        // 获取DFA的终态节点
        ArrayList<String> dfaEndNodes = getDFAEndNodes(endStateNodes, tArrays);
        // 打印由NFA转化而来的DFA的状态图
        printDFA(tArrays,dfaStartNode,dfaEndNodes,ttArray,chars);
        printDiagram(ttArray);
        
    }

    /**
     *  打印DFA图
     * @param ttArray
     */
    private void printDiagram(ArrayList<String> ttArray){
        try {
            // 打开文件,如果不存在就创建新文件
            FileWriter fileWriter = new FileWriter("./Test1.puml",true);
            fileWriter.write("@startuml\n" +
                    "state DFA{\r\n");
            for (String s : ttArray) {
                String replace = s.replace("D([", "").replace("[", "").replace("]", "").replace(")", "");
                String end = replace.split("=")[1];
                String start = replace.split("=")[0].split(",")[0];
                String ch = replace.split("=")[0].split(",")[1];
                fileWriter.write(start+"-->"+end+" : "+ch);
                fileWriter.write("\r\n");
            }
            fileWriter.write("}\n" +
                    "@enduml");
            // 刷新缓存
            fileWriter.flush();
            // 关闭文件流
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 将算出来的DFA的结果打印出来
    private void printDFA(ArrayList<ArrayList<String>> tArrays,String dfaStartNode,ArrayList<String> dfaEndNodes,ArrayList<String> ttArray,ArrayList<String> chars){
        ArrayList<String> DFA = new ArrayList<>();
        System.out.println("NFA的状态K的子集: ");
        for (int i = 0; i < tArrays.size(); i++) {
            String node = "A" + i + " = " + tArrays.get(i);
            DFA.add("[A" + i+"]");
            System.out.println(node);
        }

        System.out.println("(1) DFA的状态集: S = " + DFA);

        System.out.println("(2) DFA的字符表:∑ = " + chars);

        System.out.println("(3) DFA的转换函数:");
        // 将转换函数格式化
        for (int i = 0; i < ttArray.size(); i++) {
            System.out.println("   "+ttArray.get(i));
        }

        System.out.println("(4) DFA的初态:S0 = "+DFA.get(Integer.parseInt(dfaStartNode)));

        // 经过处理后的终态节点集合
        ArrayList<String> dfaEnd = new ArrayList<>();
        for (int i = 0; i < dfaEndNodes.size(); i++) {
            dfaEnd.add(DFA.get(Integer.parseInt(dfaEndNodes.get(i))));
        }
        System.out.println("(5) DFA的终态集合:S1 = " + dfaEnd);
    }

    /**
     * 获取DFA的终态节点
     * @param endStateNodes NFA的终态节点集合
     * @param tArrays T0-T1的子集
     * @return 返回终态结果
     */
    private ArrayList<String> getDFAEndNodes(ArrayList<String> endStateNodes,ArrayList<ArrayList<String>> tArrays){
        // 存放DFA终态节点的下标
        ArrayList<String> DFAEndStateNodes = new ArrayList<>();
        for (String endStateNode : endStateNodes) {
            int count = 0;
            for (ArrayList<String> tArray : tArrays) {
                // NFA中的终态节点在tArray中找到,表示该集合为DFA的一个终态节点
                if (tArray.indexOf(endStateNode) != -1) {
                    // 判断该终态节点是否已存在,不存在则加入
                    if (DFAEndStateNodes.indexOf(count+"") == -1) {
                        DFAEndStateNodes.add(count+"");
                    }
                }
                count++;
            }
        }
        return DFAEndStateNodes;
    }

    /**
     * 获取DFA的初态节点
     * @param startStateNode NFA的初态节点
     * @param tArrays T0-T1的子集
     * @return 返回DFA的初态节点
     */
    private String getDFAStartNode(String startStateNode,ArrayList<ArrayList<String>> tArrays){
        // 待选初态集合
        ArrayList<String> startStateNodes = new ArrayList<>();
        int count = 0;
        for (ArrayList<String> tArray : tArrays) {
            // NFA的初态节点在tArray中,表示该集合为DFA的初态
            if (tArray.indexOf(startStateNode) != -1) {
                // 因为初态节点只会存在一个,所有没有使用indexOf方法,进行判断是否已存在
                if(tArray.size() == 1){
                    return String.valueOf(count);
                }
                startStateNodes.add(count+"");
            }
            count++;
        }
        // 由定理可知,初态节点只可能有一个,如果出现多个,则出入出错
        if(startStateNodes.size() != 1){
            System.out.println("NFA子集筛选错误或者NFA初态筛选错误!");
            return null;
        }
        // 返回初态节点
        return startStateNodes.get(0);
    }

    /**
     * 获取NFA的K的子集
     * @param arrayToHashMapOfNFA NFA的状态图转化后hashMap
     * @param startStateNode NFA初态节点
     * @param chars NFA的输入字符集合
     * @return 返回NFA的K的子集
     */
    private HashMap<String, Object> getNFASonArray(HashMap<String, HashMap<String, String>> arrayToHashMapOfNFA, String startStateNode, ArrayList<String> chars){
        ArrayList<String> array = new ArrayList<>();
        array.add(startStateNode);
        // 获取初态所映射的空集射弧
        array = getEndState(startStateNode, arrayToHashMapOfNFA,array,"c");
        // 存放子集T
        ArrayList<ArrayList<String>> TArrays = new ArrayList<>();
        // 存放DFA状态图
        ArrayList<String> TTArray = new ArrayList<>();
        // 确定T0
        TArrays.add(array);
        int count = 0;
        // 确定 T1~Tn
        do{
            array = TArrays.get(count++);
            for (String aChar : chars) {
                ArrayList<String> a = move(array, aChar, arrayToHashMapOfNFA);
                ArrayList<String> b = new ArrayList<>();
                for (String s : a) {
                    b = getEndState(s, arrayToHashMapOfNFA, b, "c");
                }
                a.addAll(b);
                int mark = 0;
                for (ArrayList<String> tArray : TArrays) {
                    if (!equalToArray(tArray,a)) {
                        mark++;
                    }else{
                        TTArray.add("D([A"+(count-1) + "],"+aChar+") = [A" + mark + "]");
                    }
                }
                if(mark == TArrays.size()){
                    if(a.size() != 0){
                        TArrays.add(a);
                    }
                    TTArray.add("D([A"+(count-1) + "],"+aChar+") = [A" + mark + "]");
                }
            }
        }while(count < TArrays.size());
        HashMap<String, Object> hashMap = new HashMap<>();
        // 存放NFA的子集
        hashMap.put("TArrays",orderByAes(TArrays));
        // 存放DFA的状态的集合
        hashMap.put("TTArray",TTArray);
        return hashMap;
    }

    /**
     * 获取NFA的输入字符集合
     * @param NFA NFA的状态图
     * @return 返回输入字符集合
     */
    private ArrayList<String> getNFAInputChar(ArrayList<String> NFA){
        ArrayList<String> chars = new ArrayList<>();
        for (String s : NFA) {
            String[] split = s.split(" ");
            // 判断当前输入字母是否表示空集,如不是的话再判断当前输入
            if((!"c".equals(split[2]))&&(chars.indexOf(split[2]) == -1)){
                chars.add(split[2]);
            }
        }
        return chars;
    }

    /**
     * 获取NFA的初态
     * @param NFA NFA状态图
     * @return 返回NFA的初态
     */
    private String getStartStateNode(ArrayList<String> NFA){
        // NFA 的初态集合
        ArrayList<String> startNodes = new ArrayList<>();
        // NFA的终态集合
        ArrayList<String> endNodes = new ArrayList<>();
        for (String s : NFA) {
            String[] s1 = s.split(" ");
            startNodes.add(s1[0]);
            endNodes.add(s1[1]);
        }
        // 如果初态的节点没有在终态的节点集合中,表示该节点为NFA的初态节点
        for (String startNode : startNodes) {
            if (endNodes.indexOf(startNode) == -1) {
                return startNode;
            }
        }
        System.out.println("NFA状态图输入有误,未找到初态节点!");
        return null;
    }

    /**
     * 获取NFA的终态集合
     * @param NFA NFA状态图
     * @return 返回NFA的终态集合
     */
    private ArrayList<String> getEndStateNodes(ArrayList<String> NFA){
        // NFA 的初态集合
        ArrayList<String> startNodes = new ArrayList<>();
        // NFA的终态集合
        ArrayList<String> endNodes = new ArrayList<>();
        // 最终返回的终态集合
        ArrayList<String> endStateNodes = new ArrayList<>();
        for (String s : NFA) {
            String[] s1 = s.split(" ");
            startNodes.add(s1[0]);
            endNodes.add(s1[1]);
        }
        // 如果终态节点没有在初态节点中找到,表示该终态节点为NFA的终态节点
        for (String endNode : endNodes) {
            if (startNodes.indexOf(endNode) == -1) {
             endStateNodes.add(endNode);
            }
        }
        return endStateNodes;
    }


    /**
     * 将输入的NFA状态集合转化为HashMap
     * @param NFA 需要转换的NFA状态图
     * @return 返回转化后的NFA的hasMap状态图
     */
    private HashMap<String,HashMap<String,String>> arrayToHashMapOfNFA(ArrayList<String> NFA){
        HashMap<String,HashMap<String,String>> hashMap = new HashMap<>();
        for (String nfa : NFA) {
            // 采用空格分割,分别是初态,终态,关系字母
            String[] strings = nfa.split(" ");
            // 如果键值对能找到,则表示以string[0] 为键的map存在,则在原有的基础上添加
            if (hashMap.get(strings[0]) != null) {
                // 两个状态节点可能存在多个关系,使用hasMap时,第一个值会被后面的值覆盖
                if(hashMap.get(strings[0]).get(strings[1])==null){
                    hashMap.get(strings[0]).put(strings[1],strings[2]);
                }else{
                    hashMap.get(strings[0]).put(strings[1],hashMap.get(strings[0]).get(strings[1])+","+strings[2]);
                }
            }else{ // 找不到键值对,则新建一个
                // 存放每两个有关系的状态的信息
                HashMap<String,String> map = new HashMap<>();
                map.put(strings[1],strings[2]);
                hashMap.put(strings[0],map);
            }
        }
        return hashMap;
    }

    /**
     * 获取从键盘输入的NFA状态图
     * @return 返回输入的NFA的状态图
     */
    private ArrayList<String> getNFAStateDiagram(){
        ArrayList<String> NFA = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入NFA状态图关系:");
        while (true){
            // 输入关系图
            String str = scanner.nextLine();
            // 以";"结束就结束输入
            if(";".equals(str)){
                break;
            }
            NFA.add(str);
        }
        return NFA;
    }

    /**
     * 集合进行升序排序
     * @param arrayLists 将集合中元素进行升序排序
     * @return 返回排序后的集合
     */
    public ArrayList<ArrayList<String>> orderByAes(ArrayList<ArrayList<String>> arrayLists){
        // 遍历获取集合中每个元素
        for (ArrayList<String> arrayList : arrayLists) {
            try{
                ArrayList<Integer> array = new ArrayList<>();
                // 如果节点以数字表示,则将字符转化为数字后排序
                for (String s : arrayList) {
                    array.add(Integer.parseInt(s));
                }
                // 使用Java自带的排序方法
                Collections.sort(array);
                // 将排序后的数组经过处理放回到原来集合中的位置
                for (int i = 0; i < arrayList.size(); i++) {
                    arrayList.set(i,array.get(i).toString());
                }
            }catch (Exception e){
                // 如果集合中的字符不能转化为数字,及节点不以字符数字表示,不能排序,转换的时候抛出异常,则直接返回该集合
                return arrayLists;
            }
        }
        return arrayLists;
    }


    /**
     * 判断非空的两个长度相等的集合是否一样
     * @param array1 集合1
     * @param array2 集合2
     * @return 判断比较结果
     */
    public boolean equalToArray(ArrayList<String> array1,ArrayList<String> array2){
        // 先判断长度,不等时,直接返回false
        if(array1.size() != array2.size()){
            return false;
        }
        // 如果集合长度相等,则依次编译判断是否相等
        for (String array : array1) {
            boolean mark = false;
            for (String s : array2) {
                // 只有找到,则退出循环
                if (array.equals(s)) {
                    mark = true;
                    break;
                }
            }
            // 表示未找到array元素,及两个集合不等,直接返回false
            if(!mark){
                return false;
            }
        }
        return true;
    }


    /**
     * Move函数
     * @param array 需要实现Move的集合
     * @param str 需要判断的输入字符
     * @param map NFA的状态图
     * @return 返回move结果集
     */
    private ArrayList<String> move(ArrayList<String> array,String str,HashMap<String,HashMap<String,String>> map){
        ArrayList<String> arrayList = new ArrayList<>();
        for (String s : array) {
            HashMap<String, String> hashMap = map.get(s);
            // 为空,表示该节点为终态节点
            if(hashMap == null){
                continue;
            }
            Set<String> strings = hashMap.keySet();
            for (String string : strings) {
                // 两个状态节点存在两个或两个以上的关系,使用,分割
                String[] split = hashMap.get(string).split(",");
                for (String s1 : split) {
                    if (s1.equals(str)) {
                        if(arrayList.indexOf(string) == -1){
                            arrayList.add(string);
                        }
                    }
                }
            }
        }
        return arrayList;
    }


    /**
     * 获取node节点后和他相连的空集字符的射弧节点
     * @param node 需要处理的节点
     * @param map NFA状态图
     * @param array 用于存储射弧的节点
     * @return 返回最终集合
     */
    private ArrayList<String> getEndState(String node,HashMap<String,HashMap<String,String>> map,ArrayList<String> array,String str){
        HashMap<String, String> hashMap = map.get(node);
        // 为空,表示该节点为终态节点
        if(hashMap == null){
            return array;
        }
        Set<String> strings = hashMap.keySet();
        for (String string : strings) {
            if(hashMap.get(string).equals(str)){
                array.add(string);
                // 如果该节点后面还有,则递归调用
                array = getEndState(string,map,array,str);
            }
        }
        return array;
    }

    /**
     * 选择初态和终态输入方式(自动识别和手动输入)
     */
    private void selectStartAndEndStateInputWay(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("请选择初态和终态确认方式:");
        System.out.println("======1、程序自动识别======");
        System.out.println("======2、手动录入    ======");
        String s = scanner.nextLine();
        if("1".equals(s)){
            return;
        }else if("2".equals(s)){
            System.out.print("请输入初态: ");
            startStateNode = scanner.nextLine();
            System.out.print("请输入终态集合,使用空格隔开:");
            String endStates = scanner.nextLine();
            endStateNodes.addAll(Arrays.asList(endStates.split(" ")));
            select = false;
        }else{
            selectStartAndEndStateInputWay();
        }
    }
}

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

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

LL(1) 文法的判断以及预测分析表的输出

import java.util.*;

/**
 * @author :风雨之都
 * @date :Created 2022/3/30 10:53
 */

public class LLOne {
    /**
     * 表示空
     */
    private String ε = "ε";
    /**
     * FOLLOW集合中的特殊结束符
     */
    private String $ = "$";
    /**
     * 用于存储间接递归非终结符调用关系
     */
    private HashMap<String,String> map = new HashMap<>();
    /**
     * 用于存储所有的非终结符
     */
    private ArrayList<String> allNonEndpoint = new ArrayList<>();

    public static void main(String[] args) {
        LLOne llOne = new LLOne();
        // 获取文法信息
        ArrayList<String> grammar = llOne.getGrammar();
        // 将未处理的文法经过处理后返回
        ArrayList<Grammar> grammars = llOne.handGrammar(grammar);
        // 将隐式递归转化为显示递归
        grammars = llOne.changeImplicitToShowLeftRecursion(grammars);
        System.out.println("将隐式递归转化为显示递归处理后:");
        Print.simplePrint(grammars);
        // 消除直接左递归
        grammars = llOne.removeLeftRecursion(grammars);
        System.out.println("消除直接递归处理后:");
        Print.simplePrint(grammars);
        // 将隐式左公因子转化为显示左公因子
        grammars = llOne.changeImplicitToShowLeftCommonFactor(grammars);
        System.out.println("将隐式左公因子转化为显示左公因子处理后:");
        Print.simplePrint(grammars);
        // 消除左公因子
        grammars = llOne.removeLeftCommonFactor(grammars);
        System.out.println("消除左公因子处理后:");
        Print.simplePrint(grammars);
        // 优化化简消除后的左公因子
        grammars = llOne.combineGrammar(grammars);
        System.out.println("优化化简表达式后:");
        Print.simplePrint(grammars);
        // 获取文法的非终结点集合
        ArrayList<String> nonEndpointArray = llOne.getNonEndpointArray(grammars);
        // 获取文法的FIRST集合
        ArrayList<FIRST> firstArray = llOne.getFirstArray(grammars, nonEndpointArray);
        // 获取文法的FOLLOW集合
        ArrayList<FOLLOW> followArray = llOne.getFollowArray(grammars, nonEndpointArray);
        // 获取文法的SELECT集合
        ArrayList<Grammar> selectArray = llOne.getSelectArray(grammars, firstArray, followArray);
        Print.printFIRST(firstArray);
        Print.printFOLLOW(followArray);
        Print.printSELECT(selectArray);
        if(llOne.isLLOneGrammar(grammars)){
            Print.printPredictiveAnalysisTable(selectArray);
            ArrayList<PredictAnalyseTable> predictAnalyseTables = llOne.analyseString(grammars);
            System.out.println("预测分析法分析过程:");
            Print.printPredictAnalyseProcessTable(predictAnalyseTables);
        }
    }

    /**
     * 预测分析法分析过程
     * @param grammars
     * @return
     */
    private ArrayList<PredictAnalyseTable> analyseString(ArrayList<Grammar> grammars){
        ArrayList<PredictAnalyseTable> predictAnalyseTables = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入校验字符串: ");
        // 获取输入的字符串
        String inputString = scanner.next();
        Stack grammarStack = new Stack("S$");
        Stack inputStringStack = new Stack(inputString);
        int step = 1;
        while(!grammarStack.getStackTop().equals("$")){
            String stackTop = inputStringStack.getStackTop();
            String grammarStackStackTop = grammarStack.getStackTop();
            for (Grammar grammar : grammars) {
                if(grammar.getLeft().equals(grammarStackStackTop)&&
                        // select集合包含输入串
                        grammar.getSelectArray().contains(stackTop)){
                    predictAnalyseTables.add(new PredictAnalyseTable(step++,grammarStack,inputStringStack,grammar,"1"));
                    // 如果是终结符
                    if(isEndpoint(grammar.getRight().get(0))){
                        grammarStack.popStack();
                        grammarStack.pushStack(grammar.getRight());
                        grammarStack.popStack();
                        inputStringStack.popStack();
                        predictAnalyseTables.add(new PredictAnalyseTable(step++,grammarStack,inputStringStack,grammar,"0"));
                        // 如果是非终结符
                    }else if(isNonEndpoint(grammar.getRight().get(0))){
                        grammarStack.popStack();
                        grammarStack.pushStack(grammar.getRight());
                    }else if(grammar.getRight().get(0).equals(ε)){
                        grammarStack.popStack();
                    }
                    break;
                }
            }
        }
        predictAnalyseTables.add(new PredictAnalyseTable(step,grammarStack,inputStringStack,null,"2"));
        return predictAnalyseTables;
    }

    /**
     * 判断该文法是不是LL1文法
     * @param grammars 需要判断的文法
     */
    private boolean isLLOneGrammar(ArrayList<Grammar> grammars){
        ArrayList<String> nonEndpointFirstArray = getNonEndpointArray(grammars);
        boolean mark = true;
        for (String nonEndpoint : nonEndpointFirstArray) {
            ArrayList<Grammar> temp = new ArrayList<>();
            for (Grammar grammar : grammars) {
                if(nonEndpoint.equals(grammar.getLeft())){
                    temp.add(grammar);
                }
            }
            // 如果长度小于等于1,表示不用判断
            if(temp.size() <= 1){
                continue;
            }

            for (Grammar grammar : temp) {
                for (Grammar grammar1 : temp) {
                    if(!grammar.equal(grammar1)){
                        for (String s : grammar.getSelectArray()) {
                            if(grammar1.getSelectArray().contains(s)){
                                mark = false;
                                break;
                            }
                        }
                    }
                }
            }
        }
        if(mark){
            System.out.println("该文法是LL(1)文法!");
        }else{
            System.out.println("该文法不是LL(1)文法!SELECT集合具有相同的部分");
        }
        return mark;
    }

    /**
     * 将文法中的间接递归转化为直接递归
     * @param grammars 需要处理的文法
     * @return 处理后的文法
     */
    private ArrayList<Grammar> changeImplicitToShowLeftRecursion(ArrayList<Grammar> grammars){
        ArrayList<Grammar> newGrammar = new ArrayList<>();
            for (Grammar grammar : grammars) {
                ArrayList<Grammar> temp = new ArrayList<>();
                if (isNonEndpoint(grammar.getRight().get(0))) {
                    // 不是直接递归就进入方法调用
                    if(!grammar.getLeft().equals(grammar.getRight().get(0))){
                        ArrayList<Grammar> grammars1 = combineGrammar(grammars, grammar, grammar);
                        // 返回值为空表示该文法不存在间接递归
                        if (grammars1.size()==0) {
                            temp.add(grammar);
                        }else{
                            temp.addAll(grammars1);
                        }
                    }else{
                        // 直接递归就直接加入
                        temp.add(grammar);
                    }
                }else{
                    temp.add(grammar);
                }

                ArrayList<Grammar> exchange = new ArrayList<>();
                for (Grammar grammar1 : temp) {
                    exchange.add(new Grammar(grammar1.getLeft(),grammar1.getRight()));
                }
                newGrammar.addAll(exchange);
        }
        return deleteSameObject(newGrammar);
    }

    /**
     * 消除每个文法的左递归
     * @param grammars 需要依据的文法
     * @param grammar 需要消除左递归的文法
     * @param originalGrammar 最开始的文法
     * @return 处理后的文法
     */
    private ArrayList<Grammar> combineGrammar(ArrayList<Grammar> grammars,Grammar grammar,Grammar originalGrammar){
        ArrayList<Grammar> newGrammar = new MyArrayList<>();
        // 用于标记相同左部的文法是否需要进行存储操作
        boolean mark = false;
        for (Grammar grammar1 : grammars) {
            // 如果是自己则跳出
            if (grammar1.equal(grammar)) {
                continue;
            }
            // 找到Grammar右部第一个符号和左部相等应左部的文法
            if (grammar1.getLeft().equals(grammar.getRight().get(0))) {
                // 防止出现 A->C  C->A
                if (!grammar.getLeft().equals(map.get(grammar1.getLeft()))) {
                    // 判断是否为非终结符、不能直接找到终结符
                    if(isNonEndpoint(grammar1.getRight().get(0))&&
                            // 当前文法的右部第一个与grammar的左部不相等
                            !grammar1.getRight().get(0).equals(grammar.getLeft())&&
                            // 当前文法的右部第一个与originalGrammar的左部不相等
                            !originalGrammar.getLeft().equals(grammar1.getRight().get(0))){
                        // 该非终结符不是直接递归,则递归查找
                        newGrammar.addAll(combineGrammar(grammars, grammar1,originalGrammar));
                        //newGrammar.add(grammar1);
                        newGrammar.add(originalGrammar);
                    }else{
                        if(grammar1.getRight().get(0).equals(originalGrammar.getLeft())){
                            map.put(originalGrammar.getLeft(),grammar1.getLeft());
                            mark = true;
                        }
                        // 如果右部为终结符并且为左部等于originalGrammar右部的第一个符号
                        if(isEndpoint(grammar1.getRight().get(0))&&grammar1.getLeft().equals(originalGrammar.getRight().get(0))){
                            mark = false;
                            // 主要用于判断是否存在直接递归
                            for (Grammar grammar2 : grammars) {
                                if (
                                        // 该文法的右部的第一个为非终结符
                                        isNonEndpoint(grammar2.getRight().get(0))&&
                                        // 该文法的左部与进行比较文法的左部相同
                                        grammar1.getLeft().equals(grammar2.getLeft())&&
                                        // 该文法的右部第一个符号与起始文法左部相同,表示存在直接递归
                                        grammar2.getRight().get(0).equals(originalGrammar.getLeft())
                                        ) {
                                    // 如果存在直接递归
                                    mark = true;
                                    break;
                                }
                            }
                        }
                        // 判断该文法是否在替换的过程中是否会存在中间变量的直接递归
                        if(isRecursionGrammar(grammars,grammar1)){
                            newGrammar.add(originalGrammar);
                        }
                        // 用于判断该文法是否需要被存储
                        if(mark){
                            newGrammar.add(new Grammar(originalGrammar.getLeft(),new ArrayList<>(){{
                                addAll(grammar1.getRight());
                                ArrayList<String> arrayList = new ArrayList<>(originalGrammar.getRight());
                                arrayList.remove(originalGrammar.getRight().get(0));
                                addAll(arrayList);
                            }}));
                        }
                    }
                }else{
                    if(newGrammar.indexOf(grammar)==-1){
                        newGrammar.add(grammar);
                    }
                }
            }
        }
        return newGrammar;
    }

    /**
     * 判断该文法是否存在直接左递归
     * @param grammars 原始文法集合
     * @param grammar 需要判断的文法
     * @return 返回判断结果
     */
    private boolean isRecursionGrammar(ArrayList<Grammar> grammars,Grammar grammar){
        if(grammar.getLeft().equals(grammar.getRight().get(0))){
            return true;
        }
        for (Grammar grammar1 : grammars) {
            if(grammar1.getLeft().equals(grammar.getRight().get(0))&&grammar1.getLeft().equals(grammar1.getRight().get(0))){
                if (isRecursionGrammar(grammars,grammar1)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 消除左递归
     * @param grammars 需要处理的文法
     * @return 返回处理后的文法
     */
    private ArrayList<Grammar> removeLeftRecursion(ArrayList<Grammar> grammars){
        // 获取该文法的非终结符
        ArrayList<String> nonEndpointArray = getNonEndpointArray(grammars);
        // 存储新的文法
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        for (String s : nonEndpointArray) {
            // 存储具有相同左部的文法
            ArrayList<Grammar> temp = new ArrayList<>();
            for (Grammar grammar : grammars) {
                if(s.equals(grammar.getLeft())){
                    temp.add(grammar);
                }
            }
            // 只有大于1即等于大于2的文法的左递归才有意义,如果该非终结符的文法只有一个则没有意义
            if (temp.size()>1) {
                ArrayList<Grammar> grammars1 = handOneLeftRecursion(temp);
                newGrammar.addAll(handOneLeftRecursion(grammars1));
            }else{
                if(temp.get(0).getRight().get(0).equals(s)){
                    System.out.println(temp.get(0).left+" -> "+temp.get(0).getRight()+", 该文法没有意义");
                    return grammars;
                }
                newGrammar.addAll(temp);
            }
        }
        return newGrammar;
    }

    /**
     * 处理每一个非终结符的左递归
     * @param grammars 需要处理的文法
     * @return 返回处理结果
     */
    private ArrayList<Grammar> handOneLeftRecursion(ArrayList<Grammar> grammars){
        // 存储新的文法集合
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        // 存储非终结符文法结合
        ArrayList<Grammar> nonEndpointArray = new ArrayList<>();
        // 存储终结符文法集合
        ArrayList<Grammar> endpointArray = new ArrayList<>();
        for (Grammar grammar : grammars) {
            // 非终结符集合,定义中的Aαi
            if(isNonEndpoint(grammar.getRight().get(0))&&grammar.getLeft().equals(grammar.getRight().get(0))){
                nonEndpointArray.add(grammar);
            }else{
                // 终结符集合,定义中βi
                endpointArray.add(grammar);
            }
        }
        // 如果非终结符集合为空,表示该文法不存在直接递归,直接返回
        if (nonEndpointArray.size()==0) {
            return grammars;
        }

        // 生成一个新的非终结符
        String aNonEndpoint = getANonEndpoint(grammars.get(0).getLeft());
        // 终结符集合
        for (Grammar grammar : endpointArray) {
            newGrammar.add(new Grammar(grammar.getLeft(),new ArrayList<>(){{
                // 将去除后的非终结符的右部放入集合
                addAll(grammar.getRight());
                // 加入引入的非终结符
                add(aNonEndpoint);}}));
        }
        // 非终结符集合
        for (Grammar grammar : nonEndpointArray) {
            newGrammar.add(new Grammar(aNonEndpoint,new ArrayList<>(){{
                // 将递归非终结符去除
                grammar.getRight().remove(grammar.getLeft());
                addAll(grammar.getRight());
                add(aNonEndpoint);}}));
        }
        // 加入一个空集
        newGrammar.add(new Grammar(aNonEndpoint,new ArrayList<>(){{add(ε);}}));
        return newGrammar;
    }

    /**
     * 将隐式左公因子转化为显示左公因子
     * @param grammars 需要处理的语法
     * @return 返回处理结果
     */
    private ArrayList<Grammar> changeImplicitToShowLeftCommonFactor(ArrayList<Grammar> grammars){
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        for (Grammar grammar : grammars) {
            ArrayList<Grammar> temp = new ArrayList<>();
            // 以非终结符打头的文法,需要将该非终结符递归替换
            ArrayList<String> right = new ArrayList<>(grammar.getRight());
            if (right.size()==0) {
                continue;
            }
            if (isNonEndpoint(grammar.getRight().get(0))) {
                ArrayList<String> grammarRight = new ArrayList<>(grammar.getRight());
                ArrayList<Grammar> rightOfSameNonEndpoint = getRightOfSameNonEndpoint(grammars, grammar.getRight().get(0));
                // 将非终结符去掉,然后替换为其右部
                grammarRight.remove(grammarRight.get(0));
                for (Grammar grammar1 : rightOfSameNonEndpoint) {
                    temp.add(new Grammar(grammar.getLeft(),new ArrayList<>(){{
                        addAll(grammar1.getRight());
                        addAll(grammarRight);
                    }}));
                }
            }else{
                temp.add(grammar);
            }

            // 用于标记是否跳出该循环
            boolean out = false;
            // 判断该文法集合中是否存在间接左公因子
            for (Grammar grammar1 : temp) {
                for (Grammar grammar2 : temp) {
                    // 如果是自己,就跳过
                    if (grammar1.equal(grammar2)) {
                        continue;
                    }
                    // 找到间接递归
                    if (grammar1.getRight().get(0).equals(grammar2.getRight().get(0))) {
                        newGrammar.addAll(temp);
                        out = true;
                        break;
                    }
                }


                for (Grammar grammar2 : grammars) {
                    if(!grammar2.equal(grammar1)&&grammar1.getLeft().equals(grammar2.getLeft())&&isEndpoint(grammar2.getRight().get(0))&&grammar1.getRight().get(0).equals(grammar2.getRight().get(0))){
                        newGrammar.addAll(temp);
                        out = true;
                        break;
                    }
                }

                // 跳出循环
                if(out){
                    break;
                }
            }
            // 不存在直接左公因子就把该文法添加到新的文法集合中
            if(!out){
                newGrammar.add(grammar);
            }
        }
        return newGrammar;
    }

    /**
     * 获取左部等于该终结符的所有右部文法
     * @param grammars 需要查询的文法
     * @param nonEndpoint 需要判断的非终结符
     * @return 返回查询结果
     */
    private ArrayList<Grammar> getRightOfSameNonEndpoint(ArrayList<Grammar> grammars,String nonEndpoint){
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        for (Grammar grammar : grammars) {
            if(nonEndpoint.equals(grammar.getLeft())){
                // 如果是非终结符就递归调用
                ArrayList<String> grammarRight = new ArrayList<>(grammar.getRight());
                if(grammarRight.size()==0){
                    continue;
                }
                if(isNonEndpoint(grammarRight.get(0))){
                    ArrayList<String> temp = new ArrayList<>(grammarRight);
                    ArrayList<Grammar> rightOfSameNonEndpoint = getRightOfSameNonEndpoint(grammars, grammarRight.get(0));
                    for (Grammar grammar1 : rightOfSameNonEndpoint) {
                        newGrammar.add(new Grammar(grammar.getLeft(),new ArrayList<>(){{
                            addAll(grammar1.getRight());
                            // 将非终结符去掉,然后替换为其右部
                            temp.remove(grammar1.getLeft());
                            addAll(temp);
                        }}));
                    }
                }else{
                    newGrammar.add(grammar);
                }
            }
        }
        return newGrammar;
    }

    /**
     * 对文法进行合并简化
     * @param grammars 需要处理的文法
     * @return 返回处理后的文法
     */
    private ArrayList<Grammar> combineGrammar(ArrayList<Grammar> grammars){
        // 存储合并后的新文法
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        // 用于作比较的集合对象
        ArrayList<Grammar> temp = grammars;
        for (Grammar grammar : grammars) {
            // 文法右部最后个一个符号
            String rightLastOfGrammar = grammar.getRight().get(grammar.getRight().size() - 1);
            if (isNonEndpoint(rightLastOfGrammar)) {
                Grammar right = getRight(grammars, rightLastOfGrammar);
                if(right != null){
                    // 将合并后的文法重新放在一个集合中
                    grammar.getRight().remove(rightLastOfGrammar);
                    newGrammar.add(new Grammar(grammar.getLeft(),new ArrayList<>(){{addAll(grammar.getRight());addAll(right.getRight());}}));
                    temp = removeObject(temp,right);
                    // 将中间替换的非终结符删除
                    newGrammar = removeObject(newGrammar,right);
                }else{
                    // 如果该grammar能在temp中找到,表示该文法语句未被删除,即相同左部存在多个右部,可以加入,反之,则不能
                    if (temp.indexOf(grammar)!=-1) {
                        newGrammar.add(new Grammar(grammar.getLeft(),grammar.getRight()));
                    }
                }
            }else{
                newGrammar.add(new Grammar(grammar.getLeft(),grammar.getRight()));
            }
        }
        return newGrammar;
    }

    /**
     * 移除集合中指定的对象
     * @param grammars 需要处理的集合
     * @param grammar 需要移除的对象
     * @return 返回移除的结果
     */
    private ArrayList<Grammar> removeObject(ArrayList<Grammar> grammars,Grammar grammar){
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        for (Grammar grammar1 : grammars) {
            // 左部不相等就加入新的集合
            if(!grammar1.getLeft().equals(grammar.getLeft())){
                newGrammar.add(grammar1);
            }else{
                if(grammar.getRight().size()==grammar1.getRight().size()){
                    for (int i = 0; i < grammar1.getRight().size(); i++) {
                        // 右部只要有一个不相等就加入新的集合,并且退出循环
                        if(!grammar1.getRight().get(i).equals(grammar.getRight().get(i))){
                            newGrammar.add(grammar1);
                            break;
                        }
                    }
                }else{
                    newGrammar.add(grammar1);
                }
            }
        }
        return newGrammar;
    }

    /**
     * 判断该终结符是否只有一个唯一的左部,如果是则返回该文法的右部,如果不是则返回null
     * @param grammars 需要使用的文法语句
     * @param nonEndpoint 需要判断的非终结符
     * @return 返回处理结果
     */
    private Grammar getRight(ArrayList<Grammar> grammars,String nonEndpoint){
        int count=0;
        Grammar newGrammar = null;
        for (Grammar grammar : grammars) {
            if(grammar.getLeft().equals(nonEndpoint)){
                newGrammar = grammar;
                count++;
            }
        }
        // 如果count为1,表示该非终结符对应的文法的相同左部只有一个
        if(count == 1){
            return newGrammar;
        }else{
            return null;
        }
    }

    /**
     * 去除左递归
     * @param grammars 需要处理的文法
     * @return 处理后的文法
     */
    private ArrayList<Grammar> removeLeftCommonFactor(ArrayList<Grammar> grammars){
        // 获取非终结符集合
        ArrayList<String> nonEndpointArray = getNonEndpointArray(grammars);
        ArrayList<Grammar> newGrammar = new ArrayList<>();
        for (String s : nonEndpointArray) {
            ArrayList<Grammar> grammarArrayList = new ArrayList<>();
            for (Grammar grammar : grammars) {
                if(grammar.getLeft().equals(s)){
                    grammarArrayList.add(grammar);
                }
            }
            newGrammar.addAll(removeOneGrammar(grammarArrayList));
        }
        newGrammar = combineGrammar(newGrammar);
        return newGrammar;
    }

    /**
     * 将具有相同左部的文法进行去除左递归
     * @param grammars 具有相同左部的文法
     * @return 返回处理后的文法
     */
    private ArrayList<Grammar> removeOneGrammar(ArrayList<Grammar> grammars){
        ArrayList<Grammar> newGrammar;
        // 用于存储文法右部的第一个符号和在集合中的位置
        HashMap<String,ArrayList<Integer>> map = new HashMap<>();
        newGrammar = new ArrayList<>();
        ArrayList<String> nonEndpointArray = getNonEndpointArray(grammars);
        for (int i = 0; i < grammars.size(); i++) {
            // 如果不存在就put
            ArrayList<Integer> index=null;
            try{
                index = map.get(grammars.get(i).getRight().get(0));
            }catch (Exception e){
                // 抛出异常表示没有元素,则将该非终结符的右部设置为 ε
                System.out.println(grammars.get(i));
            }

            if (index==null) {
                index = new ArrayList<>();
            }
            // 不管存不存在都需要重新put进去
            index.add(i);
            map.put(grammars.get(i).getRight().get(0),index);
        }
        ArrayList<String> points = new ArrayList<>(map.keySet());
        // 如果键值对个数和grammars大小一样,表示其中没有相同的键,即没有相同的右部,则跳出递归
        if (points.size()==grammars.size()) {
            return grammars;
        }
        for (String point : points) {
            // 获取具有相同左部的下标
            ArrayList<Integer> index = map.get(point);
            //如果大于1,则表示需要合并
            if (index.size()>1) {
                // 引入新的非终结符
                String newEndpoint = getANonEndpoint(point);
                for (Integer i : index) {
                    // 获取含有该符号的文法
                    ArrayList<String> right = grammars.get(i).getRight();
                    // 移除具有相同部分的符号
                    right.remove(point);
                    // 如果长度为0,表示为空,则将该非终结符指向的位置设为空
                    if (right.size()==0) {
                        right.add(ε);
                    }
                    newGrammar.add(new Grammar(newEndpoint,right));
                    nonEndpointArray.add(newEndpoint);
                }
                // 将提取的公共部分的文法存入新的集合
                newGrammar.add(new Grammar(grammars.get(index.get(0)).left,new ArrayList<>(){{add(point);add(newEndpoint);}}));
            }else{
                // 如果只有一个,表示该文法不存在左递归,直接加入新的集合
                newGrammar.add(grammars.get(index.get(0)));
            }
        }
        // 递归调用实现消除左递归
        grammars = removeLeftCommonFactor(newGrammar);
        return grammars;
    }

    /**
     * 随机获取一个非终结符
     * @param advice 建议返回的非终结符
     * @return 返回一个非终结符
     */
    private String getANonEndpoint(String advice){
        if(allNonEndpoint.indexOf(advice.toUpperCase()+"'")==-1){
            allNonEndpoint.add(advice.toUpperCase()+"'");
            return advice.toUpperCase()+"'";
        }
        for (String str : new ArrayList<String>(){{addAll(Arrays.asList("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"));}}) {
            if (!allNonEndpoint.contains(str)) {
                if(!allNonEndpoint.contains(str+"'")){
                    allNonEndpoint.add(str+"'");
                    return str+"'";
                }
            }
        }
        return null;
    }

    /**
     * 获取SELECT集合
     * @param grammars 经过处理后的文法
     * @param firsts FIRST集合
     * @param follows FOLLOW集合
     * @return 返回SELECT集合
     */
    private ArrayList<Grammar> getSelectArray(ArrayList<Grammar> grammars,ArrayList<FIRST> firsts,ArrayList<FOLLOW> follows){
        for (Grammar grammar : grammars) {
            // SELECT集合
            ArrayList<String> selectArray = new ArrayList<>();
            // 如果是终结符,则follow集为该终结符
            String symbol = grammar.getRight().get(0);
            if (isEndpoint(symbol)) {
                selectArray.add(symbol);
                // 如果是非终结符则follow集应该为symbol的FIRST集
            }else if(isNonEndpoint(symbol)){
                for (String right : grammar.getRight()) {
                    boolean mark = false;
                    for (FIRST first : firsts) {
                        if (first.getNonEndpoint().equals(right)) {
                            ArrayList<String> firstArray = new ArrayList<>(first.getFirstArray());
                            // 表示包含空集,需要将FIRST中的空集去掉
                            if (firstArray.indexOf(ε)!=-1) {
                                firstArray.remove(ε);
                                firstArray.add($);
                                // 如果symbol是空集,则SELECT集合应该是symbol的FOLLOW集合
                                for (FOLLOW follow : follows) {
                                    // 该文法的左部和FOLLOW中的非终结符比较
                                    if (follow.getNonEndpoint().equals(grammar.getLeft())) {
                                        selectArray.addAll(follow.getFollowArray());
                                    }
                                }

                            }else{
                                // 只要没空集就将$去除
                                selectArray.remove($);
                                mark = true;
                            }
                            selectArray.addAll(firstArray);
                        }
                    }
                    if(mark){
                        selectArray.remove($);
                        break;
                    }
                }

            }else if(ε.equals(symbol)){
                // 如果symbol是空集,则SELECT集合应该是symbol的FOLLOW集合
                for (FOLLOW follow : follows) {
                    // 该文法的左部和FOLLOW中的非终结符比较
                    if (follow.getNonEndpoint().equals(grammar.getLeft())) {
                        selectArray.addAll(follow.getFollowArray());
                    }
                }
            }
            grammar.setSelectArray(deleteSameElements(selectArray));
        }
        return grammars;
    }

    /**
     * 获取该文法的FOLLOW集合
     * @param grammars 处理过的文法
     * @param nonEndpoints 非终结点集合
     * @return 返回FOLLOW集合
     */
    private ArrayList<FOLLOW> getFollowArray(ArrayList<Grammar> grammars,ArrayList<String> nonEndpoints){
        ArrayList<FOLLOW> follows = new ArrayList<>();
        // 遍历非终结符
        for (String nonEndpoint : nonEndpoints) {
            FOLLOW follow = getOneNonEndpointFollow(grammars, nonEndpoint,new ArrayList<>());
            follows.add(follow);
        }

        // 在进行一次遍历完善,用于判断该非终结符是否在文法的右部的最后一个元素,是的话需要加上其左部的非终结符的follow集
        ArrayList<FOLLOW> newFollows = new ArrayList<>();
        for (FOLLOW follow : follows) {
            newFollows.add(nonEndpointAtGrammarEnd(grammars, follow, follows));
        }
        return newFollows;
    }

    /**
     * 用于判断该非终结符是否在文法的右部的最后一个元素,是的话需要加上其左部的非终结符的follow集
     * @param grammars 需要依据的文法
     * @param follow 需要处理的follow集
     * @param follows 经过初步处理的集合
     * @return 返回最后处理的结果
     */
    private FOLLOW nonEndpointAtGrammarEnd(ArrayList<Grammar> grammars,FOLLOW follow,ArrayList<FOLLOW> follows){
        for (Grammar grammar : grammars) {
            // 判断该非终结符是否在该文法的最后一个,如果是的话需要加上该文法左部的非终结符的follow集
            if(grammar.getRight().get(grammar.getRight().size()-1).equals(follow.getNonEndpoint())){
                // 判断是否为右循环,不是的话才进入
                if(!grammar.getLeft().equals(follow.getNonEndpoint())){
                    for (FOLLOW follow1 : follows) {
                        // 找到对应的follow集
                        if(follow1.getNonEndpoint().equals(grammar.getLeft())){
                            for (String s : follow1.getFollowArray()) {
                                // 遍历是否存在重复元素
                                if (follow.getFollowArray().indexOf(s)==-1) {
                                    follow.getFollowArray().addAll(follow1.getFollowArray());
                                }
                            }
                        }
                    }
                }
            }else{
                // 获取该终结符的后一个符号
                String next = grammar.getRight().get(grammar.getRight().indexOf(follow.nonEndpoint) + 1);
                // 如果是非终结符,则判断该终结符的位置是否为最后一个符号
                if(isNonEndpoint(next)){
                    // 如果是的话则判断是否可以为空
                    if (grammar.getRight().indexOf(next)==grammar.getRight().size()-1) {
                        // 获取FIRST集合
                        ArrayList<String> firsts = new ArrayList<>(getNonEndpointFirstArray(grammars,next,new ArrayList<>()));
                        // 如果包含空集,表示该非终结符可能为结尾,所以需要加上该文法的左部的follow集
                        if (firsts.indexOf(ε)!=-1) {
                            for (FOLLOW follow2 : follows) {
                                // 找到对应的follow集
                                if(follow2.getNonEndpoint().equals(grammar.getLeft())){
                                    for (String s : follow2.getFollowArray()) {
                                        // 遍历是否存在重复元素
                                        if (follow.getFollowArray().indexOf(s)==-1) {
                                            follow.getFollowArray().addAll(follow2.getFollowArray());
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return new FOLLOW(follow.getNonEndpoint(),deleteSameElements(follow.getFollowArray()));
    }

    /**
     * 将一个非终结符的follow集返回
     * @param grammars 需要的文法
     * @param nonEndpoint follow对应的非终结符
     * @return 返回follow集
     */
    private FOLLOW getOneNonEndpointFollow(ArrayList<Grammar> grammars,String nonEndpoint,ArrayList<String> nodes){
        if(nodes.indexOf(nonEndpoint)!=-1){
            ArrayList<String> firstArray = getNonEndpointFirstArray(grammars, nonEndpoint, new ArrayList<>());
            firstArray.remove(ε);
            return new FOLLOW(nonEndpoint,firstArray);
        }
        ArrayList<String> followArray = new ArrayList<>();

        ArrayList<String> nonEndpointArray = getNonEndpointArray(grammars);
        // 如果该非终结符为开始符号
        if(nonEndpoint.equals(nonEndpointArray.get(0))){
            followArray.add($);
        }

        // 遍历文法
        for (Grammar grammar : grammars) {
            // 该终结符在该文法中右部的位置
            int index = grammar.getRight().indexOf(nonEndpoint);
            if (index!=-1) {
                // 判断该非终结符是否在该文法的末尾位置,如果是的话就加上结尾的特殊符号 $
                if (index == grammar.getRight().size()-1){
                    // 如果结束符未添加过则加入
                    followArray.add($);

                    // 如果在结尾处,该非终结符的follow也需要依赖于该文法左部的非终结符的follow
                    /*if(!grammar.getLeft().equals(nonEndpoint)){
                        nodes.add(grammar.getLeft());
                        FOLLOW follow = getOneNonEndpointFollow(grammars, grammar.getLeft(),nodes);
                        followArray.addAll(follow.getFollowArray());
                    }*/
                    // 该非终结符在该文法表达式右部的非结尾处,需要加上该非终结符后面符号的FIRST集合
                }else{
                    // 该终结符后面的一个符号
                    String next = grammar.getRight().get(index + 1);
                    // 如果该终结符后面是终结符,则直接加入集合
                    if (isEndpoint(next)) {
                        followArray.add(next);
                        // 如果是非终结符,则加入该终结符后面那个非终结符的FIRST集合
                    }else{
                        // 获取FIRST集合
                        ArrayList<String> firsts = new ArrayList<>(getNonEndpointFirstArray(grammars,next,new ArrayList<>()));
                        // 如果包含空集,表示该非终结符可能为结尾,所以需要加上结束符$
                        for (String first : firsts) {
                            if(first.equals(ε)){
                                followArray.add($);
                                // 如果包含空集,表示该终结符的follow依赖于该文法左部的非终结符的follow
                                //nodes.add(grammar.getLeft());
                                //FOLLOW oneNonEndpointFollow = getOneNonEndpointFollow(grammars, grammar.getLeft(),nodes);
                                //followArray.addAll(oneNonEndpointFollow.getFollowArray());
                            }else{
                                followArray.add(first);
                            }
                        }
                    }
                }
            }
        }
        // 去除重复的
        followArray = deleteSameElements(followArray);
        return new FOLLOW(nonEndpoint, followArray);
    }

    /**
     * 获取FRIST集合
     * @param grammars 需要处理的文法
     * @param nonEndpoints 该文法的非终结点集合
     * @return 返回FIRST集合
     */
    private ArrayList<FIRST> getFirstArray(ArrayList<Grammar> grammars,ArrayList<String> nonEndpoints){
        ArrayList<FIRST> firsts = new ArrayList<>();
        for (String nonEndpoint : nonEndpoints) {
            ArrayList<String> nonEndpointFirstArray = new ArrayList<>();
            for (Grammar grammar : grammars) {
                if(nonEndpoint.equals(grammar.getLeft())){
                    nonEndpointFirstArray = getNonEndpointFirstArray(grammars, nonEndpoint, nonEndpointFirstArray);
                }
            }
            firsts.add(new FIRST(nonEndpoint, nonEndpointFirstArray));
        }
        return firsts;
    }

    /**
     * 获取非终结符的FIRST集合
     * @param grammars 经过处理后的文法
     * @param nonEndpoint 非终结符
     * @param FIRSRs FIRST集合
     * @return 返回FIRST集合
     */
    private ArrayList<String> getNonEndpointFirstArray(ArrayList<Grammar> grammars,String nonEndpoint,ArrayList<String> FIRSRs){
        // 遍历文法
        for (Grammar grammar : grammars) {
            if(nonEndpoint.equals(grammar.getLeft())){
                FIRSRs = getNonEndpointFirstArray(grammars,grammar.getRight(),FIRSRs);
            }
        }
        return FIRSRs;
    }

    /**
     * 处理每一个非终结点的first集合
     * @param grammars 经过处理的文法
     * @param rights 文法的右部
     * @return 返回first集合
     */
    private ArrayList<String> getNonEndpointFirstArray(ArrayList<Grammar> grammars,ArrayList<String> rights,ArrayList<String> FIRSTs){
        for (String right : rights) {
            // 判断first是否为终结符
            if(isEndpoint(right)){
                FIRSTs.add(right);
                // 如果为非空,将终结符添加后就不需要判断后面的非终结符或者终结符
                break;
                // 判断是否为非终结符,将继续递归调用
            }else if(isNonEndpoint(right)){
                ArrayList<String> firsts = new ArrayList<>();
                firsts = getNonEndpointFirstArray(grammars, right, firsts);
                FIRSTs.addAll(firsts);
                // 集合中没有 "空" ,则跳出循环
                if(firsts.indexOf(ε)==-1){
                    // 如果返回的集合中没有空,表示该非终结符的右部不可能为空,所以FIRST集合中不会存在空元素
                    FIRSTs.remove(ε);
                    break;
                }
            }else{//既不是终结符也不是非终结符,表示尾 "空",
                // 将 "空" 加入Frist集合,然后再继续下一个循环
                FIRSTs.add(right);
            }
        }
        return deleteSameElements(FIRSTs);
    }

    /**
     * 去除集合中相同的元素
     * @return 返回没有重复元素的集合
     */
    private ArrayList<String> deleteSameElements(ArrayList<String> FIRSTs){
        ArrayList<String> firsts = new ArrayList<>();
        for (String first : FIRSTs) {
            if (firsts.indexOf(first)==-1) {
                firsts.add(first);
            }
        }
        return firsts;
    }

    /**
     * 删除集合中具有相同值的对象
     * @param grammars 需要处理的文法
     * @return 返回处理后的文法
     */
    private ArrayList<Grammar> deleteSameObject(ArrayList<Grammar> grammars){
        ArrayList<Grammar> newGrammar = new MyArrayList<>();
        for (Grammar grammar : grammars) {
            if (newGrammar.indexOf(grammar)==-1) {
                newGrammar.add(grammar);
            }
        }
        return newGrammar;
    }

    /**
     * 判断该字符是否为终结符
     * @param str 需要判断的字符
     * @return 判断结果
     */
    private boolean isEndpoint(String str){
        ArrayList<String> endpoints = new ArrayList<>(Arrays.asList("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","(",")","+","*"));
        for (String s : endpoints ){
            // 只要字符串包含终结符就返回true
            if(str.contains(s)){
                return true;
            }
        }
        return false;
    }

    /**
     * 判断该字符是否为非终结符
     * @param str 需要判断的字符
     * @return 判断结果
     */
    private boolean isNonEndpoint(String str){
        ArrayList<String> nonEndpoint = new ArrayList<>(Arrays.asList("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"));
        // 只要包含非终结符就返回true
        for (String s : nonEndpoint) {
            if (str.contains(s)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文法的非终结符集合
     * @param grammars 处理后的文法
     * @return 返回非终结符集合
     */
    private ArrayList<String> getNonEndpointArray(ArrayList<Grammar> grammars){
        // 非终结符集合
        ArrayList<String> nonEndpoints = new ArrayList<>();
        for (Grammar grammar : grammars) {
            if (nonEndpoints.indexOf(grammar.getLeft())==-1) {
                nonEndpoints.add(grammar.getLeft());
            }
        }
        return nonEndpoints;
    }

    /**
     * 获取终结符集合
     * @param grammars 需要处理的文法
     * @return 返回终结符集合
     */
    private ArrayList<String> getEndpointArray(ArrayList<Grammar> grammars){
        // 非终结符集合
        ArrayList<String> endpoints = new ArrayList<>();
        for (Grammar grammar : grammars) {
            for (String right : grammar.getRight()) {
                if (isEndpoint(right)) {
                    if (endpoints.indexOf(right)==-1) {
                        endpoints.add(right);
                    }
                }
            }
        }
        return endpoints;
    }

    /**
     * 将未处理的文法处理为自定义的文法类集合
     * @param originGrammars 未处理的文法
     * @return 返回处理后的文法
     */
    private ArrayList<Grammar> handGrammar(ArrayList<String> originGrammars){
        ArrayList<Grammar> grammars = new ArrayList<>();
        for (String originGrammar : originGrammars) {
            String[] split = originGrammar.split(" ");
            ArrayList<String> rights = new ArrayList<>(Arrays.asList(split[1].split(",")));
            grammars.add(new Grammar(split[0], rights));
        }
        return grammars;
    }

    /**
     * 从外部获取文法
     * @return 返回未处理的文法字符串
     */
    private ArrayList<String> getGrammar(){
        Scanner scanner = new Scanner(System.in);
        ArrayList<String> grammars = new ArrayList<>();
        System.out.println("请输入文法(空集请用 ε 表示,右部每个符号用,分割):");
        do{
            String str = scanner.nextLine();
            // 结束文法输入
            if(";".equals(str)){
                break;
            }
            grammars.add(str);
        }while (true);
        return grammars;
    }

    /**
     * 文法定义类
     */
    static class Grammar{
        /**
         * 文法左部
         */
        private String left;
        /**
         * 文法右部
         */
        private ArrayList<String> right;
        /**
         * SELECT集合
         */
        private ArrayList<String> selectArray;

        public Grammar(String left, ArrayList<String> right) {
            this.left = left;
            this.right = right;
        }

        public String getLeft() {
            return left;
        }

        public ArrayList<String> getRight() {
            return right;
        }

        public ArrayList<String> getSelectArray() {
            return selectArray;
        }

        public void setSelectArray(ArrayList<String> selectArray) {
            this.selectArray = selectArray;
        }

        @Override
        public String toString() {
            return "Grammar{" +
                    "left=" + left  +
                    ", right=" + right +
                    ", selectArray=" + selectArray +
                    '}';
        }

        /**
         * 判断两个对象是否相等
         * @param grammar 需要判断的文法
         * @return 返回结果
         */
        public boolean equal(Grammar grammar){
            // 左部不相等,直接返回
            if(!grammar.getLeft().equals(left)){
                return false;
            }
            // 右部长度不相等,直接返回
            if (grammar.getRight().size()!=right.size()) {
                return false;
            }

            for (int i = 0; i < grammar.getRight().size(); i++) {
                // 只要有一个元素不相等就返回
                if(!grammar.getRight().get(i).equals(right.get(i))){
                    return false;
                }
            }
            return true;
        }
    }

    /**
     * FIRST集合定义类
     */
    static class FIRST{
        /**
         * 非终结点
         */
        private String nonEndpoint;
        /**
         * 非终结点的FIRST集合
         */
        private ArrayList<String> firstArray;

        public FIRST(String nonEndpoint, ArrayList<String> firstArray) {
            this.nonEndpoint = nonEndpoint;
            this.firstArray = firstArray;
        }

        public String getNonEndpoint() {
            return nonEndpoint;
        }

        public ArrayList<String> getFirstArray() {
            return firstArray;
        }

        @Override
        public String toString() {
            return "FIRST{" +
                    "nonEndpoint='" + nonEndpoint + '\'' +
                    ", firstArray=" + firstArray +
                    '}';
        }
    }

    /**
     * FOLLOW集合类定义
     */
    static class FOLLOW{
        /**
         * 非终结点
         */
        private String nonEndpoint;
        /**
         * 非终结点的FOLLOW集合
         */
        private ArrayList<String> followArray;

        public FOLLOW(String nonEndpoint, ArrayList<String> followArray) {
            this.nonEndpoint = nonEndpoint;
            this.followArray = followArray;
        }

        public String getNonEndpoint() {
            return nonEndpoint;
        }

        public ArrayList<String> getFollowArray() {
            return followArray;
        }

        @Override
        public String toString() {
            return "FOLLOW{" +
                    "nonEndpoint='" + nonEndpoint + '\'' +
                    ", followArray=" + followArray +
                    '}';
        }
    }

    /**
     * 打印类
     */
    static class Print {
        /**
         * 打印first集合
         * @param firsts 需要打印的集合
         */
        protected static void printFIRST(ArrayList<FIRST> firsts){
            System.out.println("FIRST集合:");
            for (FIRST first : firsts) {
                System.out.println("     FIRST("+first.getNonEndpoint()+") = "+first.getFirstArray());
            }
            System.out.println();
        }

        /**
         * 打印follow集合
         * @param follows 需要打印的集合
         */
        protected static void printFOLLOW(ArrayList<FOLLOW> follows){
            System.out.println("FOLLOW集合:");
            for (FOLLOW follow : follows) {
                System.out.println("     FOLLOW("+follow.getNonEndpoint()+") = "+follow.getFollowArray());
            }
            System.out.println();
        }

        /**
         * 打印select集合
         * @param grammars 需要打印的集合
         */
        protected static void printSELECT(ArrayList<Grammar> grammars){
            System.out.println("SELECT集合:");
            for (Grammar grammar : grammars) {
                System.out.print("     SELECT("+grammar.getLeft()+" → ");
                for (String s : grammar.getRight()) {
                    System.out.print(s);
                }
                System.out.println(") = "+grammar.getSelectArray());
            }
            System.out.println();
        }

        /**
         * 简单打印
         * @param grammars 需要打印的文法
         */
        protected static void simplePrint(ArrayList<Grammar> grammars){
            for (Grammar grammar : grammars) {
                System.out.println("     "+grammar.getLeft()+" → " + grammar.getRight());
            }
            System.out.println("\n");
        }

        /**
         * 打印预测分析表
         * @param grammars 需要打印的文法
         */
        protected static void printPredictiveAnalysisTable(ArrayList<Grammar> grammars){
            LLOne llOne = new LLOne();
            ArrayList<String> endpointArray = llOne.getEndpointArray(grammars);
            endpointArray.add(llOne.$);
            // 打印头
            for (String s : endpointArray) {
                System.out.print("          "+s);
            }
            System.out.println();
            // 打印主体
            for (Grammar grammar : grammars) {
                System.out.print(grammar.getLeft());
                for (String s : endpointArray) {
                    if (grammar.getSelectArray().indexOf(s)!=-1) {
                        System.out.print("        "+grammar.getLeft()+"→");
                        for (String s1 : grammar.getRight()) {
                            System.out.print(s1);
                        }
                    }else{
                        System.out.print("           ");
                    }
                }
                System.out.println();
            }
        }

        /**
         * 打印预测分析法分析过程
         * @param predictAnalyseTables
         */
        protected static void printPredictAnalyseProcessTable(ArrayList<PredictAnalyseTable> predictAnalyseTables){
            System.out.println(String.format("%-10s","Step")+
                    String.format("%-10s","Stack")+" "+
                    String.format("%-10s","String")+
                    String.format("%-10s","Rule"));
            for (PredictAnalyseTable predictAnalyseTable : predictAnalyseTables) {
                System.out.println(" "+String.format("%-10s", predictAnalyseTable.getStep())+
                        String.format("%-10s",predictAnalyseTable.getStack())+
                        String.format("%-10s",predictAnalyseTable.getInput())+
                        String.format("%-10s",predictAnalyseTable.getRule()));
            }
        }
    }

    /**
     * 自定义集合
     * @param <E>
     */
    class MyArrayList<E> extends ArrayList<E>{
        // 重写判断相等的方法
        @Override
        public int indexOf(Object o) {
            if(o==null){
                return -1;
            }
            for (int i = 0; i < this.size(); i++) {
                if (this.get(i).toString().equals(o.toString())) {
                    return i;
                }
            }
            return -1;
        }
    }

    /**
     * 自定义栈
     */
    class Stack{
        /**
         * 用集合模拟栈
         */
        private ArrayList<String> stack = new ArrayList<>();

        public Stack(String stack) {
            char[] chars = stack.toCharArray();
            for (int i = chars.length-1; i >= 0; i--) {
                this.stack.add(String.valueOf(chars[i]));
            }
        }

        public ArrayList<String> getStack() {
            return stack;
        }

        public Stack() {
        }

        /**
         * 压栈
         * @param node
         */
        protected void pushStack(String node){
            stack.add(node);
        }

        protected void pushStack(ArrayList<String> node){
            for (int i = node.size()-1; i >=0 ; i--) {
                stack.add(node.get(i));
            }

        }

        /**
         * 弹栈
         * @return
         */
        protected String popStack(){
            String top = stack.get(stack.size() - 1);
            // 弹出栈底
            stack.remove(stack.size() - 1);
            return top;
        }

        /**
         * 获取栈顶
         * @return
         */
        protected String getStackTop(){
            return stack.get(stack.size()-1);
        }

        @Override
        public String toString() {
            return "Stack{" +
                    "stack=" + stack +
                    '}';
        }
    }

    /**
     * 预测语法分析表
     */
    class PredictAnalyseTable{
        /**
         * 第几步
         */
        private int step;
        /**
         * 栈
         */
        private String stack = "";
        /**
         * 输入字符串
         */
        private String input = "";
        /**
         * 规则
         */
        private String rule;

        /**
         * 初始化
         * @param step 第几步
         * @param stack 语法栈
         * @param input 输入字符串
         * @param grammar 语法
         * @param mark 用于标记是否匹配
         */
        public PredictAnalyseTable(int step, Stack stack, Stack input, Grammar grammar,String mark) {
            this.step = step;
            for (String s : stack.getStack()) {
                this.stack += s;
            }
            for (String s : input.getStack()) {
                this.input+=s;
            }

            if("0".equals(mark)){
                this.rule = grammar.getRight().get(0)+"匹配";
            }else if("2".equals(mark)){
                this.rule = "接受";
            }else{
                this.rule = grammar.getLeft()+"→";
                for (String s : grammar.getRight()) {
                    this.rule+=s;
                }
            }
        }

        @Override
        public String toString() {
            return "PredictAnalyseTable{" +
                    "step=" + step +
                    ", stack='" + stack + '\'' +
                    ", input='" + input + '\'' +
                    ", rule='" + rule + '\'' +
                    '}';
        }

        public int getStep() {
            return step;
        }

        public String getStack() {
            return stack;
        }

        public String getInput() {
            return input;
        }

        public String getRule() {
            return rule;
        }
    }
}

输入
在这里插入图片描述

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

  • 9
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值