一个简单的表达式解析器

package lipeng.stringdemo;

/**
 * 一个简单的表达式解析器,这个解析器可以计算由数字、运算符和括号组成的表达式的值,并能处理变量,
 * 为了处理简单,本解析器只支持一个字母的变量,不区分变量字母的大小写。因此,最多只能存储26个变量。
 * 如果用户的变量名长度大于一个字母,则只取第一个字母当作变量。
 */
public class ExpressionParser {

 // 4种标记类型
 /** 标记为空或者结束符 */
 public static final int NONE_TOKEN = 0;
 /** 标记为分隔符*/
 public static final int DELIMITER_TOKEN = 1;
 /** 标记为变量*/
 public static final int VARIABLE_TOKEN = 2;
 /** 标记为数字*/
 public static final int NUMBER_TOKEN = 3;

 // 4种错误类型
 /** 语法错误 */
 public static final int SYNTAX_ERROR = 0;
 /** 括号没有结束错误 */
 public static final int UNBALPARENS_ERROR = 1;
 /** 表达式为空错误 */
 public static final int NOEXP_ERROR = 2;
 /** 被0除错误 */
 public static final int DIVBYZERO_ERROR = 3;
 //针对四种错误类型,定义的四个错误提示
 public static final String[] ERROR_MESSAGES = { "Syntax Error", "Unbalanced Parentheses",
   "No Expression Present", "Division by Zero" };
 /** 表达式的结束标记*/
 public static final String EOE = "/0";
 
 /** 表达式字符串*/
 private String exp; 
 /** 解析器当前指针在表达式中的位置*/
 private int expIndex;  
 /** 解析器当前处理的标记*/
 private String token; 
 /** 解析器当前处理标记的类型*/
 private int tokenType;

 /** 变量的数组*/ 
 private double vars[] = new double[26];

 /**
  * 解析一个表达式,返回表达式的值。
  * @param expStr 表达式字符串
  * @return
  * @throws Exception
  */ 
 public double evaluate(String expStr) throws Exception {
  double result;
  this.exp = expStr;
  this.expIndex = 0;

  //获取第一个标记
  this.getToken();
  if (this.token.equals(EOE)){
   //没有表达式异常
   this.handleError(NOEXP_ERROR); // no expression present
  }

  //处理赋值语句
  result = this.parseAssign();
  //处理完赋值语句,应该就是表达式结束符,如果不是,则返回异常
  if (!this.token.equals(EOE)){ 
   this.handleError(SYNTAX_ERROR);
  }

  return result;
 }

 /**
  * 处理赋值语句
  */ 
 private double parseAssign() throws Exception {
  double result;//结果
  int varIndex;//变量下标
  String oldToken;//旧标记
  int oldTokenType;//旧标记的类型

  //如果标记类型是变量
  if (this.tokenType == VARIABLE_TOKEN) {
   // 保存当前标记 
   oldToken = new String(this.token);
   oldTokenType = this.tokenType;
   // 取得变量的索引,本解析器只支持一个字目的变量,
   //如果用户的变量字母长度大于1,则取第一个字母当作变量 
   varIndex = Character.toUpperCase(this.token.charAt(0)) - 'A';
   
   //获得下一个标记
   this.getToken();
   //如果当前标记不是等号=
   if (!this.token.equals("=")) {
    //回滚
    this.putBack(); 
    // 不是一个赋值语句,将标记恢复到上一个标记 
    this.token = new String(oldToken);
    this.tokenType = oldTokenType;
   } else {
    //如果当前标记是等号=,即给变量赋值,形式如a = 3 + 5;
    //则计算等号后面表达式的值,然后将得到的值赋给变量
    this.getToken();
    //因为加减法的优先级最低,所以计算加减法表达式。
    result = this.parseAddOrSub();
    //将表达式的值赋给变量,并存在实例变量vars中。
    this.vars[varIndex] = result;
    //返回表达式的值
    return result;
   }
  }
  //如果当前标记类型不是变量,或者不是赋值语句,则用加减法计算表达式的值。
  return this.parseAddOrSub();
 }

 /**
  * 计算加减法表达式
  */
 private double parseAddOrSub() throws Exception {
  char op;//操作符
  double result;//结果
  double partialResult;//子表达式的结果
  //用乘除法计算当前子表达式的值
  result = this.parseMulOrDiv();
  //如果当前标记的第一个字母是加减号,则继续进行加减法运算。
  while ((op = this.token.charAt(0)) == '+' || op == '-') {
   //取下一个标记
   this.getToken();
   //用乘除法计算当前子表达式的值
   partialResult = this.parseMulOrDiv();
   switch (op) {
   case '-':
    //如果是减法,则用已处理的子表达式的值减去当前子表达式的值
    result = result - partialResult;
    break;
   case '+':
    //如果是加法,用已处理的子表达式的值加上当前子表达式的值
    result = result + partialResult;
    break;
   }
  }
  return result;
 }

