天天看點

java字元串應用之表達式解析器

 一、表達式的組成

    1、數字

    2、運算符:+ - / * ^ % =

    3、圓括号

    4、變量

二、運算符優先級

    由高到低分别為:+-(正負号)、^、*/%、+-、=

    優先級相等的運算符按照從左到右的順序計算

三、關鍵技術點

    1、确定運算的優先級,從高到低分别為:原子元素表達式,包括數字和變量;括号表達式;一進制表達式,取數的負數;指數表達式;乘、除、取模表達式;加、減表達式;指派表達式。

    2、對于每一級别的運算,都由一個方法實作,在方法中先完成比自己高一級别的運算,再處理本級别的運算。是以,在計算整個表達式的主方法中,隻需要調用最低級别的運算的實作方法即可。

    3、确定表達式中的分隔符,(+、-、*、/、%、^、=、(、)、)。利用這些分隔符将表達式分成多段,每一段叫做一個token,分隔符也算token。

    4、用長度為26的int數組vars存儲變量的值。

    5、Character的isWhitespace方法判斷字元是否為空白符,用于去掉表達式中的空白符。

    6、Character的isLetter方法判斷字元是否為字母,用于提取表達式中的變量

    7、Character的isDigit方法判斷字元是否為數字,用于擷取表達式中的數字

四、示範執行個體

package book.oo.String;

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;    //表達式為空錯誤

    public static final int DIVBYZERO_ERROR = 3;    //被0除錯誤

    //針對4種錯誤類型定義的4個錯誤提示

    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]; //變量數組

 public ExpressionParser() {

 }

 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);

  }

  result = this.parseAssign(); //處理指派語句

  //處理完指派語句,應該就是表達式結束符,如果不是,則傳回異常

  if(!this.token.equals(EOE)) {

   this.handleError(SYNTAX_ERROR);

  }

  return result;

 }

 public 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.pareseMulOrDiv(); //用乘除法計算目前表達式的值

  //如果目前标記的第一個字母是加減号,則繼續進行加減運算

  while ((op = this.token.charAt(0)) == ''+'' || op == ''-'') {

   this.getToken(); //取下一個标記

   //用乘除法計算目前子表達式的值

   partialResult = this.pareseMulOrDiv();

   switch(op) {

   case ''-'':

    //如果是減法,則用已處理的子表達式的值減去目前子表達式的值

    result = result - partialResult;

    break;

   case ''+'':

    //如果是加法,用已處理的子表達式的值加上目前子表達式的值

    result = result + partialResult;

    break;

   }

  }

  return result;

 }

 private double pareseMulOrDiv() 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除異常

    if(partialResult == 0.0) {

     this.handleError(DIVBYZERO_ERROR);

    }

    //除數不為0,則進行除法運算

    result = result / partialResult;

    break;

   case ''%'':

    //如果是取模運算,也要判斷目前子表達式的值是否為0

    if(partialResult == 0.0) {

     this.handleError(DIVBYZERO_ERROR);

    }

    result = result % partialResult;

    break;

   }

  }

  return result;

 }

 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;

 }

 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;

 }

 private double findVar(String vname) throws Exception {

  if (!Character.isLetter(vname.charAt(0))) {

   this.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--;

  }

 }

 private void handleError(int errorType) throws Exception {

  //遇到異常情況時,根據錯誤類型,取得異常提示資訊,将提示資訊封裝在異常中抛出

  throw new Exception(ERROR_MESSAGES[errorType]);

 }

 private void getToken() {

  //設定初始值

  this.token = "";

  this.tokenType = NONE_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;

  }

 }

 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 = 5.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));

 }

}

輸出結果:

exp1("a = 5.0") = 5.0

exp2("b = 3.0") = 3.0

exp3("(a + b) * (a - b)") = 16.0

exp4("3*5-4/2") = 13.0

exp5("(4 - 2) * ((a + b) / (a - b))") = 8.0

exp6("5 % 2") = 1.0

exp7("3^2 * 5 + 4") = 49.0

五、執行個體分析

    表達式的解析,實際就是一個表達式的分解過程。根據分隔符将表達式分成若幹段。然後計算每一段的值,最後都會歸結到一個原子表達式。