zl程序教程

您现在的位置是:首页 >  工具

当前栏目

编译器一日一练(DIY系列之四则运算)

编译器 系列 DIY 一日 四则运算
2023-09-27 14:27:10 时间

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        前面说到了javacc,也谈到了加法运算、减法运算、加减法运算、连续加减法运算。今天正好可以说一下乘法、除法运算。大家都知道,运算里面是分优先级的。也就是说,表达式里面,如果有乘法和除法,那么先做乘除,再做其他的加减运算。

        因此为了实现四则运算,我们需要先处理乘除运算,再慢慢加入加减运算。

        代码链接:https://github.com/feixiaoxing/DIYCompiler

1、乘除运算

        单独的乘除运算还是比较简单的,只需要把原来的加减运算替换一下符号即可。比如,修改成这样,

options {
    STATIC = false;
}
 
PARSER_BEGIN(Adder)
import java.io.*;
public class Adder {
    public static void main(String[] args) {
        for (String arg : args) {
            try {
                System.out.println(evaluate(arg));
            } catch (ParseException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }
 
    public static long evaluate(String src) throws ParseException {
        Reader reader = new StringReader(src);
        return new Adder(reader).expr();
    }
}
PARSER_END(Adder)
 
SKIP: { <[" ", "\t", "\r", "\n"]> }
TOKEN: {
    <INTEGER: (["0"-"9"])+>
}
 
long expr() throws NumberFormatException :
{
    Token a ;
    Token b ;
    int value = 0 ;
}
{
	a = <INTEGER> {value = Integer.parseInt( a.image );}
    (
	 "*" b = <INTEGER>
    { 
		value  *= Integer.parseInt(b.image); 
	}|
	 "/" b = <INTEGER>
    { 
		value /= Integer.parseInt(b.image); 
	}
	)
    <EOF>
    { return value ; }
}

        有了这一步修改之后,不管是乘法,还是除法,都是可以拿来进行处理的。生成java代码、编译之后,就会有这样的效果出来,


C:\Users\feixiaoxing\Desktop\test>java Adder 2*3
6

C:\Users\feixiaoxing\Desktop\test>java Adder 4/3
1

2、四则运算

        有了乘除,有了加减,下面考虑的就是怎么把它们整合在一起了。这中间涉及到一个优先级的概念。在语法表达式当中,优先级越高的,越靠近变量表达式;优先级越低的,越远离变量表达式。这么说可能有一点艰深晦涩,下面可以用具体的实例来表达,

options {
    STATIC = false;
}
 
PARSER_BEGIN(Adder)
import java.io.*;
public class Adder {
    public static void main(String[] args) {
        for (String arg : args) {
            try {
                System.out.println(evaluate(arg));
            } catch (ParseException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }
 
    public static long evaluate(String src) throws ParseException {
        Reader reader = new StringReader(src);
        return new Adder(reader).expr();
    }
}
PARSER_END(Adder)
 
SKIP: { <[" ", "\t", "\r", "\n"]> }
TOKEN: {
    <INTEGER: (["0"-"9"])+>
}
 
long expr() throws NumberFormatException :
{
    long a ;
    long b ;
    long value = 0 ;
}
{
	a = primary() {value = a;}
    (
	 "+" b = primary()
    { 
		value  += b; 
	}|
	 "-" b = primary()
    { 
		value -= b; 
	}
	)*
    <EOF>
    { return value ; }
}

long primary() throws NumberFormatException :
{
    Token a ;
    Token b ;
    long value = 0 ;
}
{
	a = <INTEGER> {value = Integer.parseInt( a.image );}
    (
	 "*" b = <INTEGER>
    { 
		value  *= Integer.parseInt(b.image); 
	}|
	 "/" b = <INTEGER>
    { 
		value /= Integer.parseInt(b.image); 
	}
	)*
    { return value ; }
}

        大家可以观察一下,整个代码除了之前expr之外,还多了一个primary。此外,expr中的描述变成了加减运算,primary则变成了乘除运算。有了这个改变,javacc就会帮我们优先处理乘除,其次再处理加减了。再次经过javacc处理生成java、编译之后,就会有这样的计算效果,

C:\Users\feixiaoxing\Desktop\test>java Adder 1+2
3

C:\Users\feixiaoxing\Desktop\test>java Adder 2*2
4

C:\Users\feixiaoxing\Desktop\test>java Adder 1+2*2
5

C:\Users\feixiaoxing\Desktop\test>java Adder 1*1+2*2
5

C:\Users\feixiaoxing\Desktop\test>java Adder 1*1+2*4/1
9

        是不是看上去还蛮容易的。可是换一个思路,大家如果不用javacc,单纯用编程语言去处理,可以考虑一下,代码需要写多久。我想,这就是javacc这一类工具的厉害之处吧。

3、带括号的四则运算

        括号的引入在于,某些情况下,我们需要强制进行先加减、再乘除的运算。比如这样,(2+3)*5-1,这个时候2+3就变成了最高优先级。所以大家可以考虑下,jj文件应该如何修改。下面给出的是我这里的一个方法,供大家参考,

options {
    STATIC = false;
}
 
PARSER_BEGIN(Adder)
import java.io.*;
public class Adder {
    public static void main(String[] args) {
        for (String arg : args) {
            try {
                System.out.println(evaluate(arg));
            } catch (ParseException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }
 
    public static long evaluate(String src) throws ParseException {
        Reader reader = new StringReader(src);
        return new Adder(reader).expr();
    }
}
PARSER_END(Adder)
 
SKIP: { <[" ", "\t", "\r", "\n"]> }
TOKEN: {
    <INTEGER: (["0"-"9"])+>
}
 
long expr() throws NumberFormatException :
{
    long value = 0 ;
}
{
	value = main_expr() 
    <EOF>
    { return value ; }
}

long main_expr() throws NumberFormatException :
{
    long a ;
    long b ;
    long value = 0 ;
}
{
	a = primary() {value = a;}
    (
	 "+" b = primary()
    { 
		value  += b; 
	}|
	 "-" b = primary()
    { 
		value -= b; 
	}
	)*
    { return value ; }
}

long primary() throws NumberFormatException :
{
    long a ;
    long b ;
    long value = 0 ;
}
{
	a = secondary() {value = a;}
    (
	 "*" b = secondary()
    { 
		value  *= b; 
	}|
	 "/" b = secondary()
    { 
		value /= b; 
	}
	)*
    { return value ; }
}

long secondary() throws NumberFormatException:
{
	Token a;
	long b = 0;
	long value = 0;
}
{
	(
		a = <INTEGER> {value = Integer.parseInt( a.image );} |
		"(" b =main_expr() ")" { value = b;}
	)
	
	{ return value;}
}

        和之前的四则运算相比较,这里引入了main_expr和secondary两个表达式。之所以引入main_expr,我们先看一下secondary。它总共有两种形式,一种是INTEGER,这个无可厚非。另外一种是"(" main_expr() ")",看到这里大家应该就明白了。括号中的内容就是之前expr的内容。但是因为expr中包含有<EOF>,所以需要把expr提出去,重新创建一个main_expr作为新的、可以供递归来解析的表达式来处理。代码弄好了,接着就可以拿出来编译测试了,

C:\Users\feixiaoxing\Desktop\test>java Adder (1+2)*3
9

C:\Users\feixiaoxing\Desktop\test>java Adder (1+2)*3-1+(2+3)*4
28

C:\Users\feixiaoxing\Desktop\test>java Adder (1+2)*3-1+(2+3)*4-5-6-7-8
2

        从实验效果来看,加了括号之后的表达式效果更好,灵活性也更强。再想一想,如果不依靠工具,仅仅是手写代码,实现这样的四则表达式功能,不知道要走多少冤枉路。