package bjc.utils.parserutils; import java.util.Deque; import java.util.LinkedList; import java.util.function.Consumer; import java.util.function.Function; import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.FunctionalMap; import bjc.utils.funcdata.IList; import bjc.utils.funcdata.IMap; import bjc.utils.funcutils.StringUtils; /** * Utility to run the shunting yard algorithm on a bunch of tokens * * @author ben * * @param * The type of tokens being shunted */ public class ShuntingYard { /** * A enum representing the fundamental operator types * * @author ben * */ public static enum Operator implements IPrecedent { /** * Represents addition */ ADD(1), /** * Represents subtraction */ SUBTRACT(2), /** * Represents multiplication */ MULTIPLY(3), /** * Represents division */ DIVIDE(4); private final int precedence; private Operator(int prec) { precedence = prec; } /* * (non-Javadoc) * * @see bjc.utils.parserutils.IPrecedent#getPrecedence() */ @Override public int getPrecedence() { return precedence; } } private final class TokenShunter implements Consumer { private IList output; private Deque stack; private Function transform; public TokenShunter(IList outpt, Deque stack, Function transform) { this.output = outpt; this.stack = stack; this.transform = transform; } @Override public void accept(String token) { // Handle operators if (operators.containsKey(token)) { // Pop operators while there isn't a higher precedence one while (!stack.isEmpty() && isHigherPrec(token, stack.peek())) { output.add(transform.apply(stack.pop())); } // Put this operator onto the stack stack.push(token); } else if (StringUtils.containsOnly(token, "\\(")) { // Handle groups of parenthesis for multiple nesting levels stack.push(token); } else if (StringUtils.containsOnly(token, "\\)")) { // Handle groups of parenthesis for multiple nesting levels String swappedToken = token.replace(')', '('); // Remove tokens up to a matching parenthesis while (!stack.peek().equals(swappedToken)) { output.add(transform.apply(stack.pop())); } // Remove the parenthesis stack.pop(); } else { // Just add the transformed token output.add(transform.apply(token)); } } } /** * Holds all the shuntable operations */ private IMap operators; /** * Create a new shunting yard with a default set of operators * * @param configureBasics * Whether or not basic math operators should be provided */ public ShuntingYard(boolean configureBasics) { operators = new FunctionalMap<>(); // Add basic operators if we're configured to do so if (configureBasics) { operators.put("+", Operator.ADD); operators.put("-", Operator.SUBTRACT); operators.put("*", Operator.MULTIPLY); operators.put("/", Operator.DIVIDE); } } /** * Add an operator to the list of shuntable operators * * @param operatorToken * The token representing the operator * @param precedence * The precedence of the operator to add */ public void addOp(String operatorToken, int precedence) { // Create the precedence marker IPrecedent prec = IPrecedent.newSimplePrecedent(precedence); this.addOp(operatorToken, prec); } /** * Add an operator to the list of shuntable operators * * @param operatorToken * The token representing the operator * @param precedence * The precedence of the operator */ public void addOp(String operatorToken, IPrecedent precedence) { // Complain about trying to add an incorrect operator if (operatorToken == null) { throw new NullPointerException("Operator must not be null"); } else if(precedence == null) { throw new NullPointerException("Precedence must not be null"); } // Add the operator to the ones we handle operators.put(operatorToken, precedence); } private boolean isHigherPrec(String leftOperator, String rightOperator) { // Check if the right operator exists boolean operatorExists = operators.containsKey(rightOperator); // If it doesn't, the left is higher precedence. if (!operatorExists) { return false; } // Get the precedence of operators int rightPrecedence = operators.get(rightOperator).getPrecedence(); int leftPrecedence = operators.get(leftOperator).getPrecedence(); // Evaluate what we were asked return rightPrecedence >= leftPrecedence; } /** * Transform a string of tokens from infix notation to postfix * * @param input * The string to transform * @param tokenTransformer * The function to use to transform strings to tokens * @return A list of tokens in postfix notation */ public IList postfix(IList input, Function tokenTransformer) { // Check our input if (input == null) { throw new NullPointerException("Input must not be null"); } else if (tokenTransformer == null) { throw new NullPointerException("Transformer must not be null"); } // Here's what we're handing back IList output = new FunctionalList<>(); // The stack to put operators on Deque stack = new LinkedList<>(); // Shunt the tokens input.forEach(new TokenShunter(output, stack, tokenTransformer)); // Transform any resulting tokens stack.forEach((token) -> { output.add(tokenTransformer.apply(token)); }); return output; } /** * Remove an operator from the list of shuntable operators * * @param token * The token representing the operator. If null, remove all * operators */ public void removeOp(String token) { // Check if we want to remove all operators if (token == null) { operators = new FunctionalMap<>(); } else { operators.remove(token); } } }