/* * This software and all files contained in it are distrubted under the MIT license. * * Copyright (c) 2013 Cogito Learning Ltd * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @mainpage * CogPar is lightweight but versatile parser for mathematical expressions. * * It can be used to analyse expressions and store them in an internal data structure for later * evaluation. Repeated evaluation of the same expression using CogPar is fast. * * CogPar comes with a highly configurable tokenizer which can be adapted for your own needs. * * Arbitrary named variables are supported and values can be assigned in a single line of code. * * The parser, it's grammar an the tokenizer are well documented. You can read more about the internal * workings of CogPar <a href="http://cogitolearning.co.uk/?p=523" alt="CogPar tutorial">in these posts</a>. * * CogPar is distributed under the MIT license, so feel free to use it in your own projects. * * To download CogPar, <a href="" alt="Download CogPar">follow this link.</a> */ package uk.co.cogitolearning.cogpar; import java.util.LinkedList; /** * A parser for mathematical expressions. The parser class defines a method * parse() which takes a string and returns an ExpressionNode that holds a * representation of the expression. * * Parsing is implemented in the form of a recursive descent parser. * */ public class Parser { /** the tokens to parse */ LinkedList<Token> tokens; /** the next token */ Token lookahead; /** * Parse a mathematical expression in a string and return an ExpressionNode. * * This is a convenience method that first converts the string into a linked * list of tokens using the expression tokenizer provided by the Tokenizer * class. * * @param expression * the string holding the input * @return the internal representation of the expression in form of an * expression tree made out of ExpressionNode objects */ public ExpressionNode parse(String expression) { Tokenizer tokenizer = Tokenizer.getExpressionTokenizer(); tokenizer.tokenize(expression); LinkedList<Token> tokens = tokenizer.getTokens(); return this.parse(tokens); } /** * Parse a mathematical expression in contained in a list of tokens and return * an ExpressionNode. * * @param tokens * a list of tokens holding the tokenized input * @return the internal representation of the expression in form of an * expression tree made out of ExpressionNode objects */ public ExpressionNode parse(LinkedList<Token> tokens) { // implementing a recursive descent parser this.tokens = (LinkedList<Token>) tokens.clone(); lookahead = this.tokens.getFirst(); // top level non-terminal is expression ExpressionNode expr = expression(); if (lookahead.token != Token.EPSILON) throw new ParserException("Unexpected symbol %s found", lookahead); return expr; } /** handles the non-terminal expression */ private ExpressionNode expression() { // only one rule // expression -> signed_term sum_op ExpressionNode expr = signedTerm(); expr = sumOp(expr); return expr; } /** handles the non-terminal sum_op */ private ExpressionNode sumOp(ExpressionNode expr) { // sum_op -> PLUSMINUS term sum_op if (lookahead.token == Token.PLUSMINUS) { AdditionExpressionNode sum; // This means we are actually dealing with a sum // If expr is not already a sum, we have to create one if (expr.getType() == ExpressionNode.ADDITION_NODE) sum = (AdditionExpressionNode) expr; else sum = new AdditionExpressionNode(expr, true); // reduce the input and recursively call sum_op boolean positive = lookahead.sequence.equals("+"); nextToken(); ExpressionNode t = term(); sum.add(t, positive); return sumOp(sum); } // sum_op -> EPSILON return expr; } /** handles the non-terminal signed_term */ private ExpressionNode signedTerm() { // signed_term -> PLUSMINUS term if (lookahead.token == Token.PLUSMINUS) { boolean positive = lookahead.sequence.equals("+"); nextToken(); ExpressionNode t = term(); if (positive) return t; else return new AdditionExpressionNode(t, false); } // signed_term -> term return term(); } /** handles the non-terminal term */ private ExpressionNode term() { // term -> factor term_op ExpressionNode f = factor(); return termOp(f); } /** handles the non-terminal term_op */ private ExpressionNode termOp(ExpressionNode expression) { // term_op -> MULTDIV factor term_op if (lookahead.token == Token.MULTDIV) { MultiplicationExpressionNode prod; // This means we are actually dealing with a product // If expr is not already a PRODUCT, we have to create one if (expression.getType() == ExpressionNode.MULTIPLICATION_NODE) prod = (MultiplicationExpressionNode) expression; else prod = new MultiplicationExpressionNode(expression, true); // reduce the input and recursively call sum_op boolean positive = lookahead.sequence.equals("*"); nextToken(); ExpressionNode f = signedFactor(); prod.add(f, positive); return termOp(prod); } // term_op -> EPSILON return expression; } /** handles the non-terminal signed_factor */ private ExpressionNode signedFactor() { // signed_factor -> PLUSMINUS factor if (lookahead.token == Token.PLUSMINUS) { boolean positive = lookahead.sequence.equals("+"); nextToken(); ExpressionNode t = factor(); if (positive) return t; else return new AdditionExpressionNode(t, false); } // signed_factor -> factor return factor(); } /** handles the non-terminal factor */ private ExpressionNode factor() { // factor -> argument factor_op ExpressionNode a = argument(); return factorOp(a); } /** handles the non-terminal factor_op */ private ExpressionNode factorOp(ExpressionNode expr) { // factor_op -> RAISED expression if (lookahead.token == Token.RAISED) { nextToken(); ExpressionNode exponent = signedFactor(); return new ExponentiationExpressionNode(expr, exponent); } // factor_op -> EPSILON return expr; } /** handles the non-terminal argument */ private ExpressionNode argument() { // argument -> FUNCTION argument if (lookahead.token == Token.FUNCTION) { int function = FunctionExpressionNode.stringToFunction(lookahead.sequence); nextToken(); ExpressionNode expr = argument(); return new FunctionExpressionNode(function, expr); } // argument -> OPEN_BRACKET sum CLOSE_BRACKET else if (lookahead.token == Token.OPEN_BRACKET) { nextToken(); ExpressionNode expr = expression(); if (lookahead.token != Token.CLOSE_BRACKET) throw new ParserException("Closing brackets expected", lookahead); nextToken(); return expr; } // argument -> value return value(); } /** handles the non-terminal value */ private ExpressionNode value() { // argument -> NUMBER if (lookahead.token == Token.NUMBER) { ExpressionNode expr = new ConstantExpressionNode(lookahead.sequence); nextToken(); return expr; } // argument -> VARIABLE if (lookahead.token == Token.VARIABLE) { ExpressionNode expr = new VariableExpressionNode(lookahead.sequence); nextToken(); return expr; } if (lookahead.token == Token.EPSILON) throw new ParserException("Unexpected end of input"); else throw new ParserException("Unexpected symbol %s found", lookahead); } /** * Remove the first token from the list and store the next token in lookahead */ private void nextToken() { tokens.pop(); // at the end of input we return an epsilon token if (tokens.isEmpty()) lookahead = new Token(Token.EPSILON, "", -1); else lookahead = tokens.getFirst(); } }