形式语言与自动机
1.整体流程
读取并存储CFG
预处理
转化为Greibach范式
public void changeToGreibach_total(String filename) {
List<GrammarNode> list = new LinkedList<>();
list = readRule(list,filename); //读取文法
showRules(list);
removeUseless(list); //消除无用符号
showRules(list);
removeE(list); //消除空产生式
showRules(list);
removeUnitProduction(list); //消除单一产生式
removeUseless(list);
showRules(list);
System.out.println("消除直接左递归!");
removeLeftRecursive(list); //消除直接左递归
showRules(list);
greibachFirstStep(list);
removeUseless(list);
showRules(list);
greibachSecondStep(list);
showRules(list);
writeToTxt(list);
}
2.代码实现
2.1 数据结构
public class GrammarNode {
private String left;
private String right;
public String getLeft() {
return left;
}
public void setLeft(String left) {
this.left = left;
}
public String getRight() {
return right;
}
public void setRight(String right) {
this.right = right;
}
}
2.2 读取 CFG
- 读取文件
List<GrammarNode> readRule(List<GrammarNode> list, String filePath) {
list.clear();
try {
String encoding = "utf-8";
File file = new File(filePath);
if (file.isFile() && file.exists()){//判断文件是否存在
InputStreamReader read = new InputStreamReader(new FileInputStream(file),encoding);//考虑到编码格式
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt;
while((lineTxt = bufferedReader.readLine()) != null){
//读文件的行readline,每次使用都读一行
String[] arr =lineTxt.split(" ");
GrammarNode node = new GrammarNode();
node.setLeft(arr[0]);
node.setRight(arr[2]);
list.add(node);
}
read.close();
}else{
System.out.println("找不到指定的文件");
}
}catch (Exception e){
System.out.println("读取文件错误!");
e.printStackTrace();
}
return list;
}
- 判断是否为上下文无关文法
private void judgeContextFreeGrammar(List<GrammarNode> list)
2.3 预处理 CFG
-
消除文法中的空产生式
-
从P中去掉单独非终结符直接产生单独空字的产生式,去掉后的产生式集记为P’
removeE(List<GrammarNode> list)
-
把第一步中去掉的产生式代入其他产生式,获得新的产生式,把新的产生式加入P’’
replaceV(List<GrammarNode> list, char V, String[] production_right, Set<GrammarNode> set)
-
把P’’ 并入 P’,再加入新的非终结符,假如为S’,再把S’推出S和S’推出空字加入P’
-
-
消除文法的单一产生式
找到单一产生式,在文法集中找到相应的文法代替右部
private void removeUnitProduction(List<GrammarNode> list)
-
消除文法中的无用非终结符和产生式
private void removeUseless(List<GrammarNode> list){ System.out.println("消除无用符号!"); HashSet<Character> usefulGrammar = new HashSet<>(); usefulGrammar = generate(list); delete(list,usefulGrammar); usefulGrammar = reachable(list); delete(list,usefulGrammar); } private HashSet<Character> generate(List<GrammarNode> list) //获得生成的字符 private HashSet<Character> reachable(List<GrammarNode> list) //获得可达的字符 private void delete(List<GrammarNode> list,HashSet<Character> useful) //删除无用字符
-
消除直接左递归
private void removeLeftRecursive(List<GrammarNode> list)
2.4 转化为Greibach范式
private void greibachFirstStep(List<GrammarNode> list)
private void greibachSecondStep(List<GrammarNode> list)
for (int i=0;i<V.num;i++)
for(int j=0;j<v.num;j++) //v表示V的拷贝
if(p中存在v[j] →a && a中的每个符号都属于N)
{将v[j]从v中删除并加到N中,同时跳出内层循环}
Reach(S){
if(P中存在S→a)
{将a中的所有字符加入到M中,
if(a中存在非终结符B)
Reach(B)}
return }
for(int i=0;i<Q.num;i++){//Q是VUT的集合
for(int j=0;j<N.num;j++){
if(Q[i]不在N中)
将其加入到非“产生的”符号集N1中}}
// 消除全部非“可达的”符号
for(int i=0;i<N1.num;i++){
for(int j=0;j<M.num;j++){
if(N1[i]不在M中)
将其加入到无用符号集NM中}}
//消除产生式和无用符号,因结构类似只写了一个
for(int i=0;i<P.num;i++){
if(P[i]个产生式中含有NM符号集的元素)
将该条产生式删除}}
for(int i=0;i<V.num;i++)
for(int j=0;j<V.num;j++){
if(左部为V[i]的产生式右部所有符号都在V1中)
将V[i]加入V1中,跳出内层循环;
for(int i=0;i<P.num;i++)
if(存在产生式B→a0B1...Bkak∈P,aj∈(VUT)*,Bi∈V1)
以Bi或者空代替Bi,并将形成的产生式加入到P|中;
else if(产生式左部属于V1)
将ε产生式去除后将其加入P|中;
else if(若有S→ε)
引入S|→ε|S。S|为新的开始符号;}
# 消除无用符号
将T加入产生符号集N中
while(N中有新的符号加入)
for(i=0;i<P.num;i++)
if(产生式的右部都在N中)
将产生式的左部加入N中
# 消除无用符号
将S加入可达符号集M中
while(M中有新的符号加入)
for(i=0;i<P.num;i++)
if(产生式的左部在M中)
将产生式的右部中的非终结符加入M中
for(i=0;i<P.num;i++)
if(P[i]为单一产生式A->B,记录其右部部为B,删除该产生式)
for(j=0;j<P.num;j++)
if(P[j]的左部为B)
将B的右部部复制到A->B的右部
将新产生式加入到P中
3. PPT
public void changeToGreibach_total(String filename) {
List<Grammar> list = new LinkedList<>();
list = readRule(list,filename); //读取文法
removeUseless(list); //消除无用符号
removeE(list); //消除空产生式
removeOnlyProduction(list); //消除单一产生式
removeUseless(list);
removeDirectLeftRecursion(list); //消除直接左递归
removeIndirectLeftRecursion(list); //消除间接左递归
removeUseless(list);
getGreibach(list); //转换为Greibach范式
showRules(list);
writeToTxt(list);
}
3.1 消除无用符号
因为所有的终结符都是可产生的,所以先将所有的非终结符加入到可产生符号集N中。这里采用双层循环,外层循环的终止条件为可产生符号集中没有新的符号加入。内层循环每次遍历文法集,找到右部全部为可产生符号的产生式,如果其左部符号不属于可产生符号集中,我们就将其左部符号加入可产生符号集中。直到不满足外层循环的条件,这时的可达符号集中就是所有的可产生符号,我们对比原产生式集合,将不可产生的符号进行删去。
消除主要采用递归的思想。从初始符号S开始,遍历产生式,找到以当前非终结符为左部的产生式,如果右部都为终结符,则将符号都加入到可达符号集中。如果有非终结符,则对此符号进行递归操作。
第二种为非递归思想,与可产生类似,通过不断向可产生符号集中加入可产生的符号,直到没有新的可产生符号终止。
3.2 消除空产生式
遍历文法找到空产生式,删除该产生式,然后记录左部符号。接着遍历文法,找到右部含有该非终结符的产生式,带入空形成新的产生式。将所有新产生式加入到原产生式集中,继续遍历,直到无空产生式。
3.3 消除单一产生式
外层循环,遍历文法找到单一产生式,记录并删除,并记录右部符号。内层循环,遍历文法找到左部为该符号的所有产生式,带入外层记录的单一产生式,将新的产生式加入到产生式集合中。
3.4 消除直接和间接左递归
直接左递归产生式如例所示,分析可以看出,此文法产生的语言必定是以b开头,后跟若干的a。也就是说文法若想停止生成,必然结束于第二条产生式。因此,我们可以将其替换为如下的产生式。
消除间接左递归的方法,核心为转化为直接左递归然后消除。方法将文法中的所有非终结符以 Ai 的形式重新命名。然后要求左部的 i 的值要小于右部 i 的值。实际代码中,遍历所有产生式,对于右部第一个字母是大写字母就用该大写字母的产生式替换,找到是否有直接左递归,有的话就消除。