自己写一个文法分类器

文法定义

本文法分类器的实现语言是Java,可以对4种文法进行分类
首先我们先定义文法文件的存储格式:
我们的文法文件都是在一个txt文件中存储,txt的默认格式如下:

S,A,B,C
a,b,c
S->ABC,A->a,B->b,C->c
S

在我们定义的G = (VT , VN , P , S)中

  • 第一行对应VT,也就是我们的非终结符
  • 第二行对应VN,也就是我们的终结符
  • 第三行对应S,是我们的产生式,一个产生式的格式如下: S->ABC ,
    ->是一个产生式的推导符号,左边的S代表产生式的左部,右边的ABC代表产生式的右部
  • 最后一行是文法的开始符号

文法读取

下面我们将利用Java 的文件流来对一个文法文件进行读取

首先把txt文件加载到我们的java类中

File file = new File("grammar_type2.txt");

然后利用FileInputStream读取我们的文件File

FileInputStream fileInputStream = new FileInputStream(file);

但是使用fileInputStream对象能进行的方法只有按照定义的偏移量读取byte流,无法整行读取,所以我们需要利用一个缓冲读的类进行辅助

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));

现在我们可以通过bufferedReader读取到一整行的内容

首先读取我们的变量(非终结符),读取到一整行的数据之后,我们要将它们按照“,”的分隔符进行分割,所以我们就可以利用Java的String类的split()方法
split(String args)方法接收一个String参数,可以将一个大的字符串按照参数分割符切割成几个小的字符串,比如”A,B,C,D”利用split(“,”)之后会返回一个String数组[“A”,”B”,”C”,”D”]

String VN = bufferedReader.readLine();
String []VNs = VN.split(",");

为了让我们后面对产生式的处理更加方便(验证是否存在这个非终结符(终结符)),我们还需要定义一个HashSet来存放我们的变量

HashSet<String> SetVNs = new HashSet<String>(Arrays.asList(VNs));

读取终结符、产生式利用同样的方法

//读取终结符
String VT = bufferedReader.readLine();
String []VTs = VT.split(",");
HashSet<String> SetVTs = new HashSet<String>(Arrays.asList(VTs));

//读取生成式
String P = bufferedReader.readLine();
String []Ps = P.split(",");

最后再读取开始符

//读取开始符
String S = bufferedReader.readLine();


为了进一步对文法进行分类,我们还需要对产生式进行处理
我们定义的一个产生式是这样的“A->BCD”,我们可以以“->”为分割符,将产生式划分为左部和右部,把左部作为key,右部作为value存入HashMap中

//HashMap存储产生式对应关系
HashMap<String,String> map = new HashMap<>();
for(int i = 0;i<Ps.length;i++){
    String[] split = Ps[i].split("->");
    //产生式左部
    String left = split[0];
    //产生式右部
    String right = split[1];
    map.put(left,right);

}

读取完毕后,对这个文法进行输出

System.out.print("非终结符有:");
for (String VNout:
SetVNs) {
    System.out.print(VNout+" ");
}
System.out.println();

System.out.print("终结符有:");
for (String VTout:
        SetVTs) {
    System.out.print(VTout+" ");
}
System.out.println();

System.out.print("产生式有:");
for (String Pout:
        Ps) {
    System.out.print(Pout+" ");
}
System.out.println();

System.out.println("开始符为:"+S);

文法分类

在我们读取完毕了,下面我们对文法进行分类

首先先需要对产生式进行一个判断,如果产生式中出现了终结符和非终结符中不存在的字符,则表示该产生式是非法的,函数如下:

