diff options
Diffstat (limited to 'src/main/java/org/unitConverter/math/ExpressionParser.java')
| -rw-r--r-- | src/main/java/org/unitConverter/math/ExpressionParser.java | 735 | 
1 files changed, 0 insertions, 735 deletions
diff --git a/src/main/java/org/unitConverter/math/ExpressionParser.java b/src/main/java/org/unitConverter/math/ExpressionParser.java deleted file mode 100644 index deee51d..0000000 --- a/src/main/java/org/unitConverter/math/ExpressionParser.java +++ /dev/null @@ -1,735 +0,0 @@ -/** - * Copyright (C) 2019 Adrien Hopkins - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program.  If not, see <https://www.gnu.org/licenses/>. - */ -package org.unitConverter.math; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Deque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.BinaryOperator; -import java.util.function.Function; -import java.util.function.UnaryOperator; - -/** - * An object that can parse expressions with unary or binary operators. - *  - * @author Adrien Hopkins - * @param <T> type of object that exists in parsed expressions - * @since 2019-03-14 - * @since v0.2.0 - */ -public final class ExpressionParser<T> { -	/** -	 * A builder that can create {@code ExpressionParser<T>} instances. -	 *  -	 * @author Adrien Hopkins -	 * @param <T> type of object that exists in parsed expressions -	 * @since 2019-03-17 -	 * @since v0.2.0 -	 */ -	public static final class Builder<T> { -		/** -		 * A function that obtains a parseable object from a string. For example, -		 * an integer {@code ExpressionParser} would use -		 * {@code Integer::parseInt}. -		 *  -		 * @since 2019-03-14 -		 * @since v0.2.0 -		 */ -		private final Function<String, ? extends T> objectObtainer; -		 -		/** -		 * The function of the space as an operator (like 3 x y) -		 *  -		 * @since 2019-03-22 -		 * @since v0.2.0 -		 */ -		private String spaceFunction = null; -		 -		/** -		 * A map mapping operator strings to operator functions, for unary -		 * operators. -		 *  -		 * @since 2019-03-14 -		 * @since v0.2.0 -		 */ -		private final Map<String, PriorityUnaryOperator<T>> unaryOperators; -		 -		/** -		 * A map mapping operator strings to operator functions, for binary -		 * operators. -		 *  -		 * @since 2019-03-14 -		 * @since v0.2.0 -		 */ -		private final Map<String, PriorityBinaryOperator<T>> binaryOperators; -		 -		/** -		 * Creates the {@code Builder}. -		 *  -		 * @param objectObtainer a function that can turn strings into objects of -		 *                       the type handled by the parser. -		 * @throws NullPointerException if {@code objectObtainer} is null -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public Builder(final Function<String, ? extends T> objectObtainer) { -			this.objectObtainer = Objects.requireNonNull(objectObtainer, -					"objectObtainer must not be null."); -			this.unaryOperators = new HashMap<>(); -			this.binaryOperators = new HashMap<>(); -		} -		 -		/** -		 * Adds a binary operator to the builder. -		 *  -		 * @param text     text used to reference the operator, like '+' -		 * @param operator operator to add -		 * @param priority operator's priority, which determines which operators -		 *                 are applied first -		 * @return this builder -		 * @throws NullPointerException if {@code text} or {@code operator} is -		 *                              null -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public Builder<T> addBinaryOperator(final String text, -				final BinaryOperator<T> operator, final int priority) { -			Objects.requireNonNull(text, "text must not be null."); -			Objects.requireNonNull(operator, "operator must not be null."); -			 -			// Unfortunately, I cannot use a lambda because the -			// PriorityBinaryOperator requires arguments. -			final PriorityBinaryOperator<T> priorityOperator = new PriorityBinaryOperator<>( -					priority) { -				@Override -				public T apply(final T t, final T u) { -					return operator.apply(t, u); -				} -				 -			}; -			this.binaryOperators.put(text, priorityOperator); -			return this; -		} -		 -		/** -		 * Adds a function for spaces. You must use the text of an existing binary -		 * operator. -		 *  -		 * @param operator text of operator to use -		 * @return this builder -		 * @since 2019-03-22 -		 * @since v0.2.0 -		 */ -		public Builder<T> addSpaceFunction(final String operator) { -			Objects.requireNonNull(operator, "operator must not be null."); -			 -			if (!this.binaryOperators.containsKey(operator)) -				throw new IllegalArgumentException(String -						.format("Could not find binary operator '%s'", operator)); -			 -			this.spaceFunction = operator; -			return this; -		} -		 -		/** -		 * Adds a unary operator to the builder. -		 *  -		 * @param text     text used to reference the operator, like '-' -		 * @param operator operator to add -		 * @param priority operator's priority, which determines which operators -		 *                 are applied first -		 * @return this builder -		 * @throws NullPointerException if {@code text} or {@code operator} is -		 *                              null -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public Builder<T> addUnaryOperator(final String text, -				final UnaryOperator<T> operator, final int priority) { -			Objects.requireNonNull(text, "text must not be null."); -			Objects.requireNonNull(operator, "operator must not be null."); -			 -			// Unfortunately, I cannot use a lambda because the -			// PriorityUnaryOperator requires arguments. -			final PriorityUnaryOperator<T> priorityOperator = new PriorityUnaryOperator<>( -					priority) { -				@Override -				public T apply(final T t) { -					return operator.apply(t); -				} -			}; -			this.unaryOperators.put(text, priorityOperator); -			return this; -		} -		 -		/** -		 * @return an {@code ExpressionParser<T>} instance with the properties -		 *         given to this builder -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public ExpressionParser<T> build() { -			return new ExpressionParser<>(this.objectObtainer, this.unaryOperators, -					this.binaryOperators, this.spaceFunction); -		} -	} -	 -	/** -	 * A binary operator with a priority field that determines which operators -	 * apply first. -	 *  -	 * @author Adrien Hopkins -	 * @param <T> type of operand and result -	 * @since 2019-03-17 -	 * @since v0.2.0 -	 */ -	private static abstract class PriorityBinaryOperator<T> -			implements BinaryOperator<T>, Comparable<PriorityBinaryOperator<T>> { -		/** -		 * The operator's priority. Higher-priority operators are applied before -		 * lower-priority operators -		 *  -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		private final int priority; -		 -		/** -		 * Creates the {@code PriorityBinaryOperator}. -		 *  -		 * @param priority operator's priority -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public PriorityBinaryOperator(final int priority) { -			this.priority = priority; -		} -		 -		/** -		 * Compares this object to another by priority. -		 *  -		 * <p> -		 * {@inheritDoc} -		 * </p> -		 *  -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		@Override -		public int compareTo(final PriorityBinaryOperator<T> o) { -			if (this.priority < o.priority) -				return -1; -			else if (this.priority > o.priority) -				return 1; -			else -				return 0; -		} -		 -		/** -		 * @return priority -		 * @since 2019-03-22 -		 * @since v0.2.0 -		 */ -		public final int getPriority() { -			return this.priority; -		} -	} -	 -	/** -	 * A unary operator with a priority field that determines which operators -	 * apply first. -	 *  -	 * @author Adrien Hopkins -	 * @param <T> type of operand and result -	 * @since 2019-03-17 -	 * @since v0.2.0 -	 */ -	private static abstract class PriorityUnaryOperator<T> -			implements UnaryOperator<T>, Comparable<PriorityUnaryOperator<T>> { -		/** -		 * The operator's priority. Higher-priority operators are applied before -		 * lower-priority operators -		 *  -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		private final int priority; -		 -		/** -		 * Creates the {@code PriorityUnaryOperator}. -		 *  -		 * @param priority operator's priority -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		public PriorityUnaryOperator(final int priority) { -			this.priority = priority; -		} -		 -		/** -		 * Compares this object to another by priority. -		 *  -		 * <p> -		 * {@inheritDoc} -		 * </p> -		 *  -		 * @since 2019-03-17 -		 * @since v0.2.0 -		 */ -		@Override -		public int compareTo(final PriorityUnaryOperator<T> o) { -			if (this.priority < o.priority) -				return -1; -			else if (this.priority > o.priority) -				return 1; -			else -				return 0; -		} -		 -		/** -		 * @return priority -		 * @since 2019-03-22 -		 * @since v0.2.0 -		 */ -		public final int getPriority() { -			return this.priority; -		} -	} -	 -	/** -	 * The types of tokens that are available. -	 *  -	 * @author Adrien Hopkins -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private static enum TokenType { -		OBJECT, UNARY_OPERATOR, BINARY_OPERATOR; -	} -	 -	/** -	 * The opening bracket. -	 *  -	 * @since 2019-03-22 -	 * @since v0.2.0 -	 */ -	public static final char OPENING_BRACKET = '('; -	 -	/** -	 * The closing bracket. -	 *  -	 * @since 2019-03-22 -	 * @since v0.2.0 -	 */ -	public static final char CLOSING_BRACKET = ')'; -	 -	/** -	 * Finds the other bracket in a pair of brackets, given the position of one. -	 *  -	 * @param string          string that contains brackets -	 * @param bracketPosition position of first bracket -	 * @return position of matching bracket -	 * @throws NullPointerException if string is null -	 * @since 2019-03-22 -	 * @since v0.2.0 -	 */ -	private static int findBracketPair(final String string, -			final int bracketPosition) { -		Objects.requireNonNull(string, "string must not be null."); -		 -		final char openingBracket = string.charAt(bracketPosition); -		 -		// figure out what closing bracket to look for -		final char closingBracket; -		switch (openingBracket) { -		case '(': -			closingBracket = ')'; -			break; -		case '[': -			closingBracket = ']'; -			break; -		case '{': -			closingBracket = '}'; -			break; -		default: -			throw new IllegalArgumentException( -					String.format("Invalid bracket '%s'", openingBracket)); -		} -		 -		// level of brackets. every opening bracket increments this; every closing -		// bracket decrements it -		int bracketLevel = 0; -		 -		// iterate over the string to find the closing bracket -		for (int currentPosition = bracketPosition; currentPosition < string -				.length(); currentPosition++) { -			final char currentCharacter = string.charAt(currentPosition); -			 -			if (currentCharacter == openingBracket) { -				bracketLevel++; -			} else if (currentCharacter == closingBracket) { -				bracketLevel--; -				if (bracketLevel == 0) -					return currentPosition; -			} -		} -		 -		throw new IllegalArgumentException("No matching bracket found."); -	} -	 -	/** -	 * A function that obtains a parseable object from a string. For example, an -	 * integer {@code ExpressionParser} would use {@code Integer::parseInt}. -	 *  -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private final Function<String, ? extends T> objectObtainer; -	 -	/** -	 * A map mapping operator strings to operator functions, for unary operators. -	 *  -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private final Map<String, PriorityUnaryOperator<T>> unaryOperators; -	 -	/** -	 * A map mapping operator strings to operator functions, for binary -	 * operators. -	 *  -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private final Map<String, PriorityBinaryOperator<T>> binaryOperators; -	 -	/** -	 * The operator for space, or null if spaces have no function. -	 *  -	 * @since 2019-03-22 -	 * @since v0.2.0 -	 */ -	private final String spaceOperator; -	 -	/** -	 * Creates the {@code ExpressionParser}. -	 *  -	 * @param objectObtainer  function to get objects from strings -	 * @param unaryOperators  unary operators available to the parser -	 * @param binaryOperators binary operators available to the parser -	 * @param spaceOperator   operator used by spaces -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private ExpressionParser(final Function<String, ? extends T> objectObtainer, -			final Map<String, PriorityUnaryOperator<T>> unaryOperators, -			final Map<String, PriorityBinaryOperator<T>> binaryOperators, -			final String spaceOperator) { -		this.objectObtainer = objectObtainer; -		this.unaryOperators = unaryOperators; -		this.binaryOperators = binaryOperators; -		this.spaceOperator = spaceOperator; -	} -	 -	/** -	 * Converts a given mathematical expression to reverse Polish notation -	 * (operators after operands). -	 * <p> -	 * For example,<br> -	 * {@code 2 * (3 + 4)}<br> -	 * becomes<br> -	 * {@code 2 3 4 + *}. -	 *  -	 * @param expression expression -	 * @return expression in RPN -	 * @since 2019-03-17 -	 * @since v0.2.0 -	 */ -	private String convertExpressionToReversePolish(final String expression) { -		Objects.requireNonNull(expression, "expression must not be null."); -		 -		final List<String> components = new ArrayList<>(); -		 -		// the part of the expression remaining to parse -		String partialExpression = expression; -		 -		// find and deal with brackets -		while (partialExpression.indexOf(OPENING_BRACKET) != -1) { -			final int openingBracketPosition = partialExpression -					.indexOf(OPENING_BRACKET); -			final int closingBracketPosition = findBracketPair(partialExpression, -					openingBracketPosition); -			 -			// check for function -			if (openingBracketPosition > 0 -					&& partialExpression.charAt(openingBracketPosition - 1) != ' ') { -				// function like sin(2) or tempF(32) -				// find the position of the last space -				int spacePosition = openingBracketPosition; -				while (spacePosition >= 0 -						&& partialExpression.charAt(spacePosition) != ' ') { -					spacePosition--; -				} -				// then split the function into pre-function and function, using the -				// space position -				components.addAll(Arrays.asList(partialExpression -						.substring(0, spacePosition + 1).split(" "))); -				components.add(partialExpression.substring(spacePosition + 1, -						closingBracketPosition + 1)); -				partialExpression = partialExpression -						.substring(closingBracketPosition + 1); -			} else { -				// normal brackets like (1 + 2) * (3 / 5) -				components.addAll(Arrays.asList(partialExpression -						.substring(0, openingBracketPosition).split(" "))); -				components.add(this.convertExpressionToReversePolish( -						partialExpression.substring(openingBracketPosition + 1, -								closingBracketPosition))); -				partialExpression = partialExpression -						.substring(closingBracketPosition + 1); -			} -		} -		 -		// add everything else -		components.addAll(Arrays.asList(partialExpression.split(" "))); -		 -		// remove empty entries -		while (components.contains("")) { -			components.remove(""); -		} -		 -		// deal with space multiplication (x y) -		if (this.spaceOperator != null) { -			for (int i = 0; i < components.size() - 1; i++) { -				if (this.getTokenType(components.get(i)) == TokenType.OBJECT && this -						.getTokenType(components.get(i + 1)) == TokenType.OBJECT) { -					components.add(++i, this.spaceOperator); -				} -			} -		} -		 -		// turn the expression into reverse Polish -		while (true) { -			final int highestPriorityOperatorPosition = this -					.findHighestPriorityOperatorPosition(components); -			if (highestPriorityOperatorPosition == -1) { -				break; -			} -			 -			// swap components based on what kind of operator there is -			// 1 + 2 becomes 2 1 + -			// - 1 becomes 1 - -			switch (this -					.getTokenType(components.get(highestPriorityOperatorPosition))) { -			case UNARY_OPERATOR: -				final String unaryOperator = components -						.remove(highestPriorityOperatorPosition); -				final String operand = components -						.remove(highestPriorityOperatorPosition); -				components.add(highestPriorityOperatorPosition, -						operand + " " + unaryOperator); -				break; -			case BINARY_OPERATOR: -				final String binaryOperator = components -						.remove(highestPriorityOperatorPosition); -				final String operand1 = components -						.remove(highestPriorityOperatorPosition - 1); -				final String operand2 = components -						.remove(highestPriorityOperatorPosition - 1); -				components.add(highestPriorityOperatorPosition - 1, -						operand2 + " " + operand1 + " " + binaryOperator); -				break; -			default: -				throw new AssertionError("Expected operator, found non-operator."); -			} -		} -		 -		// join all of the components together, then ensure there is only one -		// space in a row -		String expressionRPN = String.join(" ", components).replaceAll(" +", " "); -		 -		while (expressionRPN.charAt(0) == ' ') { -			expressionRPN = expressionRPN.substring(1); -		} -		while (expressionRPN.charAt(expressionRPN.length() - 1) == ' ') { -			expressionRPN = expressionRPN.substring(0, expressionRPN.length() - 1); -		} -		return expressionRPN; -	} -	 -	/** -	 * Finds the position of the highest-priority operator in a list -	 *  -	 * @param components components to test -	 * @param blacklist  positions of operators that should be ignored -	 * @return position of highest priority, or -1 if the list contains no -	 *         operators -	 * @throws NullPointerException if components is null -	 * @since 2019-03-22 -	 * @since v0.2.0 -	 */ -	private int findHighestPriorityOperatorPosition( -			final List<String> components) { -		Objects.requireNonNull(components, "components must not be null."); -		// find highest priority -		int maxPriority = Integer.MIN_VALUE; -		int maxPriorityPosition = -1; -		 -		// go over components one by one -		// if it is an operator, test its priority to see if it's max -		// if it is, update maxPriority and maxPriorityPosition -		for (int i = 0; i < components.size(); i++) { -			 -			switch (this.getTokenType(components.get(i))) { -			case UNARY_OPERATOR: -				final PriorityUnaryOperator<T> unaryOperator = this.unaryOperators -						.get(components.get(i)); -				final int unaryPriority = unaryOperator.getPriority(); -				 -				if (unaryPriority > maxPriority) { -					maxPriority = unaryPriority; -					maxPriorityPosition = i; -				} -				break; -			case BINARY_OPERATOR: -				final PriorityBinaryOperator<T> binaryOperator = this.binaryOperators -						.get(components.get(i)); -				final int binaryPriority = binaryOperator.getPriority(); -				 -				if (binaryPriority > maxPriority) { -					maxPriority = binaryPriority; -					maxPriorityPosition = i; -				} -				break; -			default: -				break; -			} -		} -		 -		// max priority position found -		return maxPriorityPosition; -	} -	 -	/** -	 * Determines whether an inputted string is an object or an operator -	 *  -	 * @param token string to input -	 * @return type of token it is -	 * @throws NullPointerException if {@code expression} is null -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private TokenType getTokenType(final String token) { -		Objects.requireNonNull(token, "token must not be null."); -		 -		if (this.unaryOperators.containsKey(token)) -			return TokenType.UNARY_OPERATOR; -		else if (this.binaryOperators.containsKey(token)) -			return TokenType.BINARY_OPERATOR; -		else -			return TokenType.OBJECT; -	} -	 -	/** -	 * Parses an expression. -	 *  -	 * @param expression expression to parse -	 * @return result -	 * @throws NullPointerException if {@code expression} is null -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	public T parseExpression(final String expression) { -		return this.parseReversePolishExpression( -				this.convertExpressionToReversePolish(expression)); -	} -	 -	/** -	 * Parses an expression expressed in reverse Polish notation. -	 *  -	 * @param expression expression to parse -	 * @return result -	 * @throws NullPointerException if {@code expression} is null -	 * @since 2019-03-14 -	 * @since v0.2.0 -	 */ -	private T parseReversePolishExpression(final String expression) { -		Objects.requireNonNull(expression, "expression must not be null."); -		 -		final Deque<T> stack = new ArrayDeque<>(); -		 -		// iterate over every item in the expression, then -		for (final String item : expression.split(" ")) { -			// choose a path based on what kind of thing was just read -			switch (this.getTokenType(item)) { -			 -			case BINARY_OPERATOR: -				if (stack.size() < 2) -					throw new IllegalStateException(String.format( -							"Attempted to call binary operator %s with only %d arguments.", -							item, stack.size())); -				 -				// get two arguments and operator, then apply! -				final T o1 = stack.pop(); -				final T o2 = stack.pop(); -				final BinaryOperator<T> binaryOperator = this.binaryOperators -						.get(item); -				 -				stack.push(binaryOperator.apply(o1, o2)); -				break; -			 -			case OBJECT: -				// just add it to the stack -				stack.push(this.objectObtainer.apply(item)); -				break; -			 -			case UNARY_OPERATOR: -				if (stack.size() < 1) -					throw new IllegalStateException(String.format( -							"Attempted to call unary operator %s with only %d arguments.", -							item, stack.size())); -				 -				// get one argument and operator, then apply! -				final T o = stack.pop(); -				final UnaryOperator<T> unaryOperator = this.unaryOperators -						.get(item); -				 -				stack.push(unaryOperator.apply(o)); -				break; -			default: -				throw new AssertionError( -						String.format("Internal error: Invalid token type %s.", -								this.getTokenType(item))); -			 -			} -		} -		 -		// return answer, or throw an exception if I can't -		if (stack.size() > 1) -			throw new IllegalStateException( -					"Computation ended up with more than one answer."); -		else if (stack.size() == 0) -			throw new IllegalStateException( -					"Computation ended up without an answer."); -		return stack.pop(); -	} -}  | 
