org.ejml.equation.Equation Maven / Gradle / Ivy
/*
* Copyright (c) 2022, Peter Abeles. All Rights Reserved.
*
* This file is part of Efficient Java Matrix Library (EJML).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ejml.equation;
import org.ejml.data.*;
import org.ejml.ops.ConvertMatrixData;
import org.ejml.ops.DConvertMatrixStruct;
import org.ejml.ops.FConvertMatrixStruct;
import org.ejml.simple.SimpleMatrix;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import static org.ejml.equation.TokenList.Type;
/**
*
* Equation allows the user to manipulate matrices in a more compact symbolic way, similar to Matlab and Octave.
* Aliases are made to Matrices and scalar values which can then be manipulated by specifying an equation in a string.
* These equations can either be "pre-compiled" [1] into a sequence of operations or immediately executed. While the
* former is more verbose, when dealing with small matrices it significantly faster and runs close to the speed of
* normal hand written code.
*
*
* Each string represents a single line and must have one and only one assignment '=' operator. Temporary variables
* are handled transparently to the user. Temporary variables are declared at compile time, but resized at runtime.
* If the inputs are not resized and the code is precompiled, then no new memory will be declared. When a matrix
* is assigned the results of an operation it is resized so that it can store the results.
*
*
* The compiler currently produces simplistic code. For example, if it encounters the following equation "a = b*c' it
* will not invoke multTransB(b,c,a), but will explicitly transpose c and then call mult(). In the future it
* will recognize such short cuts.
*
*
* Usage example:
*
* Equation eq = new Equation();
* eq.alias(x,"x", P,"P", Q,"Q");
*
* eq.process("x = F*x");
* eq.process("P = F*P*F' + Q");
*
* Which will modify the matrices 'x' and 'P'. Support for sub-matrices and inline matrix construction is also
* available.
*
* eq.process("x = [2 1 0; 0 1 3;4 5 6]*x"); // create a 3x3 matrix then multiply it by x
* eq.process("x(1:3,5:9) = [a ; b]*2"); // fill the sub-matrix with the result
* eq.process("x(:) = a(4:2:20)"); // fill all elements of x with the specified elements in 'a'
* eq.process("x( 4 3 ) = a"); // fill only the specified number sequences with 'a'
* eq.process("x = [2:3:25 1 4]"); // create a row matrix from the number sequence
*
*
* To pre-compile one of the above lines, do the following instead:
*
* Sequence predictX = eq.compile("x = F*x");
* predictX.perform();
*
* Then you can invoke it as much as you want without the "expensive" compilation step. If you are dealing with
* larger matrices (e.g. 100 by 100) then it is likely that the compilation step has an insignificant runtime
* cost.
*
* Variables can also be lazily declared and their type inferred under certain conditions. For example:
*
* eq.alias(A,"A", B,"B");
* eq.process("C = A*B");
* DMatrixRMaj C = eq.lookupMatrix("C");
*
* In this case 'C' was lazily declared. To access the variable, or any others, you can use one of the lookup*()
* functions.
*
* Sometimes you don't get the results you expect and it can be helpful to print out the tokens and which operations
* the compiler selected. To do this set the second parameter to eq.compile() or eq.process() to true:
*
* Code:
* eq.process("C=2.1*B'*A",true);
*
* Output:
* Parsed tokens:
* ------------
* Word:C
* ASSIGN
* VarSCALAR
* TIMES
* VarMATRIX
* TRANSPOSE
* TIMES
* VarMATRIX
*
* Operations:
* ------------
* transpose-m
* multiply-ms
* multiply-mm
* copy-mm
*
*
* Built in Constants
*
* pi = Math.PI
* e = Math.E
*
*
* Supported functions
*
* eye(N) Create an identity matrix which is N by N.
* eye(A) Create an identity matrix which is A.numRows by A.numCols
* normF(A) Frobenius normal of the matrix.
* normP(A,p) P-norm for a matrix or vector. p=1 or p=2 is typical.
* sum(A) Sum of every element in A
* sum(A,d) Sum of rows for d = 0 and columns for d = 1
* det(A) Determinant of the matrix
* inv(A) Inverse of a matrix
* pinv(A) Pseudo-inverse of a matrix
* rref(A) Reduced row echelon form of A
* trace(A) Trace of the matrix
* zeros(r,c) Matrix full of zeros with r rows and c columns.
* ones(r,c) Matrix full of ones with r rows and c columns.
* rand(r,c) Matrix filled with i.i.d uniform numbers from 0 to 1
* randn(r,c) Matrix filled with i.i.d normal distribution with mean of zero and stdev of 1
* rng(seed) Specifies the random number generator's seed
* diag(A) If a vector then returns a square matrix with diagonal elements filled with vector
* diag(A) If a matrix then it returns the diagonal elements as a column vector
* dot(A,B) Returns the dot product of two vectors as a double. Does not work on general matrices.
* solve(A,B) Returns the solution X from A*X = B.
* kron(A,B) Kronecker product
* abs(A) Absolute value of A.
* max(A) Element with the largest value in A.
* max(A,d) Vector containing largest element along the rows (d=0) or columns (d=1)
* min(A) Element with the smallest value in A.
* min(A,d) Vector containing largest element along the rows (d=0) or columns (d=1)
* pow(a,b) Computes a to the power of b. Can also be invoked with "a^b" scalars only.
* sqrt(a) Computes the square root of a.
* sin(a) Math.sin(a) for scalars only
* cos(a) Math.cos(a) for scalars only
* atan(a) Math.atan(a) for scalars only
* atan2(a,b) Math.atan2(a,b) for scalars only
* exp(a) Math.exp(a) for scalars is also an element-wise matrix operator
* log(a) Math.log(a) for scalars is also an element-wise matrix operator
*
*
* Supported operations
*
* '*' multiplication (Matrix-Matrix, Scalar-Matrix, Scalar-Scalar)
* '+' addition (Matrix-Matrix, Scalar-Matrix, Scalar-Scalar)
* '-' subtraction (Matrix-Matrix, Scalar-Matrix, Scalar-Scalar)
* '/' divide (Matrix-Scalar, Scalar-Scalar)
* '/' matrix solve "x=b/A" is equivalent to x=solve(A,b) (Matrix-Matrix)
* '^' Scalar power. a^b is a to the power of b.
* '\' left-divide. Same as divide but reversed. e.g. x=A\b is x=solve(A,b)
* '.*' element-wise multiplication (Matrix-Matrix)
* './' element-wise division (Matrix-Matrix)
* '.^' element-wise power. (scalar-scalar) (matrix-matrix) (scalar-matrix) (matrix-scalar)
* ''' matrix transpose
* '=' assignment by value (Matrix-Matrix, Scalar-Scalar)
*
* Order of operations: [ ' ] precedes [ ^ .^ ] precedes [ * / .* ./ ] precedes [ + - ]
*
* Specialized submatrix and matrix construction syntax
*
* Extracts a sub-matrix from A with rows 1 to 10 (inclusive) and column 3.
* A(1:10,3)
* Extracts a sub-matrix from A with rows 2 to numRows-1 (inclusive) and all the columns.
* A(2:,:)
* Will concat A and B along their columns and then concat the result with C along their rows.
* [A,B;C]
* Defines a 3x2 matrix.
* [1 2; 3 4; 4 5]
* You can also perform operations inside:
* [[2 3 4]';[4 5 6]']
* Will assign B to the sub-matrix in A.
* A(1:3,4:8) = B
*
*
* Integer Number Sequences
* Previous example code has made much use of integer number sequences. There are three different types of integer number
* sequences 'explicit', 'for', and 'for-range'.
*
* 1) Explicit:
* Example: "1 2 4 0"
* Example: "1 2,-7,4" Commas needed to create negative numbers. Otherwise it will be subtraction.
* 2) for:
* Example: "2:10" Sequence of "2 3 4 5 6 7 8 9 10"
* Example: "2:2:10" Sequence of "2 4 6 8 10"
* 3) for-range:
* Example: "2:" Sequence of "2 3 ... max"
* Example: "2:2:" Sequence of "2 4 ... max"
* 4) combined:
* Example: "1 2 7:10" Sequence of "1 2 7 8 9 10"
*
*
* Macros
* Macros are used to insert patterns into the code. Consider this example:
*
* eq.process("macro ata( a ) = (a'*a)");
* eq.process("b = ata(c)");
*
* The first line defines a macro named "ata" with one parameter 'a'. When compiled the equation in the second
* line is replaced with "b = (a'*a)". The "(" ")" in the macro isn't strictly necissary in this situation, but
* is a good practice. Consider the following.
*
* eq.process("b = ata(c)*r");
*
* Will become "b = (a'*a)*r" but with out () it will be "b = a'*a*r" which is not the same thing!
*
* NOTE:In the future macros might be replaced with functions. Macros are harder for the user to debug, but
* functions are harder for EJML's developer to implement.
*
* Footnotes:
*
* [1] It is not compiled into Java byte-code, but into a sequence of operations stored in a List.
*
*
* @author Peter Abeles
*/
// TODO Change parsing so that operations specify a pattern.
// TODO Recycle temporary variables
// TODO intelligently handle identity matrices
@SuppressWarnings("NullAway") // Massive false positive rate
public class Equation {
HashMap variables = new HashMap<>();
HashMap macros = new HashMap<>();
// storage for a single word in the tokenizer
char[] storage = new char[1024];
ManagerFunctions functions = new ManagerFunctions();
ManagerTempVariables managerTemp = new ManagerTempVariables();
public Equation() {
alias(Math.PI, "pi");
alias(Math.E, "e");
}
/**
* Consturctor which allows you to alias variables
*
* @param args arguments for alias
* @see #alias(Object...)
*/
public Equation( Object... args ) {
this();
alias(args);
}
/**
* Specifies the seed used in random number generators
*
* @param seed New seed for random number generator
*/
public void setSeed( long seed ) {
functions.managerTemp.getRandom().setSeed(seed);
}
/**
* Sets the random seed using a seed based on the current time
*/
public void setSeed() {
functions.managerTemp.rand = new Random();
}
/**
* Adds a new Matrix variable. If one already has the same name it is written over.
*
* While more verbose for multiple variables, this function doesn't require new memory be declared
* each time it's called.
*
* @param variable Matrix which is to be assigned to name
* @param name The name of the variable
*/
public void alias( DMatrixRMaj variable, String name ) {
if (isReserved(name))
throw new RuntimeException("Reserved word or contains a reserved character");
VariableMatrix old = (VariableMatrix)variables.get(name);
if (old == null) {
variables.put(name, new VariableMatrix(variable));
} else {
old.matrix = variable;
}
}
public void alias( FMatrixRMaj variable, String name ) {
DMatrixRMaj f = new DMatrixRMaj(variable.numRows, variable.numCols);
ConvertMatrixData.convert(variable, f);
alias(f, name);
}
public void alias( DMatrixSparseCSC variable, String name ) {
DMatrixRMaj f = new DMatrixRMaj(variable.numRows, variable.numCols);
DConvertMatrixStruct.convert(variable, f);
alias(f, name);
}
public void alias( SimpleMatrix variable, String name ) {
alias((Object)variable.getMatrix(), name);
}
/**
* Adds a new floating point variable. If one already has the same name it is written over.
*
* @param value Value of the number
* @param name Name in code
*/
public void alias( double value, String name ) {
if (isReserved(name))
throw new RuntimeException("Reserved word or contains a reserved character. '" + name + "'");
VariableDouble old = (VariableDouble)variables.get(name);
if (old == null) {
variables.put(name, new VariableDouble(value));
} else {
old.value = value;
}
}
/**
* Adds a new integer variable. If one already has the same name it is written over.
*
* @param value Value of the number
* @param name Name in code
*/
public void alias( int value, String name ) {
if (isReserved(name))
throw new RuntimeException("Reserved word or contains a reserved character");
VariableInteger old = (VariableInteger)variables.get(name);
if (old == null) {
variables.put(name, new VariableInteger(value));
} else {
old.value = value;
}
}
private void alias( IntegerSequence sequence, String name ) {
if (isReserved(name))
throw new RuntimeException("Reserved word or contains a reserved character");
VariableIntegerSequence old = (VariableIntegerSequence)variables.get(name);
if (old == null) {
variables.put(name, new VariableIntegerSequence(sequence));
} else {
old.sequence = sequence;
}
}
/**
* Creates multiple aliases at once.
*/
public void alias( Object... args ) {
if (args.length%2 == 1)
throw new RuntimeException("Even number of arguments expected");
for (int i = 0; i < args.length; i += 2) {
aliasGeneric(args[i], (String)args[i + 1]);
}
}
/**
* Aliases variables with an unknown type.
*
* @param variable The variable being aliased
* @param name Name of the variable
*/
protected void aliasGeneric( Object variable, String name ) {
if (variable.getClass() == Integer.class) {
alias(((Integer)variable).intValue(), name);
} else if (variable.getClass() == Double.class) {
alias(((Double)variable).doubleValue(), name);
} else if (variable.getClass() == DMatrixRMaj.class) {
alias((DMatrixRMaj)variable, name);
} else if (variable.getClass() == FMatrixRMaj.class) {
alias((FMatrixRMaj)variable, name);
} else if (variable.getClass() == DMatrixSparseCSC.class) {
alias((DMatrixSparseCSC)variable, name);
} else if (variable.getClass() == SimpleMatrix.class) {
alias((SimpleMatrix)variable, name);
} else if (variable instanceof DMatrixFixed) {
DMatrixRMaj M = new DMatrixRMaj(1, 1);
DConvertMatrixStruct.convert((DMatrixFixed)variable, M);
alias(M, name);
} else if (variable instanceof FMatrixFixed) {
FMatrixRMaj M = new FMatrixRMaj(1, 1);
FConvertMatrixStruct.convert((FMatrixFixed)variable, M);
alias(M, name);
} else {
throw new RuntimeException("Unknown value type of " +
variable.getClass().getSimpleName() + " for variable " + name);
}
}
public Sequence compile( String equation ) {
return compile(equation, true, false);
}
/**
* Parses the equation and compiles it into a sequence which can be executed later on
*
* @param equation String in simple equation format.
* @param assignment if true an assignment is expected and an exception if thrown if there is non
* @param debug if true it will print out debugging information
* @return Sequence of operations on the variables
*/
public Sequence compile( String equation, boolean assignment, boolean debug ) {
functions.setManagerTemp(managerTemp);
Sequence sequence = new Sequence();
TokenList tokens = extractTokens(equation, managerTemp);
if (tokens.size() < 3)
throw new RuntimeException("Too few tokens");
TokenList.Token t0 = tokens.getFirst();
if (t0.word != null && t0.word.compareToIgnoreCase("macro") == 0) {
parseMacro(tokens, sequence);
} else {
insertFunctionsAndVariables(tokens);
insertMacros(tokens);
if (debug) {
System.out.println("Parsed tokens:\n------------");
tokens.print();
System.out.println();
}
// Get the results variable
if (t0.getType() != Type.VARIABLE && t0.getType() != Type.WORD) {
compileTokens(sequence, tokens);
// If there's no output then this is acceptable, otherwise it's assumed to be a bug
// If there's no output then a configuration was changed
Variable variable = tokens.getFirst().getVariable();
if (variable != null) {
if (assignment)
throw new IllegalArgumentException("No assignment to an output variable could be found. Found " + t0);
else {
sequence.output = variable; // set this to be the output for print()
}
}
} else {
compileAssignment(sequence, tokens, t0);
}
if (debug) {
System.out.println("Operations:\n------------");
for (int i = 0; i < sequence.operations.size(); i++) {
System.out.println(sequence.operations.get(i).name());
}
}
}
return sequence;
}
/**
* An assignment is being made to some output. looks something like: A = B
*/
private void compileAssignment( Sequence sequence, TokenList tokens, TokenList.Token t0 ) {
// see if it is assign or a range
List range = parseAssignRange(sequence, tokens, t0);
TokenList.Token t1 = t0.next;
if (t1.getType() != Type.SYMBOL || t1.getSymbol() != Symbol.ASSIGN)
throw new ParseError("Expected assignment operator next");
// Parse the right side of the equation
TokenList tokensRight = tokens.extractSubList(t1.next, tokens.last);
compileTokens(sequence, tokensRight);
if (tokensRight.getLast().getType() != Type.VARIABLE)
throw new RuntimeException("BUG the last token must be a variable");
// copy the results into the output
Variable variableRight = tokensRight.getFirst().getVariable();
if (range == null) {
// no range, so copy results into the entire output matrix
sequence.output = createVariableInferred(t0, variableRight);
sequence.addOperation(Operation.copy(variableRight, sequence.output));
} else {
// a sub-matrix range is specified. Copy into that inner part
if (t0.getType() == Type.WORD) {
throw new ParseError("Can't do lazy variable initialization with submatrices. " + t0.getWord());
}
sequence.addOperation(Operation.copy(variableRight, t0.getVariable(), range));
}
}
private void compileTokens( Sequence sequence, TokenList tokens ) {
checkForUnknownVariables(tokens);
handleParentheses(tokens, sequence);
if (tokens.size() > 1) {
parseBlockNoParentheses(tokens, sequence, false);
}
// see if it needs to be parsed more
if (tokens.size() != 1)
throw new RuntimeException("BUG");
}
/**
* Parse a macro defintion.
*
* "macro NAME( var0 , var1 ) = 5+var0+var1'
*/
private void parseMacro( TokenList tokens, Sequence sequence ) {
Macro macro = new Macro();
TokenList.Token t = tokens.getFirst().next;
if (t.word == null) {
throw new ParseError("Expected the macro's name after " + tokens.getFirst().word);
}
List variableTokens = new ArrayList<>();
macro.name = t.word;
t = t.next;
t = parseMacroInput(variableTokens, t);
for (TokenList.Token a : variableTokens) {
if (a.word == null) throw new ParseError("expected word in macro header");
macro.inputs.add(a.word);
}
t = t.next;
if (t == null || t.getSymbol() != Symbol.ASSIGN)
throw new ParseError("Expected assignment");
t = t.next;
macro.tokens = new TokenList(t, tokens.last);
sequence.addOperation(macro.createOperation(macros));
}
private TokenList.Token parseMacroInput( List variables, TokenList.Token t ) {
if (t.getSymbol() != Symbol.PAREN_LEFT) {
throw new ParseError("Expected (");
}
t = t.next;
boolean expectWord = true;
while (t != null && t.getSymbol() != Symbol.PAREN_RIGHT) {
if (expectWord) {
variables.add(t);
expectWord = false;
} else {
if (t.getSymbol() != Symbol.COMMA)
throw new ParseError("Expected comma");
expectWord = true;
}
t = t.next;
}
if (t == null)
throw new ParseError("Token sequence ended unexpectedly");
return t;
}
/**
* Examines the list of variables for any unknown variables and throws an exception if one is found
*/
private void checkForUnknownVariables( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
while (t != null) {
if (t.getType() == Type.WORD)
throw new ParseError("Unknown variable on right side. " + t.getWord());
t = t.next;
}
}
/**
* Infer the type of and create a new output variable using the results from the right side of the equation.
* If the type is already known just return that.
*/
private Variable createVariableInferred( TokenList.Token t0, Variable variableRight ) {
Variable result;
if (t0.getType() == Type.WORD) {
switch (variableRight.getType()) {
case MATRIX:
alias(new DMatrixRMaj(1, 1), t0.getWord());
break;
case SCALAR:
if (variableRight instanceof VariableInteger) {
alias(0, t0.getWord());
} else {
alias(1.0, t0.getWord());
}
break;
case INTEGER_SEQUENCE:
alias((IntegerSequence)null, t0.getWord());
break;
default:
throw new RuntimeException("Type not supported for assignment: " + variableRight.getType());
}
result = variables.get(t0.getWord());
} else {
result = t0.getVariable();
}
return result;
}
/**
* See if a range for assignment is specified. If so return the range, otherwise return null
*
* Example of assign range:
* a(0:3,4:5) = blah
* a((0+2):3,4:5) = blah
*/
private List parseAssignRange( Sequence sequence, TokenList tokens, TokenList.Token t0 ) {
// find assignment symbol
TokenList.Token tokenAssign = t0.next;
while (tokenAssign != null && tokenAssign.symbol != Symbol.ASSIGN) {
tokenAssign = tokenAssign.next;
}
if (tokenAssign == null)
throw new ParseError("Can't find assignment operator");
// see if it is a sub matrix before
if (tokenAssign.previous.symbol == Symbol.PAREN_RIGHT) {
TokenList.Token start = t0.next;
if (start.symbol != Symbol.PAREN_LEFT)
throw new ParseError("Expected left param for assignment");
TokenList.Token end = tokenAssign.previous;
TokenList subTokens = tokens.extractSubList(start, end);
subTokens.remove(subTokens.getFirst());
subTokens.remove(subTokens.getLast());
handleParentheses(subTokens, sequence);
List inputs = parseParameterCommaBlock(subTokens, sequence);
if (inputs.isEmpty())
throw new ParseError("Empty function input parameters");
List range = new ArrayList<>();
addSubMatrixVariables(inputs, range);
if (range.size() != 1 && range.size() != 2) {
throw new ParseError("Unexpected number of range variables. 1 or 2 expected");
}
return range;
}
return null;
}
/**
* Searches for pairs of parentheses and processes blocks inside of them. Embedded parentheses are handled
* with no problem. On output only a single token should be in tokens.
*
* @param tokens List of parsed tokens
* @param sequence Sequence of operators
*/
protected void handleParentheses( TokenList tokens, Sequence sequence ) {
// have a list to handle embedded parentheses, e.g. (((((a)))))
List left = new ArrayList<>();
// find all of them
TokenList.Token t = tokens.first;
while (t != null) {
TokenList.Token next = t.next;
if (t.getType() == Type.SYMBOL) {
if (t.getSymbol() == Symbol.PAREN_LEFT)
left.add(t);
else if (t.getSymbol() == Symbol.PAREN_RIGHT) {
if (left.isEmpty())
throw new ParseError(") found with no matching (");
TokenList.Token a = left.remove(left.size() - 1);
// remember the element before so the new one can be inserted afterwards
TokenList.Token before = a.previous;
TokenList sublist = tokens.extractSubList(a, t);
// remove parentheses
sublist.remove(sublist.first);
sublist.remove(sublist.last);
// if its a function before () then the () indicates its an input to a function
if (before != null && before.getType() == Type.FUNCTION) {
List inputs = parseParameterCommaBlock(sublist, sequence);
if (inputs.isEmpty())
throw new ParseError("Empty function input parameters");
else {
createFunction(before, inputs, tokens, sequence);
}
} else if (before != null && before.getType() == Type.VARIABLE &&
before.getVariable().getType() == VariableType.MATRIX) {
// if it's a variable then that says it's a sub-matrix
TokenList.Token extract = parseSubmatrixToExtract(before, sublist, sequence);
// put in the extract operation
tokens.insert(before, extract);
tokens.remove(before);
} else {
// if null then it was empty inside
TokenList.Token output = parseBlockNoParentheses(sublist, sequence, false);
if (output != null)
tokens.insert(before, output);
}
}
}
t = next;
}
if (!left.isEmpty())
throw new ParseError("Dangling ( parentheses");
}
/**
* Searches for commas in the set of tokens. Used for inputs to functions.
*
* Ignore comma's which are inside a [ ] block
*
* @return List of output tokens between the commas
*/
protected List parseParameterCommaBlock( TokenList tokens, Sequence sequence ) {
// find all the comma tokens
List commas = new ArrayList<>();
TokenList.Token token = tokens.first;
int numBracket = 0;
while (token != null) {
if (token.getType() == Type.SYMBOL) {
switch (token.getSymbol()) {
case COMMA:
if (numBracket == 0)
commas.add(token);
break;
case BRACKET_LEFT:
numBracket++;
break;
case BRACKET_RIGHT:
numBracket--;
break;
default:
}
}
token = token.next;
}
List output = new ArrayList<>();
if (commas.isEmpty()) {
output.add(parseBlockNoParentheses(tokens, sequence, false));
} else {
TokenList.Token before = tokens.first;
for (int i = 0; i < commas.size(); i++) {
TokenList.Token after = commas.get(i);
if (before == after)
throw new ParseError("No empty function inputs allowed!");
TokenList.Token tmp = after.next;
TokenList sublist = tokens.extractSubList(before, after);
sublist.remove(after);// remove the comma
output.add(parseBlockNoParentheses(sublist, sequence, false));
before = tmp;
}
// if the last character is a comma then after.next above will be null and thus before is null
if (before == null)
throw new ParseError("No empty function inputs allowed!");
TokenList.Token after = tokens.last;
TokenList sublist = tokens.extractSubList(before, after);
output.add(parseBlockNoParentheses(sublist, sequence, false));
}
return output;
}
/**
* Converts a submatrix into an extract matrix operation.
*
* @param variableTarget The variable in which the submatrix is extracted from
*/
protected TokenList.Token parseSubmatrixToExtract( TokenList.Token variableTarget,
TokenList tokens, Sequence sequence ) {
List inputs = parseParameterCommaBlock(tokens, sequence);
List variables = new ArrayList<>();
// for the operation, the first variable must be the matrix which is being manipulated
variables.add(variableTarget.getVariable());
addSubMatrixVariables(inputs, variables);
if (variables.size() != 2 && variables.size() != 3) {
throw new ParseError("Unexpected number of variables. 1 or 2 expected");
}
// first parameter is the matrix it will be extracted from. rest specify range
Operation.Info info;
// only one variable means its referencing elements
// two variables means its referencing a sub matrix
if (inputs.size() == 1) {
Variable varA = variables.get(1);
if (varA.getType() == VariableType.SCALAR) {
info = functions.create("extractScalar", variables);
} else {
info = functions.create("extract", variables);
}
} else if (inputs.size() == 2) {
Variable varA = variables.get(1);
Variable varB = variables.get(2);
if (varA.getType() == VariableType.SCALAR && varB.getType() == VariableType.SCALAR) {
info = functions.create("extractScalar", variables);
} else {
info = functions.create("extract", variables);
}
} else {
throw new ParseError("Expected 2 inputs to sub-matrix");
}
sequence.addOperation(info.op);
return new TokenList.Token(info.output);
}
/**
* Goes through the token lists and adds all the variables which can be used to define a sub-matrix. If anything
* else is found an excpetion is thrown
*/
private void addSubMatrixVariables( List inputs, List variables ) {
for (int i = 0; i < inputs.size(); i++) {
TokenList.Token t = inputs.get(i);
if (t.getType() != Type.VARIABLE)
throw new ParseError("Expected variables only in sub-matrix input, not " + t.getType());
Variable v = t.getVariable();
if (v.getType() == VariableType.INTEGER_SEQUENCE || isVariableInteger(t)) {
variables.add(v);
} else {
throw new ParseError("Expected an integer, integer sequence, or array range to define a submatrix");
}
}
}
/**
* Parses a code block with no parentheses and no commas. After it is done there should be a single token left,
* which is returned.
*/
protected TokenList.Token parseBlockNoParentheses( TokenList tokens, Sequence sequence, boolean insideMatrixConstructor ) {
// search for matrix bracket operations
if (!insideMatrixConstructor) {
parseBracketCreateMatrix(tokens, sequence);
}
// First create sequences from anything involving a colon
parseSequencesWithColons(tokens, sequence);
// process operators depending on their priority
parseNegOp(tokens, sequence);
parseOperationsL(tokens, sequence);
parseOperationsLR(new Symbol[]{Symbol.POWER, Symbol.ELEMENT_POWER}, tokens, sequence);
parseOperationsLR(new Symbol[]{Symbol.TIMES, Symbol.RDIVIDE, Symbol.LDIVIDE, Symbol.ELEMENT_TIMES, Symbol.ELEMENT_DIVIDE}, tokens, sequence);
parseOperationsLR(new Symbol[]{Symbol.PLUS, Symbol.MINUS}, tokens, sequence);
// Commas are used in integer sequences. Can be used to force to compiler to treat - as negative not
// minus. They can now be removed since they have served their purpose
stripCommas(tokens);
// now construct rest of the lists and combine them together
parseIntegerLists(tokens);
parseCombineIntegerLists(tokens);
if (!insideMatrixConstructor) {
if (tokens.size() > 1) {
System.err.println("Remaining tokens: " + tokens.size);
TokenList.Token t = tokens.first;
while (t != null) {
System.err.println(" " + t);
t = t.next;
}
throw new RuntimeException("BUG in parser. There should only be a single token left");
}
return tokens.first;
} else {
return null;
}
}
/**
* Removes all commas from the token list
*/
private void stripCommas( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
while (t != null) {
TokenList.Token next = t.next;
if (t.getSymbol() == Symbol.COMMA) {
tokens.remove(t);
}
t = next;
}
}
/**
* Searches for descriptions of integer sequences and array ranges that have a colon character in them
*
* Examples of integer sequences:
* 1:6
* 2:4:20
* :
*
* Examples of array range
* 2:
* 2:4:
*/
protected void parseSequencesWithColons( TokenList tokens, Sequence sequence ) {
TokenList.Token t = tokens.getFirst();
if (t == null)
return;
int state = 0;
TokenList.Token start = null;
TokenList.Token middle = null;
TokenList.Token prev = t;
boolean last = false;
while (true) {
if (state == 0) {
if (isVariableInteger(t) && (t.next != null && t.next.getSymbol() == Symbol.COLON)) {
start = t;
state = 1;
t = t.next;
} else if (t != null && t.getSymbol() == Symbol.COLON) {
// If it starts with a colon then it must be 'all' or a type-o
IntegerSequence range = new IntegerSequence.Range(null, null);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(range);
TokenList.Token n = new TokenList.Token(varSequence);
tokens.insert(t.previous, n);
tokens.remove(t);
t = n;
}
} else if (state == 1) {
// var : ?
if (isVariableInteger(t)) {
state = 2;
} else {
// array range
IntegerSequence range = new IntegerSequence.Range(start, null);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(range);
replaceSequence(tokens, varSequence, start, prev);
state = 0;
}
} else if (state == 2) {
// var:var ?
if (t != null && t.getSymbol() == Symbol.COLON) {
middle = prev;
state = 3;
} else {
// create for sequence with start and stop elements only
IntegerSequence numbers = new IntegerSequence.For(start, null, prev);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(numbers);
replaceSequence(tokens, varSequence, start, prev);
if (t != null)
t = t.previous;
state = 0;
}
} else if (state == 3) {
// var:var: ?
if (isVariableInteger(t)) {
// create 'for' sequence with three variables
IntegerSequence numbers = new IntegerSequence.For(start, middle, t);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(numbers);
t = replaceSequence(tokens, varSequence, start, t);
} else {
// array range with 2 elements
IntegerSequence numbers = new IntegerSequence.Range(start, middle);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(numbers);
replaceSequence(tokens, varSequence, start, prev);
}
state = 0;
}
if (last) {
break;
} else if (t.next == null) {
// handle the case where it is the last token in the sequence
last = true;
}
prev = t;
t = t.next;
}
}
/**
* Searches for a sequence of integers
*
* example:
* 1 2 3 4 6 7 -3
*/
protected void parseIntegerLists( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
if (t == null || t.next == null)
return;
int state = 0;
TokenList.Token start = null;
TokenList.Token prev = t;
boolean last = false;
while (true) {
if (state == 0) {
if (isVariableInteger(t)) {
start = t;
state = 1;
}
} else if (state == 1) {
// var ?
if (isVariableInteger(t)) { // see if its explicit number sequence
state = 2;
} else { // just scalar integer, skip
state = 0;
}
} else if (state == 2) {
// var var ....
if (!isVariableInteger(t)) {
// create explicit list sequence
IntegerSequence sequence = new IntegerSequence.Explicit(start, prev);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(sequence);
replaceSequence(tokens, varSequence, start, prev);
state = 0;
}
}
if (last) {
break;
} else if (t.next == null) {
// handle the case where it is the last token in the sequence
last = true;
}
prev = t;
t = t.next;
}
}
/**
* Looks for sequences of integer lists and combine them into one big sequence
*/
protected void parseCombineIntegerLists( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
if (t == null || t.next == null)
return;
int numFound = 0;
TokenList.Token start = null;
TokenList.Token end = null;
while (t != null) {
if (t.getType() == Type.VARIABLE && (isVariableInteger(t) ||
t.getVariable().getType() == VariableType.INTEGER_SEQUENCE)) {
if (numFound == 0) {
numFound = 1;
start = end = t;
} else {
numFound++;
end = t;
}
} else if (numFound > 1) {
IntegerSequence sequence = new IntegerSequence.Combined(start, end);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(sequence);
replaceSequence(tokens, varSequence, start, end);
numFound = 0;
} else {
numFound = 0;
}
t = t.next;
}
if (numFound > 1) {
IntegerSequence sequence = new IntegerSequence.Combined(start, end);
VariableIntegerSequence varSequence = functions.getManagerTemp().createIntegerSequence(sequence);
replaceSequence(tokens, varSequence, start, end);
}
}
private TokenList.Token replaceSequence( TokenList tokens, Variable target, TokenList.Token start, TokenList.Token end ) {
TokenList.Token tmp = new TokenList.Token(target);
tokens.insert(start.previous, tmp);
tokens.extractSubList(start, end);
return tmp;
}
/**
* Checks to see if the token is an integer scalar
*
* @return true if integer or false if not
*/
private static boolean isVariableInteger( TokenList.Token t ) {
if (t == null)
return false;
return t.getScalarType() == VariableScalar.Type.INTEGER;
}
/**
* Searches for brackets which are only used to construct new matrices by concatenating
* 1 or more matrices together
*/
protected void parseBracketCreateMatrix( TokenList tokens, Sequence sequence ) {
List left = new ArrayList<>();
TokenList.Token t = tokens.getFirst();
while (t != null) {
TokenList.Token next = t.next;
if (t.getSymbol() == Symbol.BRACKET_LEFT) {
left.add(t);
} else if (t.getSymbol() == Symbol.BRACKET_RIGHT) {
if (left.isEmpty())
throw new RuntimeException("No matching left bracket for right");
TokenList.Token start = left.remove(left.size() - 1);
// Compute everything inside the [ ], this will leave a
// series of variables and semi-colons hopefully
TokenList bracketLet = tokens.extractSubList(start.next, t.previous);
parseBlockNoParentheses(bracketLet, sequence, true);
MatrixConstructor constructor = constructMatrix(bracketLet);
// define the matrix op and inject into token list
Operation.Info info = Operation.matrixConstructor(constructor);
sequence.addOperation(info.op);
tokens.insert(start.previous, new TokenList.Token(info.output));
// remove the brackets
tokens.remove(start);
tokens.remove(t);
}
t = next;
}
if (!left.isEmpty())
throw new RuntimeException("Dangling [");
}
private MatrixConstructor constructMatrix( TokenList bracketLet ) {
// Go through the bracket and construct the matrix
MatrixConstructor constructor = new MatrixConstructor(functions.getManagerTemp());
TokenList.Token n = bracketLet.first;
while (n != null) {
if (n.getType() == Type.VARIABLE) {
constructor.addToRow(n.getVariable());
} else if (n.getType() == Type.SYMBOL) {
if (n.getSymbol() == Symbol.SEMICOLON) {
constructor.endRow();
}
} else {
throw new ParseError("Expected variable or symbol only");
}
n = n.next;
}
constructor.endRow();
return constructor;
}
/**
* Searches for cases where a minus sign means negative operator. That happens when there is a minus
* sign with a variable to its right and no variable to its left
*
* Example:
* a = - b * c
*/
protected void parseNegOp( TokenList tokens, Sequence sequence ) {
if (tokens.size == 0)
return;
TokenList.Token token = tokens.first;
while (token != null) {
TokenList.Token next = token.next;
escape:
if (token.getSymbol() == Symbol.MINUS) {
if (token.previous != null && token.previous.getType() != Type.SYMBOL)
break escape;
if (token.previous != null && token.previous.getType() == Type.SYMBOL &&
(token.previous.symbol == Symbol.TRANSPOSE))
break escape;
if (token.next == null || token.next.getType() == Type.SYMBOL)
break escape;
if (token.next.getType() != Type.VARIABLE)
throw new RuntimeException("Crap bug rethink this function");
// create the operation
Operation.Info info = Operation.neg(token.next.getVariable(), functions.getManagerTemp());
// add the operation to the sequence
sequence.addOperation(info.op);
// update the token list
TokenList.Token t = new TokenList.Token(info.output);
tokens.insert(token.next, t);
tokens.remove(token.next);
tokens.remove(token);
next = t;
}
token = next;
}
}
/**
* Parses operations where the input comes from variables to its left only. Hard coded to only look
* for transpose for now
*
* @param tokens List of all the tokens
* @param sequence List of operation sequence
*/
protected void parseOperationsL( TokenList tokens, Sequence sequence ) {
if (tokens.size == 0)
return;
TokenList.Token token = tokens.first;
if (token.getType() != Type.VARIABLE)
throw new ParseError("The first token in an equation needs to be a variable and not " + token);
while (token != null) {
if (token.getType() == Type.FUNCTION) {
throw new ParseError("Function encountered with no parentheses");
} else if (token.getType() == Type.SYMBOL && token.getSymbol() == Symbol.TRANSPOSE) {
if (token.previous.getType() == Type.VARIABLE)
token = insertTranspose(token.previous, tokens, sequence);
else
throw new ParseError("Expected variable before transpose");
}
token = token.next;
}
}
/**
* Parses operations where the input comes from variables to its left and right
*
* @param ops List of operations which should be parsed
* @param tokens List of all the tokens
* @param sequence List of operation sequence
*/
protected void parseOperationsLR( Symbol[] ops, TokenList tokens, Sequence sequence ) {
if (tokens.size == 0)
return;
TokenList.Token token = tokens.first;
if (token.getType() != Type.VARIABLE)
throw new ParseError("The first token in an equation needs to be a variable and not " + token);
boolean hasLeft = false;
while (token != null) {
if (token.getType() == Type.FUNCTION) {
throw new ParseError("Function encountered with no parentheses");
} else if (token.getType() == Type.VARIABLE) {
if (hasLeft) {
if (isTargetOp(token.previous, ops)) {
token = createOp(token.previous.previous, token.previous, token, tokens, sequence);
}
} else {
hasLeft = true;
}
} else {
if (token.previous.getType() == Type.SYMBOL) {
throw new ParseError("Two symbols next to each other. " + token.previous + " and " + token);
}
}
token = token.next;
}
}
/**
* Adds a new operation to the list from the operation and two variables. The inputs are removed
* from the token list and replaced by their output.
*/
protected TokenList.Token insertTranspose( TokenList.Token variable,
TokenList tokens, Sequence sequence ) {
Operation.Info info = functions.create('\'', variable.getVariable());
sequence.addOperation(info.op);
// replace the symbols with their output
TokenList.Token t = new TokenList.Token(info.output);
// remove the transpose symbol
tokens.remove(variable.next);
// replace the variable with its transposed version
tokens.replace(variable, t);
return t;
}
/**
* Adds a new operation to the list from the operation and two variables. The inputs are removed
* from the token list and replaced by their output.
*/
protected TokenList.Token createOp( TokenList.Token left, TokenList.Token op, TokenList.Token right,
TokenList tokens, Sequence sequence ) {
Operation.Info info = functions.create(op.symbol, left.getVariable(), right.getVariable());
sequence.addOperation(info.op);
// replace the symbols with their output
TokenList.Token t = new TokenList.Token(info.output);
tokens.remove(left);
tokens.remove(right);
tokens.replace(op, t);
return t;
}
/**
* Adds a new operation to the list from the operation and two variables. The inputs are removed
* from the token list and replaced by their output.
*/
protected TokenList.Token createFunction( TokenList.Token name, List inputs, TokenList tokens, Sequence sequence ) {
Operation.Info info;
if (inputs.size() == 1)
info = functions.create(name.getFunction().getName(), inputs.get(0).getVariable());
else {
List vars = new ArrayList<>();
for (int i = 0; i < inputs.size(); i++) {
vars.add(inputs.get(i).getVariable());
}
info = functions.create(name.getFunction().getName(), vars);
}
sequence.addOperation(info.op);
// replace the symbols with the function's output
TokenList.Token t = new TokenList.Token(info.output);
tokens.replace(name, t);
return t;
}
/**
* Looks up a variable given its name. If none is found then return null.
*/
public T lookupVariable( String token ) {
Variable result = variables.get(token);
return (T)result;
}
public Macro lookupMacro( String token ) {
return macros.get(token);
}
public DMatrixRMaj lookupDDRM( String token ) {
return ((VariableMatrix)variables.get(token)).matrix;
}
public FMatrixRMaj lookupFDRM( String token ) {
DMatrixRMaj d = ((VariableMatrix)variables.get(token)).matrix;
FMatrixRMaj f = new FMatrixRMaj(d.numRows, d.numCols);
ConvertMatrixData.convert(d, f);
return f;
}
public int lookupInteger( String token ) {
return ((VariableInteger)variables.get(token)).value;
}
public double lookupDouble( String token ) {
Variable v = variables.get(token);
if (v instanceof VariableMatrix) {
// if( ((VariableMatrix)v).matrix instanceof DMatrix ) {
DMatrix m = ((VariableMatrix)v).matrix;
if (m.getNumCols() == 1 && m.getNumRows() == 1) {
return m.get(0, 0);
} else {
throw new RuntimeException("Can only return 1x1 real matrices as doubles");
}
// } else if( ((VariableMatrix)v).matrix instanceof FMatrix) {
// FMatrix m = ((VariableMatrix) v).matrix;
// if (m.getNumCols() == 1 && m.getNumRows() == 1) {
// return m.get(0, 0);
// } else {
// throw new RuntimeException("Can only return 1x1 real matrices as doubles");
// }
// }
}
return ((VariableScalar)variables.get(token)).getDouble();
}
/**
* Parses the text string to extract tokens.
*/
protected TokenList extractTokens( String equation, ManagerTempVariables managerTemp ) {
// add a space to make sure everything is parsed when its done
equation += " ";
TokenList tokens = new TokenList();
int length = 0;
boolean again; // process the same character twice
TokenType type = TokenType.UNKNOWN;
for (int i = 0; i < equation.length(); i++) {
again = false;
char c = equation.charAt(i);
if (type == TokenType.WORD) {
if (isLetter(c)) {
storage[length++] = c;
} else {
// add the variable/function name to token list
String name = new String(storage, 0, length);
tokens.add(name);
type = TokenType.UNKNOWN;
again = true; // process unexpected character a second time
}
} else if (type == TokenType.INTEGER) { // Handle integer numbers. Until proven to be a float
if (c == '.') {
type = TokenType.FLOAT;
storage[length++] = c;
} else if (c == 'e' || c == 'E') {
type = TokenType.FLOAT_EXP;
storage[length++] = c;
} else if (Character.isDigit(c)) {
storage[length++] = c;
} else if (isSymbol(c) || Character.isWhitespace(c)) {
int value = Integer.parseInt(new String(storage, 0, length));
tokens.add(managerTemp.createInteger(value));
type = TokenType.UNKNOWN;
again = true; // process unexpected character a second time
} else {
throw new ParseError("Unexpected character at the end of an integer " + c);
}
} else if (type == TokenType.FLOAT) { // Handle floating point numbers
if (c == '.') {
throw new ParseError("Unexpected '.' in a float");
} else if (c == 'e' || c == 'E') {
storage[length++] = c;
type = TokenType.FLOAT_EXP;
} else if (Character.isDigit(c)) {
storage[length++] = c;
} else if (isSymbol(c) || Character.isWhitespace(c)) {
double value = Double.parseDouble(new String(storage, 0, length));
tokens.add(managerTemp.createDouble(value));
type = TokenType.UNKNOWN;
again = true; // process unexpected character a second time
} else {
throw new ParseError("Unexpected character at the end of an float " + c);
}
} else if (type == TokenType.FLOAT_EXP) { // Handle floating point numbers in exponential format
boolean end = false;
if (c == '-') {
char p = storage[length - 1];
if (p == 'e' || p == 'E') {
storage[length++] = c;
} else {
end = true;
}
} else if (Character.isDigit(c)) {
storage[length++] = c;
} else if (isSymbol(c) || Character.isWhitespace(c)) {
end = true;
} else {
throw new ParseError("Unexpected character at the end of an float " + c);
}
if (end) {
double value = Double.parseDouble(new String(storage, 0, length));
tokens.add(managerTemp.createDouble(value));
type = TokenType.UNKNOWN;
again = true; // process the current character again since it was unexpected
}
} else {
if (isSymbol(c)) {
boolean special = false;
if (c == '-') {
// need to handle minus symbols carefully since it can be part of a number of a minus operator
// if next to a number it should be negative sign, unless there is no operator to its left
// then its a minus sign.
if (i + 1 < equation.length() && Character.isDigit(equation.charAt(i + 1)) &&
(tokens.last == null || isOperatorLR(tokens.last.getSymbol()))) {
type = TokenType.INTEGER;
storage[0] = c;
length = 1;
special = true;
}
}
if (!special) {
TokenList.Token t = tokens.add(Symbol.lookup(c));
if (t.previous != null && t.previous.getType() == Type.SYMBOL) {
// there should only be two symbols in a row if its an element-wise operation
if (t.previous.getSymbol() == Symbol.PERIOD) {
tokens.remove(t.previous);
tokens.remove(t);
tokens.add(Symbol.lookupElementWise(c));
}
}
}
} else if (Character.isWhitespace(c)) {
continue;// ignore white space
} else {
// start adding to the word
if (Character.isDigit(c)) {
type = TokenType.INTEGER;
} else {
type = TokenType.WORD;
}
storage[0] = c;
length = 1;
}
}
// see if it should process the same character again
if (again)
i--;
}
return tokens;
}
/**
* Search for WORDS in the token list. Then see if the WORD is a function or a variable. If so replace
* the work with the function/variable
*/
void insertFunctionsAndVariables( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
while (t != null) {
if (t.getType() == Type.WORD) {
Variable v = lookupVariable(t.word);
if (v != null) {
t.variable = v;
t.word = null;
} else if (functions.isFunctionName(t.word)) {
t.function = new Function(t.word);
t.word = null;
}
}
t = t.next;
}
}
/**
* Checks to see if a WORD matches the name of a macro. if it does it applies the macro at that location
*/
void insertMacros( TokenList tokens ) {
TokenList.Token t = tokens.getFirst();
while (t != null) {
if (t.getType() == Type.WORD) {
Macro v = lookupMacro(t.word);
if (v != null) {
TokenList.Token before = t.previous;
List inputs = new ArrayList<>();
t = parseMacroInput(inputs, t.next);
TokenList sniplet = v.execute(inputs);
tokens.extractSubList(before.next, t);
tokens.insertAfter(before, sniplet);
t = sniplet.last;
}
}
t = t.next;
}
}
public SimpleMatrix lookupSimple( String token ) {
return SimpleMatrix.wrap(lookupDDRM(token));
}
protected enum TokenType {
WORD,
INTEGER,
FLOAT,
FLOAT_EXP,
UNKNOWN
}
/**
* Checks to see if the token is in the list of allowed character operations. Used to apply order of operations
*
* @param token Token being checked
* @param ops List of allowed character operations
* @return true for it being in the list and false for it not being in the list
*/
protected static boolean isTargetOp( TokenList.Token token, Symbol[] ops ) {
Symbol c = token.symbol;
for (int i = 0; i < ops.length; i++) {
if (c == ops[i])
return true;
}
return false;
}
protected static boolean isSymbol( char c ) {
return c == '*' || c == '/' || c == '+' || c == '-' || c == '(' || c == ')' || c == '[' || c == ']' ||
c == '=' || c == '\'' || c == '.' || c == ',' || c == ':' || c == ';' || c == '\\' || c == '^';
}
/**
* Operators which affect the variables to its left and right
*/
protected static boolean isOperatorLR( Symbol s ) {
if (s == null)
return false;
switch (s) {
case ELEMENT_DIVIDE:
case ELEMENT_TIMES:
case ELEMENT_POWER:
case RDIVIDE:
case LDIVIDE:
case TIMES:
case POWER:
case PLUS:
case MINUS:
case ASSIGN:
return true;
default:
return false;
}
}
/**
* Returns true if the character is a valid letter for use in a variable name
*/
protected static boolean isLetter( char c ) {
return !(isSymbol(c) || Character.isWhitespace(c));
}
/**
* Returns true if the specified name is NOT allowed. It isn't allowed if it matches a built in operator
* or if it contains a restricted character.
*/
protected boolean isReserved( String name ) {
if (functions.isFunctionName(name))
return true;
for (int i = 0; i < name.length(); i++) {
if (!isLetter(name.charAt(i)))
return true;
}
return false;
}
/**
* Compiles and performs the provided equation.
*
* @param equation String in simple equation format
*/
public Equation process( String equation ) {
compile(equation).perform();
return this;
}
/**
* Compiles and performs the provided equation.
*
* @param equation String in simple equation format
*/
public Equation process( String equation, boolean debug ) {
compile(equation, true, debug).perform();
return this;
}
/**
* Prints the results of the equation to standard out. Useful for debugging
*/
public void print( String equation ) {
// first assume it's just a variable
Variable v = lookupVariable(equation);
if (v == null) {
Sequence sequence = compile(equation, false, false);
sequence.perform();
v = sequence.output;
}
if (v instanceof VariableMatrix) {
((VariableMatrix)v).matrix.print();
} else if (v instanceof VariableScalar) {
System.out.println("Scalar = " + ((VariableScalar)v).getDouble());
} else {
System.out.println("Add support for " + v.getClass().getSimpleName());
}
}
/**
* Returns the functions manager
*/
public ManagerFunctions getFunctions() {
return functions;
}
}