 /**
  * 计算乘除法表达式,包括取模运算
  */
 private double parseMulOrDiv() throws Exception {
  char op;//操作符
  double result;//结果
  double partialResult;//子表达式的结果
  //用指数运算计算当前子表达式的值
  result = this.parseExponent();
  //如果当前标记的第一个字母是乘、除或者取模运算符,则继续进行乘除法运算。
  while ((op = this.token.charAt(0)) == '*' || op == '/' || op == '%') {
   //取下一个标记
   this.getToken();
   //用指数运算计算当前子表达式的值
   partialResult = this.parseExponent();
   switch (op) {
   case '*':
    //如果是乘法,则用已处理子表达式的值乘以当前子表达式的值
    result = result * partialResult;
    break;
   case '/':
    //如果是除法,先判断当前子表达式的值是否为0,如果为0,则抛出被0除异常
    //除数不能为0
    if (partialResult == 0.0){
     this.handleError(DIVBYZERO_ERROR);
    }
    //除数不为0,则进行除法运算
    result = result / partialResult;
    break;
   case '%':
    //如果是取模运算,也要判断当前子表达式的值是否为0
    //如果为0,则抛出被0除异常
    if (partialResult == 0.0){
     this.handleError(DIVBYZERO_ERROR);
    }
    //进行取模运算
    result = result % partialResult;
    break;
   }
  }
  return result;
 }

 /**
  * 计算指数表达式
  * @throws Exception
  */ 
 private double parseExponent() throws Exception {
  double result;//结果
  double partialResult;//子表达式的值
  double ex;//指数的底数
  int t;//指数的幂

  //用一元运算计算当前子表达式的值(底数)
  result = this.parseUnaryOperator();
  //如果当前标记为"^"运算符,则为指数计算
  if (this.token.equals("^")) {
   //获取下一个标记,即获取指数的幂
   this.getToken();
   partialResult = this.parseExponent();
   ex = result;
   if (partialResult == 0.0) {
    //如果指数的幂为0,则指数的值为1
    result = 1.0;
   } else {
    //否则,指数的值为个数为指数幂的底数相乘的结果。
    for (t = (int) partialResult - 1; t > 0; t--){
     result = result * ex;
    }
   }
  }
  return result;
 }

 /**
  * 计算一元运算,+,-,表示正数和复数
  */ 
 private double parseUnaryOperator() throws Exception {
  double result;//结果
  String op;//操作符

  op = "";
  //如果当前标记类型为分隔符,而且分隔符的值等于+或者-。
  if ((this.tokenType == DELIMITER_TOKEN) &&
    this.token.equals("+") || this.token.equals("-")) {
   op = this.token;
   this.getToken();
  }
  //用括号运算计算当前子表达式的值
  result = this.parseBracket();
  if (op.equals("-")){
   //如果操作符为-,则表示负数,将子表达式的值变为负数
   result = -result;
  }

  return result;
 }

 /**
  * 计算括号运算
  */ 
 private double parseBracket() throws Exception {
  double result;//结果
  //如果当前标记为左括号,则表示是一个括号运算
  if (this.token.equals("(")) {
   //取下一个标记
   this.getToken();
   //用加减法运算计算子表达式的值
   result = this.parseAddOrSub();
   //如果当前标记不等于右括号,抛出括号不匹配异常
   if (!this.token.equals(")")){
    this.handleError(UNBALPARENS_ERROR);
   }
   //否则取下一个标记
   this.getToken();
  } else {
   //如果当前标记不是左括号,表示不是一个括号运算,则用原子元素运算计算子表达式的值
   result = this.parseAtomElement();
  }

  return result;
 }

 /**
  * 计算原子元素运算,包括变量和数字
  * @return
  * @throws Exception
  */ 
 private double parseAtomElement() throws Exception {
  double result = 0.0;//结果
  
  switch (this.tokenType) {
  case NUMBER_TOKEN:
   //如果当前标记类型为数字
   try {
    //将数字的字符串转换成数字值
    result = Double.parseDouble(this.token);
   } catch (NumberFormatException exc) {
    this.handleError(SYNTAX_ERROR);
   }
   //取下一个标记
   this.getToken();
   break;
  case VARIABLE_TOKEN:
   //如果当前标记类型是变量,则取变量的值
   result = this.findVar(token);
   this.getToken();
   break;
  default:
   this.handleError(SYNTAX_ERROR);
   break;
  }
  return result;
 }

 /**
  * 根据变量名获取变量的值,如果变量名长度大于1,则只取变量的第一个字符
  * @param vname 变量名
  * @throws Exception
  */
 private double findVar(String vname) throws Exception {
  //如果变量的第一个字符不是字母,则抛出语法异常
  if (!Character.isLetter(vname.charAt(0))) {
   handleError(SYNTAX_ERROR);
   return 0.0;
  }
  //从实例变量数组vars中取出该变量的值
  return vars[Character.toUpperCase(vname.charAt(0)) - 'A'];
 }

 /**
  * 回滚,将解析器当前指针往前移到当前标记位置
  */
 private void putBack() {
  if (this.token == EOE){
   return;
  }
  //解析器当前指针往前移动
  for (int i = 0; i < this.token.length(); i++) {
   this.expIndex--;
  }
 }

