Chinaunix首页 | 论坛 | 博客
  • 博客访问: 6282
  • 博文数量: 6
  • 博客积分: 1450
  • 博客等级: 上尉
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-24 21:52
文章分类

全部博文(6)

文章存档

2011年(2)

2010年(2)

2009年(2)

我的朋友
最近访客

分类: Java

2011-03-10 22:42:38

  对于形如2*2+2/1+3的算术表达式,如果不将优先级顺序考虑进去的话,那么解析如上的表达式十分容易,

a = get first operand
while(operand present){
op = get operator
b = get second operand
a = a op b
}
  如果将优先级考虑进去的话,而且还使用上述算法,那么复杂度可想而知.在此,我用递归下降的方式实现解析有优先级的算术表达式.

  在此解析的算术表达式,由如下元素组成:

   数字
运算符+ - * / %
  运算符的优先级如下

  % / * > + -

  优先级相等的运算符从左向右顺序计算

  在使用递归向下解析器时,表达式被视为递归的数据结构,那么所有的表达式可以由如下的规则生成

  表达式 ->项 [+项][-项]

  项 -> 因数 [*因数][/因数][%因数]

  因数 -> 数字 或表达式

  注意,上面的生成规则是以表达式中只包含+-*/%运算符且无变量为前提的,而且生成规则也包含了运算优先级,下面举例说明表达式的解析过程

  2*3+2

  有两个项,分别为2*3和2,前者包含两个因素2,3

  下面以一个例子跟踪递归向下解析过程

  2*3+3*4

  获得第一项2*3