//判断产生式是否合法(非终结符、终结符中是否存在)
private static boolean judgeValid(String left,String right,HashSet<String> SetVNs,HashSet<String> SetVTs){
    //左边必须有一个非终结符
    for(int i = 0;i<left.length();i++){
        String s = (String.valueOf(left.charAt(i)));
        //如果终结符和非终结符中都不包含该字符,返回false
        if(!SetVNs.contains(s)||!SetVTs.contains(s))
            return false;
    }
    for(int i = 0;i<right.length();i++){
        String s = (String.valueOf(left.charAt(i)));
        //如果终结符和非终结符中都不包含该字符,返回false
        if(!SetVNs.contains(s)||!SetVTs.contains(s))
            return false;
    }
    return true;
}

然后我们写判断是否为0型文法的函数

0型文法的定义如下:

设G=(VN,VT,P,S),如果它的每个产生式α→β是这样一种结构:α∈(VN∪VT)且至少含有一个非终结符,而β∈(VN∪VT),则G是一个0型文法。

所以,我们只需要对产生式的左边进行判断:如果产生式的左部没有出现非终结符,则表示该产生式不属于0型文法,在函数中,我们只需要逐一判断非终结符HashSet是否存在该字符即可。

private static boolean judgeZeroGrammar(String left,HashSet<String> SetVNs){
     //左边必须有一个非终结符
    for(int i = 0;i<left.length();i++){
        String s = (String.valueOf(left.charAt(i)));
        if(SetVNs.contains(s))
            return  true;

    }

    return false;
}

在写完判断0型文法的函数之后,我们再来判断1型文法,1型文法的定义如下:

1型(上下文有关):规则α → β 有|α|≤|β| 规则形式:ξAη→ξγη,A ∈ VN,ξ , γ , η ∈(VT , VN) *
, γ ≠ ε

也就是产生式的右部长度一定大于或等于左部的长度,但是也有特例:
α→ε也满足1型文法

所以我们只需要判断左部右部的长度然后再判断左部是否含有非终结符即可

//判断是否为1型文法
private static boolean judgeOneGrammar(String left,String right,HashSet<String> SetVNs){
    if(right.length()>=left.length()){
        //左边必须有一个非终结符
        for(int i = 0;i<left.length();i++){
            String s = (String.valueOf(left.charAt(i)));
            if(SetVNs.contains(s))
                return true;
        }
    }
    //其实已经包含在上面的条件中
    else if(left.length()==1&&right.equals("ε")&&SetVNs.contains(left)){
        return true;
    }
    return false;
}

然后我们再继续写判断为2型文法的函数

2型文法的定义如下:

2型(上下文无关) 规则形式:A → β , A∈VN , β ∈(VT , VN) *

也就是说,产生式的左部必须仅有一个非终结符,所以我们只需要判断产生式左部的长度和符号就行,函数如下:

//判断是否为2型文法
private static boolean judgeTwoGrammar(String left,String right,HashSet<String> SetVNs,HashSet<String> SetVTs){
    //左边必须有且仅有一个非终结符
    if(left.length()==1&&SetVNs.contains(left)){
        return true;

    }
    return false;

}

最后我们还需要3型文法的判断,3型文法较为复杂,定义如下:

A → aB或者A → a(右线性) A → Ba或者A → a(左线性) a∈ VT∪{ε}

所以我们需要作出以下判断:

  1. 判别式的左部必须只有一个字符,且必须是非终结符
  2. 判别式的右边最多只能有两个字符,且当判别式的右边有两个字符时必须有一个为终结符而另一个为非终结符。当判别式的右边只有一个字符时,此字符必须为终结符;
  3. 对于3型文法中的所有产生式,其右边有两个字符的产生式,这些产生式右边两个字符中终结符和非终结符的相对位置一定要固定,如果一个产生式右边的两个字符的排列是:终结符+非终结符,那么所有产生式右边只要有两个字符的,都必须前面是终结符而后面是非终结符,这是右线性文法。要么就全是:非终结符+终结符的形式,称为左线性文法。

我们先对1作出判断

//先判断左边,左边只能有一个字符,而且必须是非终结符
if(left.length()==1&&SetVNs.contains(left)){

}