 /**
  * 处理异常情况
  * @param errorType 错误类型
  * @throws Exception
  */
 private void handleError(int errorType) throws Exception {
  //遇到异常情况时,根据错误类型,取得异常提示信息,将提示信息封装在异常中抛出
  //可以考虑用自定义异常,而不用Exception
  throw new Exception(ERROR_MESSAGES[errorType]);
 }

 /**
  * 获取下一个标记
  */ 
 private void getToken() {
  //初始值
  this.tokenType = NONE_TOKEN;
  this.token = "";

  // 检查表达式是否结束
  // 如果解析器当前指针已经到达了字符串的长度,则表明表达式已经结束,置当前标记的置为EOE
  if (this.expIndex == this.exp.length()) {
   this.token = EOE;
   return;
  }

  // 跳过表达式中的空白符
  while (this.expIndex < this.exp.length()
    && Character.isWhitespace(this.exp.charAt(this.expIndex))) {
   ++this.expIndex;
  }

  // 再次检查表达式是否结束
  if (this.expIndex == this.exp.length()) {
   this.token = EOE;
   return;
  }
  
  //取得解析器当前指针指向的字符
  char currentChar = this.exp.charAt(this.expIndex);
  //如果当前字符是一个分隔符,则认为这是一个分隔符标记,给当前标记和标记类型赋值,并将指针后移
  if (isDelim(currentChar)) { 
   this.token += currentChar;
   this.expIndex++;
   this.tokenType = DELIMITER_TOKEN;
   
  } else if (Character.isLetter(currentChar)) {
   //如果当前字符是一个字母,则认为是一个变量标记。
   //将解析器指针往后移,直到遇到一个分隔符,之间的字符都是变量的组成部分
   while (!isDelim(currentChar)) {
    this.token += currentChar;
    this.expIndex++;
    if (this.expIndex >= this.exp.length()) {
     break;
    } else {
     currentChar = this.exp.charAt(this.expIndex);
    }
   }
   //设置标记类型为变量
   this.tokenType = VARIABLE_TOKEN;
   
  } else if (Character.isDigit(currentChar)) {
   // 如果当前字符是一个数字,则认为当前标记的类型为数字
   // 将解析器指针往后移,直到遇到一个分隔符,之间的字符都是该数字的组成部分
   while (!isDelim(currentChar)) {
    this.token += currentChar;
    this.expIndex++;
    if (this.expIndex >= this.exp.length()){
     break;
    } else {
     currentChar = this.exp.charAt(this.expIndex);
    }
   }
   //设置标记类型为数字
   this.tokenType = NUMBER_TOKEN;
  } else {
   //无法识别的字符,则认为表达式结束
   this.token = EOE;
   return;
  }
 }

 /**
  * 判断一个字符是否为分隔符。
  * 表达式中的字符包括:
  * 加"+"、减"-"、乘"*"、除"/"、取模"%"、指数"^"、赋值"="、左括号"("、右括号")"
  * @param c
  * @return
  */
 private boolean isDelim(char c) {
  if ((" +-/*%^=()".indexOf(c) != -1))
   return true;
  return false;
 }

 public static void main(String[] args) throws Exception {
  ExpressionParser test = new ExpressionParser();
  String exp1 = "a = 5.0";
  System.out.println("exp1(/"a = 4.0/") = " + test.evaluate(exp1));
  String exp2 = "b = 3.0";
  System.out.println("exp2(/"b = 3.0/") = " + test.evaluate(exp2));

  String exp3 = "(a+b) * (a-b)";
  System.out.println("exp3(/"(a+b) * (a-b)/") = " + test.evaluate(exp3));

  String exp4 = "3*5-4/2";
  System.out.println("exp4(/"3*5-4/2/") = " + test.evaluate(exp4));

  String exp5 = "(4-2)*((a+b)/(a-b))";
  System.out.println("exp5(/"(4-2)*((a+b)/(a-b))/") = " + test.evaluate(exp5));
  
  String exp6 = "5 % 2";
  System.out.println("exp6(/"5%2/") = " + test.evaluate(exp6));
  String exp7 = "3^2 * 5 + 4";
  System.out.println("exp7(/"3^2 * 5 + 4/") = " + test.evaluate(exp7));
  
  /**
   * 一个简单的表达式根据运算时的优先级从高到底为:
   * (1)原子元素表达式,包括数字和变量;
   * (2)括号表达式;
   * (3)一元表达式,取数的负数;
   * (4)指数表达式;
   * (5)乘、除、取模表达式;
   * (6)加、减表达式
   * (7)赋值表达式;
   * 因此,在计算一个表达式的值时,应该按优先级从高到底进行运算。
   * 在本程序中,每个优先级的表达式的运算都用一个私有方法实现。在私有方法中,首先计算更高优先级的表达式。
   * 即采用了类似递归调用的方式,尽管在evaluate方法中最先调用的是优先级最低的表达式的值,
   * 但在实质上却是优先级最高的表达式的私有方法最先被执行完。这就保证了表达式的运算是按照优先级从高到底的顺序执行的。
   */
 }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值