计算得到6
获得第二项3*4
计算得到12
从第二项递归计算过程返回
6+12计算得到18
  为计算表达式的值,需要对表达式进行分解,例如2*3-4可以分解成2,*,3,-,4四个元素,这些元素在解析器术语中称为标识符,是一个不能再分的独立单元.为了将表达式分解一独立的元素单元,需要设计一个过程,该过程能从头至尾扫描整个表达式,顺序地返回每个元素,并且能够识别每个元素的,在本解析器,实现该功能的函数称为getToken.

  本文的解析器封装在一个Parser的类中,为对getToken功能有个更好的理解,现说明它的第一部分

  1. //解析器
  2. class Parser{
  3. //标识符的类型种类
  4. final int NONE = 0;
  5. final int NUMBER= 1;
  6. final int DELIMITER = 2;
  7. //异常的类型
  8. final int NOEXP = 0;
  9. final int SYNTAX = 1;
  10. final int DIVBYZERO = 2;

  11. final String EOE = "\0";//标明表达式结尾
  12. private int tokType;//用于存放标识符类型
  13. private String token;//用于存放标识符
  14. private String exp;//用于存放表达式
  15. private int expIdx;
  解析器解析表达式时,每个标识符必须有与之关联的标识符,本解析器只用到2种类型,分别为NUMBER,DELIMITER.此外NONE类型只是当标识符未定义时的一个占位符.

  此外,Parser类还定义几个异常,其中NOEXP是当解析器解析时没有表达式,SYNTAX代表表达式不符合规则的错误,DIVBYZERO代表除数为0时的错误.

  final变量EOE表示解析器己达到表达式的结尾.

  被解析的表达式己字符串形式保存,exp保存该字符串的一个引用,exIdx保存下一个标识符在exp中的索引,初始值为0.当前标识符保存在token中,其类型则保存在tokType.这些变量都是private型,只允许解析器自己访问而不能被外部代码修改.

  下面列出getToken函数的完整代码,每调用一次getToken(),将得到表达式的下一个标识,也就是exp[expIdx]后的一个标识.getToken()将标识符保存在token中,标识符类型则保存在tokType之中.

  1. //获得下一个标识符
  2. private void getToken(){
  3. token = "";
  4. tokType = NONE;

  5. //检查表达式是否到达末尾
  6. if(expIdx == exp.length()){
  7. token = EOE;
  8. return;
  9. }

  10. //去掉空格
  11. while(expIdx < exp.length() && Character.isWhitespace(exp.charAt(expIdx))) expIdx++;

  12. //当表达式以空格结束
  13. if(expIdx >= exp.length())
  14. {
  15. token = EOE;
  16. return;
  17. }

  18. if(isDelim(exp.charAt(expIdx))){//是运算符
  19. token += exp.charAt(expIdx));
  20. tokType = DELIMITER;
  21. expIdx ++;
  22. } else if(Character.isDigit(exp.charAt(expIdx))){//是数字
  23. while(!isDelim(exp.charAt(expIdx))){
  24. token += exp.charAt(expIdx);
  25. expIdx ++;
  26. if(expIdx >= exp.length())
  27. break;
  28. }
  29. tokType = NUMBER;
  30. } else {//不知名的字符结束字符串
  31. token = EOE;
  32. return;
  33. }
  34. }

  35. private boolean isDelim(char c){//判断字符是否是运算符
  36. if((" +-*/%").indexOf(c) != -1)
  37. return true;
  38. return false;
  39. }

  40. }
  下面简单分析下getToken().getToken()首先做初始化工作,然后查看expIdx是否等于表达式的.由于expIdx保存的是解析器解析表达当前的进度,如果expIdx和exp.length(),那么表明解析器完成了表达式的解析.

  如果解析器还能找到未处理的标识符,则解析过程继续进行.首先跳过下一个标识符之前所有的空格,如果表达式以空格结尾,则返回EOE结尾.根据exp[expIdx]后的一个字符的类型不同,getToken()对当前标识符的处理过程不同.如果一个字符为运算符,那么getToken()将当前标识符保存在token中,并将tokType设置为DELIMITER.若下一个字符为数字,token保存当前标识符,并将tokType设为NUMBER.如果下一个字符不为以上两种之一,则token保存EOE返回.

  下面为解析器的代码,这个解析器只能解析由数字和运算符组成的表达式,其中运行符只包含+-*/%.

  1. class ParserException extends Exception{
  2. private String error;
  3. public ParserException(String error){
  4. this.error = error;
  5. }
  6. public String toString(){
  7. return error;
  8. }
  9. }
  10. class Parser {

  11. final int NONE = 0;
  12. final int NUMBER = 1;
  13. final int DELIMITER = 2;

  14. final int NOEXP = 0;
  15. final int SYNTAX = 1;
  16. final int DIVBYZERO = 2;


  17. final String EOE = "\0";
  18. private String exp;
  19. private String token;
  20. private int expIdx;
  21. private int tokType;

  22. //解析入口
  23. public double evaluate(String expStr) throws ParserException{
  24. this.exp = expStr;
  25. this.expIdx = 0;
  26. double result;

  27. getToken();
  28. if(token.equals(EOE)){
  29. handleErr(NOEXP);
  30. }

  31. result = evalExp1();

  32. if(!token.equals(EOE))
  33. {
  34. handleErr(SYNTAX);
  35. }
  36. return result;
  37. }
  38. //加或减
  39. private double evalExp1() throws ParserException{
  40. double result;
  41. double partialResult;
  42. char op;

  43. result = evalExp2();

  44. while((op = token.charAt(0)) == '+' || op == '-'){
  45. getToken();
  46. partialResult = evalExp2();
  47. switch(op){
  48. case '+':result += partialResult;break;
  49. case '-':result -=partialResult;break;
  50. }
  51. }
  52. return result;
  53. }
  54. //乘或除或取余
  55. private double evalExp2() throws ParserException{
  56. double result;
  57. double partialResult;
  58. char op;

  59. result = atom();

  60. while((op = token.charAt(0)) == '*' || op == '/' || op == '%'){
  61. getToken();
  62. partialResult = atom();
  63. switch(op){
  64. case '*':result *= partialResult;break;
  65. case '/':if(partialResult == 0.0) handleErr(DIVBYZERO);result /=partialResult;break;
  66. case '%':result %= partialResult;break;
  67. }
  68. }
  69. return result;
  70. }
  71. //获得数的值
  72. private double atom() throws ParserException{
  73. double result = 0.0;

  74. switch(tokType){
  75. case NUMBER:try{
  76. result = Double.parseDouble(token);
  77. getToken();
  78. }catch(NumberFormatException exc){
  79. handleErr(SYNTAX);
  80. }
  81. break;
  82. default:handleErr(SYNTAX);
  83. }
  84. return result;
  85. }
  86. //错误处理
  87. private void handleErr(int error) throws ParserException{
  88. String[] errs = {
  89. "表达式不存在",
  90. "表达式不符合规则",
  91. "除数为0"};
  92. throw new ParserException(errs[error]);
  93. }
  94. //获得下一个标识符
  95. private void getToken(){
  96. token = "";
  97. tokType = NONE;

  98. //检查表达式是否到达末尾
  99. if(expIdx == exp.length()){
  100. token = EOE;
  101. return;
  102. }

  103. //去掉空格
  104. while(expIdx < exp.length() && Character.isWhitespace(exp.charAt(expIdx))) expIdx++;

  105. //当表达式以空格结束
  106. if(expIdx >= exp.length())
  107. {
  108. token = EOE;
  109. return;
  110. }

  111. if(isDelim(exp.charAt(expIdx))){//是运算符
  112. token += exp.charAt(expIdx);
  113. tokType = DELIMITER;
  114. expIdx ++;
  115. } else if(Character.isDigit(exp.charAt(expIdx))){//是数字
  116. while(!isDelim(exp.charAt(expIdx))){
  117. token += exp.charAt(expIdx);
  118. expIdx ++;
  119. if(expIdx >= exp.length())
  120. break;
  121. }
  122. tokType = NUMBER;
  123. } else {//不知名的字符结束字符串
  124. token = EOE;
  125. return;
  126. }
  127. }

  128. private boolean isDelim(char c){//判断字符是否是运算符
  129. if((" +-*/%").indexOf(c) != -1)
  130. return true;
  131. return false;
  132. }
  133. }
 在代码最开始部分声明了一个ParserException类,这是一个异常类,当解析器解析表达式时就会根据异常类抛出特定的,该异常的处理需要使用该解析器的主程序处理.使用该解析器的方法是先实例化一个Parser,然后将一个表达式字符串传入该实例的evaluate方法,该方法返回最终的结果.下面的代码 说明解析器的使用方法.

  1. import java.io.*;
  2. public class PDemo{
  3. public static void main(String[] args){
  4. String expr;
  5.            BufferedReader br = new
  6.                BufferedReader(new InputStreamReader(System.in));    
  7.            Parser p = new Parser();
  8.            System.out.println("Enter an empty expression to stop");
  9.            for(;;){
  10.                System.out.print("Enter expression:");
  11.                expr = br.readLine();
  12.                if(expr.equals("")) break;
  13.                try{
  14.                    System.out.println("Result :" +p.evaluate(expr));
  15.                    System.out.println();
  16.                }catch(ParserException exc){
  17.                    System.out.println(exc);
  18.                }
  19.                }
  20. }
  21. }

转载请指明出处www.cnblogs.com/notifyer
阅读(313) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~