JAVA implements a simple algebraic operation language compiler (4) -- expression calculation

The last article introduced the lexical analysis part of our compiler. In this article, we mainly discuss how to use JAVA to calculate expressions.


Before this, we must have a place to manage variables . Here we create a new class named Varibles, which has a class variable variblesMap of type HashMap responsible for saving the variable names and values ​​when the program is running. Because it is managed by Map, it will be automatically overwritten when the variable is repeatedly assigned.

package com.liu.system;

import java.util.HashMap;
import java.util.Map;

/*
 * Class for storing variables
 * Created on 2017.3.9
 * @author lyq
 * */
public class Varibles {
	public static Map<String, String> variblesMap = new HashMap<String, String>();
}



Next, let's enter today's topic - the calculation of expressions. The input we accept is a list of strings containing only numbers, addition, subtraction, multiplication, and division signs and parentheses, and the final return is the result of the expression. If the format of the expression is not correct, it should be able to report the corresponding error.


Anyone who has studied data structures should know that there are three ways to traverse a tree: preorder traversal, inorder traversal, and postorder traversal, which correspond to prefix expressions , infix expressions , and postfix expressions , respectively . Infix expressions are the order of expressions that we usually see. Although it is easy to understand by people, it is very difficult for computers to parse infix expressions, mainly because infix expressions have parentheses and are not easy to perform. deal with. Therefore, when calculating the value of an expression, the infix expression is generally converted into a postfix expression, and then the stack is used for evaluation.


Here we choose to convert the infix expression into a postfix expression first, and then evaluate the postfix expression.


For example, the infix expression is a + b*c + (d * e + f) * g , then the postfix expression becomes   abc * + de * f + g * + , and the conversion idea is:

1) If an operand is encountered, we add it directly to the postfix expression.

2) If an operator is encountered, we put it on the stack, and when we encounter an opening parenthesis, we also put it on the stack.

3) If the left bracket is encountered, it is directly pushed onto the stack.

4) If a closing bracket is encountered, the stack element is popped, and the popped operator is added to the suffix expression until the opening bracket is encountered, and the opening bracket is only popped and not added to the suffix expression .

5) If any other operator is encountered, such as ("+", "*", "-", "/"), etc., pop the element from the stack until a lower priority element is encountered (or the stack is empty ) up to . After popping these elements,

Only push the encountered operator onto the stack.

5) If we read the end of the input, pop all elements from the stack in turn and add them to the postfix expression.

Since JDK has built-in Stack, which is the stack, we don't need to write the stack manually, but can use it directly. The JAVA implementation of expression conversion is given below.

	/*
	 * Convert an infix expression to a postfix expression
	 * @param str String to be converted
	 * @return returns the postfix expression
	 * @exception uses an uninitialized variable
	 * */
	private static List<String> changeForm(List<String> list) throws MyException{
		// used to store operator symbols
		Stack<String> symbolStack = new Stack<String>();
		//Used to store numbers and the final suffix expression
		List<String> result = new ArrayList<String>();
		//Used to store the subscript position of the scanned string
		int index = 0;
		
		while(index < list.size()){
			String str = list.get(index);
			//When encountering an integer number, add it directly to the suffix expression
			if(str.matches("[\\d]+")){
				result.add(str);
			}
			//When encountering floating point numbers, add them directly to the suffix expression
			else if(str.matches("[\\d]+\\.[\\d]+")){
				result.add(str);
			}
			/ / When encountering a variable name, go to the variable name set to find out whether there is a variable, if there is, it will be pushed into the stack, if not, an error will be reported
			else if(str.matches("[a-zA-Z]+[a-zA-Z0-9]*")){
				if (SentenceAnalysis.isKeyWord(str)) {
					throw new MyException(Error.NAME_WITH_KEYWORD);
				}
				if (Varibles.variblesMap.containsKey(str)) {
					String value = Varibles.variblesMap.get(str);
					result.add(value);
				}
				// use uninitialized variables for operation
				else{
					throw new MyException(Error.NO_THIS_VARIBLE);
				}
			}
			//+ sign
			else if(str.equals("+")){
				// If the symbol stack is empty, push directly to the stack
				if(symbolStack.empty()){
					symbolStack.push(str);
				}else{
					boolean mark = true;
					while(mark && !symbolStack.empty()){
						String top = symbolStack.peek();
						if(top.equals("+")||top.equals("-")
							||top.equals("*")||top.equals("/")
							){
							result.add(symbolStack.pop());
						}else {
							mark = false;
						}
					}
					symbolStack.push(str);
				}
			}
			//-No
			else if(str.equals("-")){
				// If the symbol stack is empty, push directly to the stack
				if(symbolStack.empty()){
					symbolStack.push(str);
				}else{
					boolean mark = true;
					while(mark && !symbolStack.empty()){
						String top = symbolStack.peek();
						if(top.equals("+")||top.equals("-")
							||top.equals("*")||top.equals("/")
							){
							result.add(symbolStack.pop());
						}else {
							mark = false;
						}
					}
					symbolStack.push(str);
				}
			}
			//*No
			else if(str.equals("*")){
				// If the symbol stack is empty, push directly to the stack
				if(symbolStack.empty()){
					symbolStack.push(str);
				}else{
					boolean mark = true;
					while(mark && !symbolStack.empty()){
						String top = symbolStack.peek();
						if(top.equals("*") || top.equals("/")
							){
							result.add(symbolStack.pop());
						}else {
							mark = false;
						}
					}
					symbolStack.push(str);
				}		
			}
			//divide
			else if(str.equals("/")){
				// If the symbol stack is empty, push directly to the stack
				if(symbolStack.empty()){
					symbolStack.push(str);
				}else{
					boolean mark = true;
					while(mark && !symbolStack.empty()){
						String top = symbolStack.peek();
						if(top.equals("*")||top.equals("/")
							){
							result.add(symbolStack.pop());
						}else {
							mark = false;
						}
					}
					symbolStack.push(str);
				}
			}
			//Left parenthesis directly onto the stack
			else if(str.equals("(")){
				symbolStack.push(str);
			}
			//right parenthesis
			else if (str.equals(")")) {
				boolean mark = true;
				while(mark && !symbolStack.empty()){
					// Stop popping the top element of the stack when encountering the left parenthesis, the left parenthesis does not output
					if(symbolStack.peek().equals("(")){
						symbolStack.pop();
						mark = false;
					}
					//If it is not a left parenthesis, pop the top element of the stack and add it to the postfix expression
					else {
						result.add(symbolStack.pop());
					}
				}
			}
			//cannot find symbol of this type
			else {
				throw new MyException(Error.NO_THIS_TYPE);
			}
			index++;
		}
		while(!symbolStack.empty()){
			result.add(symbolStack.pop());
		}
		return result;
	}
	


