/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm.functions;
import java.text.MessageFormat;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import vmm.core.I18n;
/**
* A Parser can parse a string that holds the definition of a
* real-valued or complex-valued function or expression.
*
An expression can use the operators +, -, *, /, and ^, where ^
* represents expontiation. Multiplication can also be represented
* by juxtaposition. The ternary conditional operator "?:"
* can be used, as in "x < 0 ? -x : x". In the
* condition, the relational operators =, !=, <, >, <=, and >=
* and the logical operators and, or, and not can be used. Not equal can
* be expressed as <> as well as by !=, and the logical operators
* can be written as & |, and ~. Parentheses (), braces {}, and brackets []
* can be used for grouping.
*
Names in expressions are not case-sensitive. The constants pi, e, i can be
* used in expressions (where i is the complex number, square root of minus one.
* Standard functions that are defined for both real and complex arguments are:
* abs, sqrt, cubert, exp, log, log2, log10, sin, cos, tan, sec, csc, cot,
* sinh, cosh, tanh, sech, scsh, coth, arcsin, arccos, arctan, arcsinh,
* arccosh, arctanh. Log can also be written as ln, and the inverse trig
* functions can also be written as asin, acos, atan, asinh, acosh, and atahn.
* Functions defined only for real arguments are: trunc, round, ceiling, floor,
* and signum. The signum function can also be written as sign or sgn.
* The real- and imaginary-part functions, re and im, are defined for a complex
* argument and produce a real result; they will also accept real arguemnts.
* The function complex(x,y) takes two real arguments and produces the complex
* value x+i*y; complex(x,y) is just another way to write this complex value,
* without using the constant i directly.
*
The definition of an expression or function
* can include references to parameters and functions that have
* been added to the parser (see {@link #add(Variable)},
* {@link #add(ComplexVariable)}, {@link #add(Function)},
* and {@link #add(ComplexFunction)}.
*/
public class Parser {
/**
* Create a parser with no parent. (See {@link #Parser(Parser)}.)
*/
public Parser() {
this(null);
}
/**
* Create a new parser. (This is mainly for internal use in this class.)
* @param parent if the parent is non-null, then this parser inherits
* any symbols that have been added to the parent parser. Symbols that
* are added to this parser do not affect the parent, but will hide symbols
* in the parent that have the same name making them unavailable for use
* by this parser.
*/
public Parser(Parser parent) {
if (parent != null)
symbolTable = new SymbolTable(parent.symbolTable);
else {
symbolTable = new SymbolTable();
add(PI);
add(E);
add(I);
StandardFunction[] functions = StandardFunction.getFunctions();
for (StandardFunction f : functions)
symbolTable.put(f.getName().toLowerCase(),f);
}
}
/**
* Parse an expression. This is just a synonym for {@link #parseExpression(String)}.
* @throws ParseError if a syntax error is found in the definition of the expression.
*/
public Expression parse(String str) {
return parseExpression(str);
}
/**
* Parse a real-valued expression.
* @throws ParseError if a syntax error is found in the definition of the expression.
*/
public Expression parseExpression(String str) {
Context context = new Context(str,symbolTable,false);
context.bldr.start(0,Type.REAL);
Type type = doParse(context);
if (type == Type.COMPLEX)
error(context,"vmm.parser.ExpectedRealFoundComplex");
if (type == Type.BOOLEAN)
error(context,"vmm.parser.ExpectedRealFoundBoolean");
return new Expression(context.bldr.finish(Type.REAL));
}
/**
* Define a real-valued function of one real argument by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName The name of the argument to the function. This must be non-null.
* It should be a legal identifier. The argument name can be used in the definition
* of the function.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public Function1 parseFunction1(String name, String definition, String argumentName) {
return (Function1)parseFunction(name,definition,argumentName);
}
/**
* Define a real-valued function of two real arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName1 The name of the first argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName2 The name of the second argument to the function. This must be non-null.
* It should be a legal identifier.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public Function2 parseFunction2(String name, String definition, String argumentName1, String argumentName2) {
return (Function2)parseFunction(name,definition,argumentName1,argumentName2);
}
/**
* Define a real-valued function of three real arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName1 The name of the first argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName2 The name of the second argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName3 The name of the third argument to the function. This must be non-null.
* It should be a legal identifier.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public Function3 parseFunction3(String name, String definition, String argumentName1, String argumentName2, String argumentName3) {
return (Function3)parseFunction(name,definition,argumentName1, argumentName2, argumentName3);
}
/**
* Define a real-valued function of any number of real arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName The names of the arguments to the function. There can be any number
* of argumentNames. They must be non-null and should be legal identifiers.
* @return the function. If the number of arguments is one, two, or three, then the
* return value is actually of type {@link Function1}, {@link Function2}, or
* {@link Function3}, respectively.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public Function parseFunction(String name, String definition, String... argumentName) {
Context context;
int argCount;
if (argumentName != null && argumentName.length > 0) {
argCount = argumentName.length;
SymbolTable tbl = new SymbolTable(symbolTable);
for (int i = 0; i < argumentName.length; i++)
tbl.put(argumentName[i].toLowerCase(), new Argument(i));
context = new Context(definition,tbl,false);
}
else {
argCount = 0;
context = new Context(definition,symbolTable,false);
}
context.bldr.start(argCount,Type.REAL);
Type type = doParse(context);
if (type == Type.COMPLEX)
error(context,"vmm.parser.ExpectedRealFoundComplex");
if (type == Type.BOOLEAN)
error(context,"vmm.parser.ExpectedRealFoundBoolean");
ProgFunction func = context.bldr.finish(Type.REAL);
if (argCount == 1)
return new Function1(name,func);
else if (argCount == 2)
return new Function2(name,func);
else if (argCount == 3)
return new Function3(name,func);
else
return new Function(name,func);
}
/**
* Parse a complex-valued expression.
* @throws ParseError if a syntax error is found in the definition of the expression.
*/
public ComplexExpression parseComplexExpression(String str) {
Context context = new Context(str,symbolTable,true);
context.bldr.start(0,Type.COMPLEX);
Type type = doParse(context);
if (type == Type.BOOLEAN)
error(context,"vmm.parser.ExpectedCompplexFoundBoolean");
if (type == Type.REAL)
context.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
return new ComplexExpression(context.bldr.finish(Type.COMPLEX));
}
/**
* Define a complex-valued function of one real argumentsby parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName The name of the argument to the function. This must be non-null.
* It should be a legal identifier.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public ComplexFunction1 parseComplexFunction1(String name, String definition, String argumentName) {
return (ComplexFunction1)parseComplexFunction(name,definition,argumentName);
}
/**
* Define a conplex-valued function of two complex arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName1 The name of the first argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName2 The name of the second argument to the function. This must be non-null.
* It should be a legal identifier.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public ComplexFunction2 parseComplexFunction2(String name, String definition, String argumentName1, String argumentName2) {
return (ComplexFunction2)parseComplexFunction(name,definition,argumentName1,argumentName2);
}
/**
* Define a conplex-valued function of three complex arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName1 The name of the first argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName2 The name of the second argument to the function. This must be non-null.
* It should be a legal identifier.
* @param argumentName3 The name of the third argument to the function. This must be non-null.
* It should be a legal identifier.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public ComplexFunction3 parseComplexFunction3(String name, String definition, String argumentName1, String argumentName2, String argumentName3) {
return (ComplexFunction3)parseComplexFunction(name,definition,argumentName1, argumentName2, argumentName3);
}
/**
* Define a complex-valued function of any number of complex arguments by parsing its definition.
* @param name the name of the function. This can be null and really only has to be
* non-null if the function will be added to a Parser for use in other expressions.
* @param definition The string that will be parsed to define the function.
* @param argumentName The names of the arguments to the function. There can be any number
* of argumentNames. They must be non-null and should be legal identifiers.
* @return the function. If the number of arguments is one, two, or three, then the
* return value is actually of type {@link ComplexFunction1}, {@link ComplexFunction2}, or
* {@link ComplexFunction3}, respectively.
* @throws ParseError if a syntax error is found in the definition of the function.
*/
public ComplexFunction parseComplexFunction(String name, String definition, String... argumentName) {
Context context;
int argCount;
if (argumentName != null && argumentName.length > 0) {
argCount = argumentName.length;
SymbolTable tbl = new SymbolTable(symbolTable);
for (int i = 0; i < argumentName.length; i++)
tbl.put(argumentName[i].toLowerCase(), new ComplexArgument(i));
context = new Context(definition,tbl,false);
}
else {
argCount = 0;
context = new Context(definition,symbolTable,true);
}
context.bldr.start(argCount,Type.COMPLEX);
Type type = doParse(context);
if (type == Type.BOOLEAN)
error(context,"vmm.parser.ExpectedCompplexFoundBoolean");
if (type == Type.REAL)
context.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
ProgFunction func = context.bldr.finish(Type.COMPLEX);
if (argCount == 1)
return new ComplexFunction1(name,func);
else if (argCount == 2)
return new ComplexFunction2(name,func);
else if (argCount == 3)
return new ComplexFunction3(name,func);
else
return new ComplexFunction(name,func);
}
/**
* Add a variable that can then be used as a parameter in expressions
* parsed by this parser.
* @param v the variable to be added to the parser. This must be non-null
* and must have a non-null name. Furthermore, the name should be
* a legal identifier that can be used in expressions; that is, it should
* start with a letter and contain only letters, digits, the underscore
* character, with one or more apostrophes allowed at the end of the name.
*/
public void add(Variable v) {
symbolTable.put(v.getName().toLowerCase(), v);
}
/**
* Add a complex variable that can then be used as a parameter in expressions
* parsed by this parser.
* @param v the variable to be added to the parser. This must be non-null
* and must have a non-null name. Furthermore, the name should be
* a legal identifier that can be used in expressions; that is, it should
* start with a letter and contain only letters, digits, the underscore
* character, with one or more apostrophes allowed at the end of the name.
*/
public void add(ComplexVariable v) {
symbolTable.put(v.getName().toLowerCase(), v);
}
/**
* Add a real-valued function to the parser so that it can be used in
* expressions parsed by this parser.
* @param f the function to be added to the parser. (Note that the only way to get
* such a function is from a Parser.) This must be non-null and must
* have a non-null name. The name should be a legal identifier; that is, it should
* start with a letter and contain only letters, digits, the underscore
* character, with one or more apostrophes allowed at the end of the name.
*/
public void add(Function f) {
symbolTable.put(f.getName().toLowerCase(), f);
}
/**
* Add a complex-valued function to the parser so that it can be used in
* expressions parsed by this parser.
* @param f the function to be added to the parser. (Note that the only way to get
* such a function is from a Parser.) This must be non-null and must
* have a non-null name. The name should be a legal identifier; that is, it should
* start with a letter and contain only letters, digits, the underscore
* character, with one or more apostrophes allowed at the end of the name.
*/
public void add(ComplexFunction f) {
symbolTable.put(f.getName().toLowerCase(), f);
}
/**
* Gets the object from the parser's symbol table that has the specified name,
* or returns null if there is no such object. The parent of the parser, if
* there is one, is also searched. The name is not case sensitive.
* (This is mostly for internal use, and not likely to be used by ordinary
* programmers.)
*/
public Object get(String name) {
return symbolTable.get(name.toLowerCase());
}
/**
* Removes the object from the parser's symbol table that has the specified name,
* if there is such an object. (The object is only removed if it is found in
* this parser, not in the parent.) The name is not case sensitive.
* (This is mostly for internal use, and not likely to be used by ordinary
* programmers.)
*/
public void remove(String objectName) {
symbolTable.remove(objectName.toLowerCase());
}
//-------------------------------------------------------------------------------------------
private static Variable PI = new Variable("pi",Math.PI);
private static Variable E = new Variable("e",Math.E);
private static ComplexVariable I = new ComplexVariable("i",0,1);
private static EnumSet relationalOps = EnumSet.of(Token.EQUAL, Token.NOT_EQUAL,
Token.GREATER, Token.GREATER_EQUAL, Token.LESS, Token.LESS_EQUAL);
private static EnumSet canStartFactor = EnumSet.of(Token.NUMBER, Token.VARIABLE, Token.COMPLEX_VARIABLE,
Token.ARGUMENT, Token.COMPLEX_ARGUMENT, Token.FUNCTION, Token.COMPLEX_FUNCTION, Token.STANDARD_FUNCTION,
Token.FUNCTION_COMPLEX_TO_REAL, Token.LEFT_BRACE, Token.LEFT_PAREN, Token.LEFT_BRACKET,
Token.TIMES, Token.DIVIDE);
private static EnumSet needRealToComplex = EnumSet.of(StackOp.SQRT, StackOp.CUBERT,
StackOp.LOG, StackOp.LOG2, StackOp.LOG10, StackOp.ARCSIN, StackOp.ARCCOS, StackOp.ARCTAN,
StackOp.ARCSINH, StackOp.ARCCOSH, StackOp.ARCTANH);
private SymbolTable symbolTable;
private void error(Context context, String errorMessage, Object... arg) {
String err = I18n.tr(errorMessage);
if (arg != null && arg.length > 0)
err = MessageFormat.format(err,arg);
context.bldr.reset();
throw new ParseError(err, context.pos, context.str);
}
private Type doParse(Context cntx) {
if (cntx.peek() == Token.EOS)
error(cntx,"vmm.parser.EmpytDefinition");
Type type = parseBExpr(cntx);
if (cntx.peek() != Token.EOS)
error(cntx,"vmm.parser.ExtraStuff");
return type;
}
private Type parseBExpr(Context cntx) {
Type type = parseBTerm(cntx);
while (cntx.peek() == Token.OR) {
if (type != Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequriesBoolean", "OR");
cntx.next();
Type nextType = parseBTerm(cntx);
if (nextType != Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequriesBoolean", "OR");
cntx.bldr.addStackOp(StackOp.OR);
}
if (cntx.peek() == Token.QUESTION) {
if (type != Type.BOOLEAN)
error(cntx,"vmm.parser.ConditionalRequiresBoolean");
cntx.next();
int firstExpr = cntx.bldr.startSubProg();
type = parseNumericalExpr(cntx);
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.ConditionalExpressionsMustBeNumerical");
cntx.bldr.finishSubProg();
int secondExpr = cntx.bldr.startSubProg();
Type nextType;
if (cntx.peek() != Token.COLON) {
nextType = type;
if (type == Type.REAL)
cntx.bldr.addRealConstant(Double.NaN);
else
cntx.bldr.addComplexConstant(Double.NaN, Double.NaN);
}
else {
cntx.next();
nextType = parseBExpr(cntx);
if (nextType == Type.BOOLEAN)
error(cntx,"vmm.parser.ConditionalExpressionsMustBeNumerical");
if (type == Type.COMPLEX && nextType == Type.REAL)
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
}
cntx.bldr.finishSubProg();
if (type == Type.REAL && nextType == Type.COMPLEX) {
cntx.bldr.addConditional(firstExpr, secondExpr, true);
type = Type.COMPLEX;
}
else
cntx.bldr.addConditional(firstExpr,secondExpr,false);
}
return type;
}
private Type parseBTerm(Context cntx) {
Type type = parseBFactor(cntx);
while (cntx.peek() == Token.AND) {
if (type != Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequriesBoolean", "AND");
cntx.next();
Type nextType = parseBFactor(cntx);
if (nextType != Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequriesBoolean", "AND");
cntx.bldr.addStackOp(StackOp.AND);
}
return type;
}
private Type parseBFactor(Context cntx) {
int notCount = 0;
while (cntx.peek() == Token.NOT) {
notCount++;
cntx.next();
}
Type type = parseRelation(cntx);
if (notCount > 0 && type != Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequriesBoolean", "NOT");
if (notCount % 2 == 1)
cntx.bldr.addStackOp(StackOp.NOT);
return type;
}
private Type parseRelation(Context cntx) {
Type type = parseNumericalExpr(cntx);
if (relationalOps.contains(cntx.peek())) {
Token op = cntx.next();
String opName = cntx.tokstr;
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
if (type == Type.COMPLEX && op != Token.EQUAL && op != Token.NOT_EQUAL)
error(cntx,"vmm.parser.RelationNotDefinedForComplex",opName);
Type nextType = parseNumericalExpr(cntx);
if (nextType == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
if (nextType == Type.COMPLEX && op != Token.EQUAL && op != Token.NOT_EQUAL)
error(cntx,"vmm.parser.RelationNotDefinedForComplex",opName);
if (type == Type.REAL && nextType == Type.COMPLEX) {
cntx.bldr.addStackOp(StackOp.FIRST_OP_TO_COMPLEX);
type = Type.COMPLEX;
}
switch (op) {
case EQUAL:
cntx.bldr.addStackOp( type == Type.COMPLEX ? StackOp.C_EQ : StackOp.EQ );
break;
case NOT_EQUAL:
cntx.bldr.addStackOp( type == Type.COMPLEX ? StackOp.C_NE : StackOp.NE );
break;
case GREATER:
cntx.bldr.addStackOp(StackOp.GT);
break;
case LESS:
cntx.bldr.addStackOp(StackOp.LT);
break;
case GREATER_EQUAL:
cntx.bldr.addStackOp(StackOp.GE);
break;
case LESS_EQUAL:
cntx.bldr.addStackOp(StackOp.LE);
break;
}
if (relationalOps.contains(cntx.peek()))
error(cntx,"vmm.parser.CantStringRelations");
return Type.BOOLEAN;
}
return type;
}
private Type parseNumericalExpr(Context cntx) {
Token leadingSign = null;
if (cntx.peek() == Token.PLUS || cntx.peek() == Token.MINUS)
leadingSign = cntx.next();
Type type = parseNumericalTerm(cntx);
if (leadingSign != null) {
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",leadingSign == Token.PLUS ? "+" : "-");
if (leadingSign == Token.MINUS)
cntx.bldr.addStackOp( type == Type.REAL ? StackOp.UNARY_MINUS : StackOp.C_UNARY_MINUS);
}
while (cntx.peek() == Token.PLUS || cntx.peek() == Token.MINUS) {
Token op = cntx.next();
String opName = cntx.tokstr;
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
Type nextType = parseNumericalTerm(cntx);
if (nextType == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
if (type == Type.REAL && nextType == Type.COMPLEX) {
cntx.bldr.addStackOp(StackOp.FIRST_OP_TO_COMPLEX);
type = Type.COMPLEX;
}
else if (type == Type.COMPLEX && nextType == Type.REAL)
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
if (type == Type.REAL)
cntx.bldr.addStackOp( op == Token.PLUS ? StackOp.PLUS : StackOp.MINUS );
else
cntx.bldr.addStackOp( op == Token.PLUS ? StackOp.C_PLUS : StackOp.C_MINUS);
}
return type;
}
private Type parseNumericalTerm(Context cntx) {
Type type = parseNumericalFactor(cntx);
while (canStartFactor.contains(cntx.peek())) {
Token op;
String opName;
if (cntx.peek() == Token.TIMES || cntx.peek() == Token.DIVIDE) {
op = cntx.next();
opName = cntx.tokstr;
}
else {
op = Token.TIMES;
opName = "*";
}
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
Type nextType = parseNumericalFactor(cntx);
if (nextType == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
if (type == Type.REAL && nextType == Type.COMPLEX) {
cntx.bldr.addStackOp(StackOp.FIRST_OP_TO_COMPLEX);
type = Type.COMPLEX;
}
else if (type == Type.COMPLEX && nextType == Type.REAL)
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
if (type == Type.REAL)
cntx.bldr.addStackOp( op == Token.TIMES ? StackOp.TIMES : StackOp.DIVIDE );
else
cntx.bldr.addStackOp( op == Token.TIMES ? StackOp.C_TIMES : StackOp.C_DIVIDE);
}
return type;
}
private Type parseNumericalFactor(Context cntx) {
Type type = parseNumericalPrimary(cntx);
while (cntx.peek() == Token.POWER) {
cntx.next();
String opName = cntx.tokstr;
if (type == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
Type nextType = parseNumericalPrimary(cntx);
if (nextType == Type.BOOLEAN)
error(cntx,"vmm.parser.OperatorRequiresNumerical",opName);
if (type == Type.REAL) {
if (nextType == Type.COMPLEX) {
cntx.bldr.addStackOp(StackOp.FIRST_OP_TO_COMPLEX);
type = Type.COMPLEX;
}
else if (cntx.complexOnly) {
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
cntx.bldr.addStackOp(StackOp.FIRST_OP_TO_COMPLEX);
type = nextType = Type.COMPLEX;
}
}
if (type == Type.COMPLEX && nextType == Type.REAL)
cntx.bldr.addStackOp(StackOp.C_REAL_POWER);
else if (type == Type.REAL)
cntx.bldr.addStackOp(StackOp.POWER);
else
cntx.bldr.addStackOp(StackOp.C_POWER);
}
return type;
}
private Type parseNumericalPrimary(Context cntx) {
Type type;
Token tok = cntx.next();
Token openingParen;
switch (tok) {
case NUMBER:
cntx.bldr.addRealConstant(cntx.number);
return Type.REAL;
case VARIABLE:
cntx.bldr.addVariableRef((Variable)cntx.symbol);
return Type.REAL;
case ARGUMENT:
cntx.bldr.addArgumentReference(((Argument)cntx.symbol).argnum);
return Type.REAL;
case COMPLEX_VARIABLE:
cntx.bldr.addComplexVariableRef((ComplexVariable)cntx.symbol);
return Type.COMPLEX;
case COMPLEX_ARGUMENT:
cntx.bldr.addArgumentReference(((ComplexArgument)cntx.symbol).argnum);
return Type.COMPLEX;
case FUNCTION:
case COMPLEX_FUNCTION:
case STANDARD_FUNCTION:
case FUNCTION_COMPLEX_TO_REAL:
Token funcTok = tok;
Object symbol = cntx.symbol;
String funcName = cntx.tokstr;
openingParen = cntx.next();
if (openingParen != Token.LEFT_PAREN && openingParen != Token.LEFT_BRACE && openingParen != Token.LEFT_BRACKET)
error(cntx,"vmm.parser.FunctionRequiresParen",funcName);
int argCount = 0;
int expectedArgs = funcTok == Token.FUNCTION ? ((Function)symbol).getArity() :
funcTok == Token.COMPLEX_FUNCTION ? ((ComplexFunction)symbol).getArity() :
funcTok == Token.STANDARD_FUNCTION ? 1 : 2;
Type argType = null;
while (true) {
tok = cntx.peek();
if (tok == Token.RIGHT_PAREN || tok == Token.RIGHT_BRACE || tok == Token.RIGHT_BRACKET)
break;
argCount++;
if (argCount > expectedArgs)
error(cntx,"vmm.parser.TooManyArguments",funcName);
argType = parseBExpr(cntx);
if (funcTok == Token.FUNCTION || funcTok == Token.FUNCTION_COMPLEX_TO_REAL) {
if (argType != Type.REAL)
error(cntx,"vmm.parser.NeedRealArgument",funcName);
}
else if (funcTok == Token.COMPLEX_FUNCTION) {
if (argType == Type.BOOLEAN)
error(cntx,"vmm.parser.NeedComplexArgument",funcName);
if (argType == Type.REAL)
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
}
else if (funcTok == Token.STANDARD_FUNCTION) {
StandardFunction f = (StandardFunction)symbol;
if (argType == Type.COMPLEX) {
if (f.getComplexArgOp() == null)
error(cntx,"vmm.parser.NeedRealArgument",funcName);
}
else if (argType == Type.REAL) {
if (f.getRealArgOp() == null || (cntx.complexOnly && needRealToComplex.contains(f.getRealArgOp()))) {
cntx.bldr.addStackOp(StackOp.REAL_TO_COMPLEX);
argType = Type.COMPLEX;
}
}
else { // argType is boolean
if (f.getRealArgOp() == null || (cntx.complexOnly && needRealToComplex.contains(f.getRealArgOp())))
error(cntx,"vmm.parser.NeedComplexArgument",funcName);
else
error(cntx,"vmm.parser.NeedRealArgument",funcName);
}
}
tok = cntx.peek();
if (tok != Token.COMMA)
break;
cntx.next();
}
tok = cntx.next();
if (argCount < expectedArgs)
error(cntx,"vmm.parser.NotEnoughArguments",funcName);
if (openingParen == Token.LEFT_PAREN && tok != Token.RIGHT_PAREN)
error(cntx,"vmm.parser.MissingCloseOfArgumentList",")");
if (openingParen == Token.LEFT_BRACE && tok != Token.RIGHT_BRACE)
error(cntx,"vmm.parser.MissingCloseOfArgumentList","}");
if (openingParen == Token.LEFT_BRACKET && tok != Token.RIGHT_BRACKET)
error(cntx,"vmm.parser.MissingCloseOfArgumentList","]");
if (funcTok == Token.FUNCTION) {
cntx.bldr.addFunctionRef(((Function)symbol).getProgFunction());
return Type.REAL;
}
else if (funcTok == Token.COMPLEX_FUNCTION) {
cntx.bldr.addFunctionRef(((ComplexFunction)symbol).getProgFunction());
return Type.COMPLEX;
}
else if (funcTok == Token.FUNCTION_COMPLEX_TO_REAL)
return Type.COMPLEX;
else {
StandardFunction f = (StandardFunction)symbol;
cntx.bldr.addStackOp( argType == Type.COMPLEX ? f.getComplexArgOp() : f.getRealArgOp() );
return argType == Type.COMPLEX ? f.getReturnTypeForComplexArg() : f.getReturnTypeForRealArg();
}
case LEFT_PAREN:
type = parseBExpr(cntx);
tok = cntx.next();
if (tok == Token.EOS)
error(cntx,"vmm.parser.MissingRightGroupThingAtEOS",")","(");
if (tok != Token.RIGHT_PAREN)
error(cntx,"vmm.parser.MissingRightGroupThing",")","(",cntx.tokstr);
return type;
case LEFT_BRACE:
type = parseBExpr(cntx);
tok = cntx.next();
if (tok == Token.EOS)
error(cntx,"vmm.parser.MissingRightGroupThingAtEOS","]","[");
if (tok != Token.RIGHT_BRACE)
error(cntx,"vmm.parser.MissingRightGroupThing","]","[",cntx.tokstr);
return type;
case LEFT_BRACKET:
type = parseBExpr(cntx);
tok = cntx.next();
if (tok == Token.EOS)
error(cntx,"vmm.parser.MissingRightGroupThingAtEOS","}","{");
if (tok != Token.RIGHT_PAREN)
error(cntx,"vmm.parser.MissingRightGroupThing","}","{",cntx.tokstr);
return type;
case RIGHT_PAREN:
error(cntx,"vmm.parser.ExtraRightGroupThing",")","(");
return null;
case RIGHT_BRACE:
error(cntx,"vmm.parser.ExtraRightGroupThing","}","{");
return null;
case RIGHT_BRACKET:
error(cntx,"vmm.parser.ExtraRightGroupThing","]","[");
return null;
case UNKNOWN_CHAR:
error(cntx,"vmm.parser.UnknownChar",cntx.tokstr);
return null;
case UNKNOWN_WORD:
error(cntx,"vmm.parser.UndefinedWord",cntx.tokstr);
return null;
case ILLEGAL_NUMBER:
error(cntx,"vmm.parser.IllegalNumber",cntx.tokstr);
return null;
case EOS:
error(cntx,"vmm.parser.IncompleteExpression");
default:
error(cntx,"vmm.parser.UnexcpectedToken",cntx.tokstr);
return null;
}
}
//-------------------------------------------------------------------------------------------
private enum Token {
NUMBER, ILLEGAL_NUMBER,
FUNCTION, COMPLEX_FUNCTION, STANDARD_FUNCTION, FUNCTION_COMPLEX_TO_REAL,
VARIABLE, COMPLEX_VARIABLE, ARGUMENT, COMPLEX_ARGUMENT,
UNKNOWN_WORD,
LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, LEFT_BRACKET, RIGHT_BRACKET,
PLUS, MINUS, TIMES, DIVIDE, POWER, AND, OR, NOT,
EQUAL, NOT_EQUAL, GREATER, LESS, GREATER_EQUAL, LESS_EQUAL,
QUESTION, COLON, COMMA,
UNKNOWN_CHAR, EOS
}
private static class Context {
private final static Pattern numberRegex = Pattern.compile("(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?");
private Matcher numberMatcher; // Used for reading floating point numbers; created from the floatRegex Pattern.
private SymbolTable symbolTable;
private Token currentToken;
ProgFunction.Builder bldr;
boolean complexOnly;
Object symbol;
String str;
int pos;
double number; // set for token type NUMBER only
String tokstr; // set for all tokens
Context(String str, SymbolTable table, boolean complexOnly) {
this.str = str;
this.pos = 0;
this.symbolTable = table;
this.complexOnly = complexOnly;
this.bldr = new ProgFunction.Builder();
}
Token peek() {
if (currentToken == null)
currentToken = readToken();
return currentToken;
}
Token next() {
Token t = peek();
currentToken = null;
return t;
}
private Token readToken() {
symbol = null;
if (str == null)
return Token.EOS;
while (pos < str.length() && Character.isWhitespace(str.charAt(pos)))
pos++;
if (pos >= str.length())
return Token.EOS;
char ch = str.charAt(pos);
if (Character.isLetter(ch) || ch == '_') {
tokstr = "";
while ( pos < str.length() && (Character.isLetterOrDigit(str.charAt(pos)) || str.charAt(pos) == '_') ) {
tokstr += str.charAt(pos);
pos++;
}
while ( pos < str.length() && str.charAt(pos) == '\'') {
tokstr += '\'';
pos++;
}
String lower = tokstr.toLowerCase();
symbol = symbolTable.get(lower);
if (symbol != null) {
if (symbol instanceof Variable)
return Token.VARIABLE;
else if (symbol instanceof ComplexVariable)
return Token.COMPLEX_VARIABLE;
else if (symbol instanceof Function)
return Token.FUNCTION;
else if (symbol instanceof ComplexFunction)
return Token.COMPLEX_FUNCTION;
else if (symbol instanceof Argument)
return Token.ARGUMENT;
else if (symbol instanceof ComplexArgument)
return Token.COMPLEX_ARGUMENT;
else if (symbol instanceof StandardFunction)
return Token.STANDARD_FUNCTION;
else
throw new IllegalStateException("internal error: unknown object type in symbol table");
}
else if (lower.equals("complex"))
return Token.FUNCTION_COMPLEX_TO_REAL;
else if (lower.equals("and"))
return Token.AND;
else if (lower.equals("or"))
return Token.OR;
else if (lower.equals("not"))
return Token.NOT;
else
return Token.UNKNOWN_WORD;
}
else if (Character.isDigit(ch) || ch == '.') {
if (numberMatcher == null)
numberMatcher = numberRegex.matcher(str);
numberMatcher.region(pos,str.length());
if (numberMatcher.lookingAt()) {
tokstr = numberMatcher.group();
pos = numberMatcher.end();
try {
number = Double.parseDouble(tokstr);
if (Double.isInfinite(number) || Double.isNaN(number))
throw new NumberFormatException();
}
catch (NumberFormatException e) {
return Token.ILLEGAL_NUMBER;
}
return Token.NUMBER;
}
else {
pos++;
tokstr = ".";
return Token.ILLEGAL_NUMBER;
}
}
else if (ch == '=') {
tokstr = "=";
pos++;
if (pos == str.length())
return Token.EQUAL;
else if (str.charAt(pos) == '=') {
tokstr += '=';
pos++;
return Token.EQUAL;
}
else if (str.charAt(pos) == '>') {
tokstr += '>';
pos++;
return Token.GREATER_EQUAL;
}
else if (str.charAt(pos) == '<') {
tokstr += '<';
pos++;
return Token.LESS_EQUAL;
}
else
return Token.EQUAL;
}
else if (ch == '<') {
tokstr = "<";
pos++;
if (pos == str.length())
return Token.LESS;
else if (str.charAt(pos) == '=') {
pos++;
tokstr += '=';
return Token.LESS_EQUAL;
}
else if (str.charAt(pos) == '>') {
pos++;
tokstr += '>';
return Token.NOT_EQUAL;
}
else
return Token.LESS;
}
else if (ch == '>') {
tokstr = ">";
pos++;
if (pos == str.length())
return Token.GREATER;
else if (str.charAt(pos) == '=') {
tokstr += '=';
pos++;
return Token.GREATER_EQUAL;
}
else if (str.charAt(pos) == '>') {
tokstr += '>';
pos++;
return Token.NOT_EQUAL;
}
else
return Token.GREATER;
}
else if (ch == '!') {
tokstr = "!";
pos++;
if (pos < str.length() && str.charAt(pos) == '=') {
tokstr += '=';
pos++;
return Token.NOT_EQUAL;
}
else
return Token.UNKNOWN_CHAR;
}
else if (ch == '*') {
tokstr = "*";
pos++;
if (pos < str.length() && str.charAt(pos) == '*') {
tokstr += '*';
pos++;
return Token.POWER;
}
else
return Token.TIMES;
}
else if (ch == '&') {
tokstr = "&";
pos++;
if (pos < str.length() && str.charAt(pos) == '&') {
tokstr += '&';
pos++;
}
return Token.AND;
}
else if (ch == '|') {
tokstr = "|";
pos++;
if (pos < str.length() && str.charAt(pos) == '|') {
tokstr += '|';
pos++;
}
return Token.OR;
}
else {
tokstr = "" + ch;
pos++;
switch(ch) {
case '(': return Token.LEFT_PAREN;
case ')': return Token.RIGHT_PAREN;
case '{': return Token.LEFT_BRACE;
case '}': return Token.RIGHT_BRACE;
case '[': return Token.LEFT_BRACKET;
case ']': return Token.RIGHT_BRACKET;
case '?': return Token.QUESTION;
case ':': return Token.COLON;
case ',': return Token.COMMA;
case '+': return Token.PLUS;
case '-': return Token.MINUS;
case '/': return Token.DIVIDE;
case '^': return Token.POWER;
case '~': return Token.NOT;
default: return Token.UNKNOWN_CHAR;
}
}
}
}
private static class SymbolTable {
private SymbolTable parent;
private HashMap table = new HashMap();
SymbolTable() {
}
SymbolTable(SymbolTable parent) {
this.parent = parent;
}
void put(String name, Object val) {
table.put(name, val);
}
void remove(String name) {
if (name != null)
table.remove(name.toLowerCase());
}
Object get(String name) {
Object val = table.get(name);
if (val == null && parent != null)
return parent.get(name);
else
return val;
}
}
private static class Argument {
int argnum;
Argument(int n) {
argnum = n;
}
}
private static class ComplexArgument {
int argnum;
ComplexArgument(int n) {
argnum = n;
}
}
}