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

Posted 嵌入式-老费

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译器一日一练(DIY系列之四则运算)相关的知识,希望对你有一定的参考价值。

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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

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

以上是关于编译器一日一练(DIY系列之四则运算)的主要内容,如果未能解决你的问题,请参考以下文章

编译器一日一练(DIY系列之总结)

编译器一日一练(DIY系列之总结)

编译器一日一练(DIY系列之简单语法树)

编译器一日一练(DIY系列之简单语法树)

编译器一日一练(DIY系列之汇编输出)

编译器一日一练(DIY系列之汇编优化)