然后再对右部进行判断
当右部只有一个字符时,并且为终结符是就可以判断是3型文法

//当判别式的右边只有一个字符时,此字符必须为终结符;
if(right.length()==1){
    if(SetVTs.contains(right))
        return true;
}

当右部有两个字符时情况较为复杂

首先我们需要提取出右部的第一个字符和第二个字符

//判断是左线型还是右线型
String first = right.substring(0,1);
String second = right.substring(1);

当第一个字符为非终结符,第二个字符为终结符时,对应产生式是左线性,非终结符也必须是左线性的

我们需要额外一个函数用于判断该非终结符是左线性还是右线性,右线性返回1,左线性返回2,两个都符合返回0,都不符合返回-1

这个函数大概跟判断3型文法的相同,下面重点讲解判断左右线性的代码

//左边为非终结符,右边为终结符,判断是否为左线性
if(SetVNs.contains(first)&&SetVTs.contains(second)){
    if(first.equals(VN)||JudgeVNLinear(first,SetVNs,SetVTs,map)==2)
        return 2;
}

如果第一个为非终结符而且运用递归判断非终结符也为左线性,则返回2

//左边为终结符,右边为非终结符,判断是否为右线性
else if(SetVNs.contains(second)&&SetVTs.contains(first)){
    if(second.equals(VN)||JudgeVNLinear(second,SetVNs,SetVTs,map)==1)
        return 1;
}

如果第一个为终结符而且递归判断第二个的非终结符对应也是右线性则返回1

完整的判断左右线性函数如下:

//  判断该非终结符对应的生成式是右终结符(1)还是左终结符(2),都符合(0),都不符合(-1)
private static int JudgeVNLinear(String VN,HashSet<String> SetVNs,HashSet<String> SetVTs,HashMap<String,String> map){
    //先判断是否为非终结符
    if(SetVNs.contains(VN)){
        //判断产生式是否为3型文法
        String result = map.get(VN);
        //式子右边两个字符
        if(result.length()==2){
            //判断是左线型还是右线型
            String first = result.substring(0,1);
            String second = result.substring(1);
            //左边为非终结符,右边为终结符,判断是否为左线性
            if(SetVNs.contains(first)&&SetVTs.contains(second)){
                if(first.equals(VN)||JudgeVNLinear(first,SetVNs,SetVTs,map)==2)
                    return 2;
            }
            //左边为终结符,右边为非终结符,判断是否为右线性
            else if(SetVNs.contains(second)&&SetVTs.contains(first)){
                if(second.equals(VN)||JudgeVNLinear(second,SetVNs,SetVTs,map)==1)
                    return 1;
            }
        }
        //右边只有一个终结符
        else if(result.length()==1){
            if(SetVTs.contains(result))
                return 0;
        }
    }
    return -1;
}

在作完左右线性的判断之后,我们就可以判断是否为3型文法

当产生式右部有两个字符时,判断是否为左右线性,如果是返回true

//判别式的右边最多只能有两个字符,且当判别式的右边有两个字符时必须有一个为终结符而另一个为非终结符。
if(right.length()==2){
    //判断是左线型还是右线型
    String first = right.substring(0,1);
    String second = right.substring(1);
    //左边为非终结符(对应产生式也为左线性),右边为终结符,判断是为左线性
    if(SetVNs.contains(first)&&SetVTs.contains(second)){
        int flag = JudgeVNLinear(first,SetVNs,SetVTs,map);
        if(flag==0||flag==2)
            return true;
    }
    //左边为终结符,右边为非终结符(对应产生式也为右线性),判断是否为右线性
    else if(SetVNs.contains(second)&&SetVTs.contains(first)){
        int flag = JudgeVNLinear(second,SetVNs,SetVTs,map);
        if(flag==0||flag==1)
            return true;
    }

}

完整的判断3型文法函数如下:

