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


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

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

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

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

    1、乘除运算

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

    1. options {
    2. STATIC = false;
    3. }
    4. PARSER_BEGIN(Adder)
    5. import java.io.*;
    6. public class Adder {
    7. public static void main(String[] args) {
    8. for (String arg : args) {
    9. try {
    10. System.out.println(evaluate(arg));
    11. } catch (ParseException ex) {
    12. System.err.println(ex.getMessage());
    13. }
    14. }
    15. }
    16. public static long evaluate(String src) throws ParseException {
    17. Reader reader = new StringReader(src);
    18. return new Adder(reader).expr();
    19. }
    20. }
    21. PARSER_END(Adder)
    22. SKIP: { <[" ", "\t", "\r", "\n"]> }
    23. TOKEN: {
    24. <INTEGER: (["0"-"9"])+>
    25. }
    26. long expr() throws NumberFormatException :
    27. {
    28. Token a ;
    29. Token b ;
    30. int value = 0 ;
    31. }
    32. {
    33. a = <INTEGER> {value = Integer.parseInt( a.image );}
    34. (
    35. "*" b = <INTEGER>
    36. {
    37. value *= Integer.parseInt(b.image);
    38. }|
    39. "/" b = <INTEGER>
    40. {
    41. value /= Integer.parseInt(b.image);
    42. }
    43. )
    44. <EOF>
    45. { return value ; }
    46. }

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

    1. C:\Users\feixiaoxing\Desktop\test>java Adder 2*3
    2. 6
    3. C:\Users\feixiaoxing\Desktop\test>java Adder 4/3
    4. 1

    2、四则运算

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

    1. options {
    2. STATIC = false;
    3. }
    4. PARSER_BEGIN(Adder)
    5. import java.io.*;
    6. public class Adder {
    7. public static void main(String[] args) {
    8. for (String arg : args) {
    9. try {
    10. System.out.println(evaluate(arg));
    11. } catch (ParseException ex) {
    12. System.err.println(ex.getMessage());
    13. }
    14. }
    15. }
    16. public static long evaluate(String src) throws ParseException {
    17. Reader reader = new StringReader(src);
    18. return new Adder(reader).expr();
    19. }
    20. }
    21. PARSER_END(Adder)
    22. SKIP: { <[" ", "\t", "\r", "\n"]> }
    23. TOKEN: {
    24. <INTEGER: (["0"-"9"])+>
    25. }
    26. long expr() throws NumberFormatException :
    27. {
    28. long a ;
    29. long b ;
    30. long value = 0 ;
    31. }
    32. {
    33. a = primary() {value = a;}
    34. (
    35. "+" b = primary()
    36. {
    37. value += b;
    38. }|
    39. "-" b = primary()
    40. {
    41. value -= b;
    42. }
    43. )*
    44. <EOF>
    45. { return value ; }
    46. }
    47. long primary() throws NumberFormatException :
    48. {
    49. Token a ;
    50. Token b ;
    51. long value = 0 ;
    52. }
    53. {
    54. a = <INTEGER> {value = Integer.parseInt( a.image );}
    55. (
    56. "*" b = <INTEGER>
    57. {
    58. value *= Integer.parseInt(b.image);
    59. }|
    60. "/" b = <INTEGER>
    61. {
    62. value /= Integer.parseInt(b.image);
    63. }
    64. )*
    65. { return value ; }
    66. }

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

    1. C:\Users\feixiaoxing\Desktop\test>java Adder 1+2
    2. 3
    3. C:\Users\feixiaoxing\Desktop\test>java Adder 2*2
    4. 4
    5. C:\Users\feixiaoxing\Desktop\test>java Adder 1+2*2
    6. 5
    7. C:\Users\feixiaoxing\Desktop\test>java Adder 1*1+2*2
    8. 5
    9. C:\Users\feixiaoxing\Desktop\test>java Adder 1*1+2*4/1
    10. 9

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

    3、带括号的四则运算

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

    1. options {
    2. STATIC = false;
    3. }
    4. PARSER_BEGIN(Adder)
    5. import java.io.*;
    6. public class Adder {
    7. public static void main(String[] args) {
    8. for (String arg : args) {
    9. try {
    10. System.out.println(evaluate(arg));
    11. } catch (ParseException ex) {
    12. System.err.println(ex.getMessage());
    13. }
    14. }
    15. }
    16. public static long evaluate(String src) throws ParseException {
    17. Reader reader = new StringReader(src);
    18. return new Adder(reader).expr();
    19. }
    20. }
    21. PARSER_END(Adder)
    22. SKIP: { <[" ", "\t", "\r", "\n"]> }
    23. TOKEN: {
    24. <INTEGER: (["0"-"9"])+>
    25. }
    26. long expr() throws NumberFormatException :
    27. {
    28. long value = 0 ;
    29. }
    30. {
    31. value = main_expr()
    32. <EOF>
    33. { return value ; }
    34. }
    35. long main_expr() throws NumberFormatException :
    36. {
    37. long a ;
    38. long b ;
    39. long value = 0 ;
    40. }
    41. {
    42. a = primary() {value = a;}
    43. (
    44. "+" b = primary()
    45. {
    46. value += b;
    47. }|
    48. "-" b = primary()
    49. {
    50. value -= b;
    51. }
    52. )*
    53. { return value ; }
    54. }
    55. long primary() throws NumberFormatException :
    56. {
    57. long a ;
    58. long b ;
    59. long value = 0 ;
    60. }
    61. {
    62. a = secondary() {value = a;}
    63. (
    64. "*" b = secondary()
    65. {
    66. value *= b;
    67. }|
    68. "/" b = secondary()
    69. {
    70. value /= b;
    71. }
    72. )*
    73. { return value ; }
    74. }
    75. long secondary() throws NumberFormatException:
    76. {
    77. Token a;
    78. long b = 0;
    79. long value = 0;
    80. }
    81. {
    82. (
    83. a = <INTEGER> {value = Integer.parseInt( a.image );} |
    84. "(" b =main_expr() ")" { value = b;}
    85. )
    86. { return value;}
    87. }

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

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

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

  • 相关阅读:
    【Python开发】FastAPI 07:Depends 依赖注入
    Linux 上的 .NET 如何自主生成 Dump
    【数据结构】Golang 实现单链表
    《互联网的世界》第四讲-拥塞控制与编码
    在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素。
    MallBook 助力SKT思珂特教育集团,立足变化,拥抱敏捷交易
    chmod的用法,及几个权限的宏
    【创建VUE3新项目】从零开始如何创建一个干净的vue3项目【基于前端详细介绍】
    【集合】如果初始化HashMap,传一个17的值,它会怎么处理?
    数字化转型体系化再认识
  • 原文地址:https://blog.csdn.net/feixiaoxing/article/details/127564528