The only thing to point out in the above code is that when a variable is encountered, we will first call a static method of the SentenceAnalysis class to determine whether it is a reserved word. This class will be given in the next article for syntax and semantic analysis. If the variable name is a reserved word, an appropriate exception should be thrown. If the variable name is not a reserved word, we can go to the variblesMap of the aforementioned Varibles class to find the variable, and if found, replace the variable in the expression with the corresponding value, and if not found, throw the variable uninitialized exception.


The above method returns a list of strings of suffix expressions. Next, we need to evaluate the suffix expressions. The idea is:

1) Scan the postfix expression from left to right, and push it onto the stack if an operand is encountered.

2) If an operator is encountered, two elements are exited from the stack, the one that exits first is placed on the right side of the operator, the one that exits later is placed on the left side of the operator, and the result after the operation is pushed onto the stack.

3) After the expression is scanned, there is only one element in the stack, which is the result of the operation.


In the second step of the above operation, there may be an exception that the element cannot be found in the stack. In the third step, there may be more than one remaining element in the stack. These are all because the input expression is wrong, and the corresponding error should be reported.

The specific JAVA code implementation of the above operation is as follows:

	/*
	 * postfix expression evaluation
	 * @param list postfix expression
	 * @exception MyException may be thrown when the divisor is 0 or the expression form is wrong. For details, please refer to the exception message
	 * @exception EmptyStackException expression is in wrong form
	 * */
	private static String evaluation(List<String> list) throws MyException,EmptyStackException{
		Stack<String> stack = new Stack<String>();
		//scan for postfix expressions
		for(int i = 0;i < list.size();i++){
			String str = list.get(i);
			/ / Encounter the operand directly into the stack
			if(str.matches("[\\d]+") || str.matches("[\\d]+\\.[\\d]+")){
				stack.push(str);
			}else{
				String snumber1 = stack.pop();
				String snumber2 = stack.pop();
				//All integers. All are converted to integers
				if(snumber1.matches("[\\d]+") && snumber2.matches("[\\d]+")){
					int number1 = Integer.parseInt(snumber1);
					int number2 = Integer.parseInt(snumber2);
					switch (str) {
					case "+":
						stack.push(String.valueOf(number2 + number1));
						break;
					case "-":
						stack.push(String.valueOf(number2 - number1));				
						break;
					case "*":
						stack.push(String.valueOf(number2 * number1));						
						break;
					case "/":
						if(number1 == 0){
							throw new MyException(Error.DIVIDED_BY_ZERO);
						}else{
							stack.push(String.valueOf(number2 / number1));							
						}				
						break;
					}
				}
				// If there is a floating point type, convert it to a floating point type
				else{
					double number1 = Double.parseDouble(snumber1);
					double number2 = Double.parseDouble(snumber2);
					switch (str) {
					case "+":
						stack.push(String.valueOf(number2 + number1));
						break;
					case "-":
						stack.push(String.valueOf(number2 - number1));				
						break;
					case "*":
						stack.push(String.valueOf(number2 * number1));						
						break;
					case "/":
						if(number1 == 0){
							throw new MyException(Error.DIVIDED_BY_ZERO);
						}else{
							stack.push(String.valueOf(number2 / number1));							
						}				
						break;
					}
				}			
			}
		}
		if(stack.size() > 1){
			throw new MyException(Error.WRONG_FORMAT_OF_EXPRESSION);
		}
		return stack.pop();
	}


Since there is one item in the project requirements: the data type of the calculation result is determined by the input expression, so we should consider two cases where the input has only an integer type and a floating-point type when performing the calculation.


Finally, we need to define an external calculation interface for the expression calculation class , which is responsible for implementing the above two steps. The code is as follows:

	/*
	 * External computing interface
	 * @param list String to be calculated
	 * @return returns the result of the calculation
	 */
	public static String forResult(List<String> list) throws MyException,EmptyStackException{
		List<String> result = changeForm(list);
		return evaluation(result);
	}
	

The above is the expression calculation part of the compiler. With this part and the lexical analysis introduced in the previous article, we can implement the syntax and semantic analysis part of the compiler next.


Thanks for reading, see you in the next article!



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325844831&siteId=291194637