//判断是否为3型文法
private static boolean judgeThreeGrammar(String left,String right,HashSet<String> SetVNs,HashSet<String> SetVTs,HashMap<String,String> map){
    //先判断左边,左边只能有一个字符,而且必须是非终结符
    if(left.length()==1&&SetVNs.contains(left)){

        //判别式的右边最多只能有两个字符,且当判别式的右边有两个字符时必须有一个为终结符而另一个为非终结符。
        if(right.length()==2){
            //判断是左线型还是右线型
            String first = right.substring(0,1);
            String second = right.substring(1);
            //左边为非终结符(对应产生式也为左线性),右边为终结符,判断是为左线性
            if(SetVNs.contains(first)&&SetVTs.contains(second)){
                int flag = JudgeVNLinear(first,SetVNs,SetVTs,map);
                if(flag==0||flag==2)
                    return true;
            }
            //左边为终结符,右边为非终结符(对应产生式也为右线性),判断是否为右线性
            else if(SetVNs.contains(second)&&SetVTs.contains(first)){
                int flag = JudgeVNLinear(second,SetVNs,SetVTs,map);
                if(flag==0||flag==1)
                    return true;
            }

        }
        //当判别式的右边只有一个字符时,此字符必须为终结符;
        else if(right.length()==1){
            if(SetVTs.contains(right))
                return true;
        }

    }
    return false;

}

在写完判断函数之后,我们就可以在main函数里面编写我们的分类函数了

遍历HashMap中的每一个key,得到它们的value
key对应产生式的左部,value对应产生式的右部,先判断是否为3型,再判断是否为2型直到判断是否为0型文法

for (String left:map.keySet()) {
    String right = map.get(left);
    if(!judgeValid(left,right,SetVNs,SetVTs)){
        System.out.println("输入的产生式不合法,请重新输入");
        return;
    }
    //判断是否为3型
    if(judgeThreeGrammar(left,right,SetVNs,SetVTs,map)){
        type = Math.min(type,3);
    }
    else if(judgeTwoGrammar(left,right,SetVNs,SetVTs)){
        type = Math.min(type,2);
    }
    else if(judgeOneGrammar(left,right,SetVNs)){
        type = Math.min(type,1);
    }
    else if(judgeZeroGrammar(left,SetVNs)){
        type = Math.min(type,0);
    }
    //不是文法
    else{
        type = -1;
    }
}

type标志保存了我们的产生式文法的最低等级,在遍历完之后,我们利用switch语句进行结果的输出即可

switch (type){
    case 3:
        System.out.println("该文法是3型文法");
        break;
    case 2:
        System.out.println("该文法是2型文法");
        break;
    case 1:
        System.out.println("该文法是1型文法");
        break;
    case 0:
        System.out.println("该文法是0型文法");
        break;
    case -1:
        System.out.println("该文法不是文法");
        break;
}

最后还需要关闭IO流

//关闭IO流
bufferedReader.close();
fileInputStream.close();

结果验证

为了验证我们的结果,我们定义一些文法txt文件,首先定义3型文法文件

3型文法txt如下:

S,A,B,C
a,b,c
S->Ab,A->a,B->b,C->c
S

程序运行结果:

在这里插入图片描述

2型文法txt如下:

S,A,B,C
a,b,c
S->ABC,A->a,B->b,C->c
S

程序运行结果:
在这里插入图片描述

1型文法txt如下:

S,B,C
a,b,c
S->aSBC,S->aBC,CB->BC,aB->ab,bB->bb,bC->bc,cC->cc
S

程序运行结果:

在这里插入图片描述

0型文法txt如下:

S,B,C
a,b,c
S->aSBC,S->aBC,CB->BC,aBC->ab,bB->bb,bC->bc,cC->cc
S

程序运行结果如下:

在这里插入图片描述
完整的文法分类代码:
https://github.com/cool-boy-klay/-compilationPrinciple/tree/master/compilation_principle_week1

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值