package parser;
import java.util.HashMap;
import java.util.Map;
import operators.BinaryOperatorCreator;
import operators.Factorial;
import operators.Minus;
import operators.Operators;
import operators.UnaryOperator;
import operators.UnaryOperatorCreator;
import valueTypes.ErrorValue;
import valueTypes.FunctionValue;
import variables.Constants;
import variables.Variable;
/**
* The parser class which can parse strings into evaluation trees.
*
* @author Curran Kelleher
*
*/
public class RecursiveDescentParser {
// all present binary operators
// the type is <String symbol,BinaryOperatorCreator operator>
Map<String,BinaryOperatorCreator> binaryOperators = new HashMap<String,BinaryOperatorCreator>();
// all present unary operators
// the type is <String name,UnaryOperatorCreator operator>
Map<String,UnaryOperatorCreator> unaryOperators = new HashMap<String,UnaryOperatorCreator>();
// the current token being processed
Object token;
/**
* The last parsed unary operator.necessary for linking statements with
* their associated functions.
*
* For example, when parsing "if(a<0){a = 0}", the "if" statement of "if(a<0)"
* would go in this variable, then the function {a = 0} will be linked to
* it.
*/
UnaryOperator lastParsedUnaryOperator;
/**
* the scanner used to scan for tokens in the expression string
*/
TokenScanner tokenScanner;
/**
* Constructs a RecursiveDescentParser. It automatically includes all
* operators defined in Operators.getBinaryOperators() and
* Operators.getUnaryOperators().
*
* @see Operators
*/
public RecursiveDescentParser() {
// initialize all constants (such as pi, e, etc.)
Constants.initializeConstants();
// populate ourselves with all binary operators (such as +, -, etc.)
Operators.getBinaryOperators(this);
// populate ourselves with all unary operators (such as sin, cos, etc.)
Operators.getUnaryOperators(this);
}
/**
* Parses the specified expression string.
*
* @param expression
* the expression string
* @return the resulting evaluation tree.
*/
public ExpressionNode parse(String expression) {
// reset the scanner
tokenScanner = new TokenScanner(binaryOperators, unaryOperators,
expression);
// reset the lastParsedUnaryOperator
lastParsedUnaryOperator = null;
// scan the first token
token = tokenScanner.scan();
return expression();
}
/**
* Recursively descends through all binary operator precedence levels, then
* calls factor()
*
* @param precedence
* the current precedence
* @return the resulting evaluation tree.
*/
private ExpressionNode expression() {
return expression(-6);
}
/**
* Recursively descends through all binary operator precedence levels, then
* calls factor()
*
* @param precedence
* the current precedence
* @return the resulting evaluation tree.
*/
private ExpressionNode expression(int precedence) {
precedence++;
ExpressionNode root = precedence == 4 ? factor()
: expression(precedence);
while (true) {
if (token instanceof BinaryOperatorCreator ? ((BinaryOperatorCreator) token).precedence == precedence
: false) {
BinaryOperatorCreator binaryOperatorCreator = (BinaryOperatorCreator) token;
token = tokenScanner.scan();
root = binaryOperatorCreator.create(root,
precedence == 4 ? factor() : expression(precedence));
} else if (token instanceof ErrorValue)
return (ErrorValue) token;
// else if(token represents Factorial)
else if (token instanceof Character ? ((Character) token)
.charValue() == '!' : false) {
token = tokenScanner.scan();
return new Factorial(root);
} else if (token instanceof Character ? ((Character) token)
.charValue() == '{' : false) {
UnaryOperator previouslyParsedUnaryOperator = lastParsedUnaryOperator;
FunctionValue associatedFunction = new FunctionValue(
parseGrouping('}'));
if (previouslyParsedUnaryOperator == null)
return new ErrorValue("no operator present to associate the function "
+ associatedFunction + " with");
else {
// if success
if (previouslyParsedUnaryOperator
.linkAssociatedFunction(associatedFunction))
return root;
else // if failure
return new ErrorValue("the function "+associatedFunction+" cannot be associated with an operator which does not accept associated functions.");
}
} else
return root;
}
}
/**
* Deals with atoms, groupings, and unary operators
*
* @return the resulting evaluation tree
*/
private ExpressionNode factor() {
ExpressionNode root = null;
if (token instanceof Value) {
root = (Value) token;
token = tokenScanner.scan();
return root;
}
if (token instanceof UnaryOperatorCreator) {
UnaryOperatorCreator unopCreator = (UnaryOperatorCreator) token;
token = tokenScanner.scan();
ExpressionNode child = factor();
return lastParsedUnaryOperator = unopCreator.create(child);
}
if (token instanceof Minus) {
token = tokenScanner.scan();
return Minus.createUnaryMinus(factor());
}
if (token instanceof Character) {
char c = ((Character) token).charValue();
switch (c) {
case '(':
return parseGrouping(')');
case '[':
return ExpressionList.extractExpressionList(parseGrouping(']'));
case '{':
return new FunctionValue(parseGrouping('}'));
default:
return new ErrorValue("invalid character: " + c);
}
}
if (token instanceof Variable) {
root = (Variable) token;
token = tokenScanner.scan();
return root;
}
return new ErrorValue("Syntax error: '" + token + "'");
}
/**
* Parses expressions within groupers. (such as (a+b))
*
* @param endGroupingChar
* the character at the end of the group. For example ')' for
* (a+b).
* @return the resulting evaluation tree.
*/
private ExpressionNode parseGrouping(char endGroupingChar) {
token = tokenScanner.scan();
ExpressionNode root = expression();
if (token instanceof Character ? ((Character) token).charValue() != endGroupingChar
: true)
return new ErrorValue("missing '" + endGroupingChar + "' at "
+ token);
token = tokenScanner.scan();
return root;
}
/**
* Maps a symbol to a BinaryOperatorCreator. When a string is parsed into an
* evaluation tree, this mapping will be used to create all BinaryOperators.
*
* @param symbol
* the symbol of the binary operator (for example '+')
* @param creator
* the BinaryOperatorCreator which, when called upon, will create
* a BinaryOperators which will perform the operation
* corresponding to the symbol of the operator.
*/
public void addBinaryOperator(String symbol, BinaryOperatorCreator creator) {
binaryOperators.put(symbol, creator);
}
/**
* Maps an operator name to a UnaryOperatorCreator. When a string is parsed
* into an evaluation tree, this mapping will be used to create all
* UnaryOperators.
*
* @param name
* the name of the operator (for example "sin")
* @param creator
* the UnaryOperatorCreator which, when called upon, will create
* a UnaryOperators which will perform the operation
* corresponding to the name of the operator.
*/
public void addUnaryOperator(String name, UnaryOperatorCreator creator) {
unaryOperators.put(name, creator);
}
}