I have been working on a simple calculator project in Java since November 2014 as hobby. Now I want to add complex number support and Matrices. I also wants to add support for symbolic computation in future. For this purpose I wrote a Literal class which is meant for to hold all types like Matrix class objects and Complex class objects in one class and Variables in future. Currently I am dealing with expressions like this
sin(2+3i*[[2+3,4][9,average(3,5,9)]]*9.8*PI)
So far I have written tokenizer that tokenize this type of expressions in to tokens of type functions, expression brackets, functions brackets, matrices, constants and numbers.
To verify syntax and parse Complex Numbers and Matrices from token I have written LiteralParser class which parse and return Literal Object in case of success and if fails throws NumberFormatException. I could use regular expression but I think that will be slow.
I have also tried Apache ComplexFormat and RealMatrixFormat. Apache ComplexFormat does a weird thing that it treats "2i" as "2+0i" and Apache RealMatrixFormat does not support expressions on indices and it is only for real numbers.
Now I want that my code should be less nested by utilizing some functions and some performance enhancements. if is there any already existing library that does this job then let me know.
LiteralParser.java
package com.kmstudios.evaluator;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class LiteralParser
{
private final MatrixFormat format;
private final Method evaluator;
public LiteralParser(MatrixFormat format, Method m)
{
this.format = format;
this.evaluator = m;
}
public void parse(String literal) throws NumberFormatException, InvocationTargetException, IllegalAccessException
{
if (literal == null || literal.isEmpty()) throw new NumberFormatException("cannot parse empty literal");
//If format is null then its mean that there should be no matrix in Literal
if (this.format != null && literal.charAt(0) == this.format.getPrefix()) /*return*/ parseMatrix(literal);
else /*return*/ parseNumber(literal);
}
private void parseMatrix(String mat) throws NumberFormatException, InvocationTargetException, IllegalAccessException
{
/* Input: [[1,2][3,4][5,6]]
* Intended output:
* 1 2
* 3 4
* 5 6
*
* Criteria:
* 1: Nested matrices are not allowed
* 2: Checks for any syntax error
* 3: Supports expression on indices e.g [[2+3*9-6+pi*average(1,2,3),25][23,96]]
* 4: Solves expressions by invoking evaluator method which should be static and
* accepts String as argument and returns answer as String
* 5: Check for any columns balance e.g [[1][1,2]] all row must have
* same numbers of columns
* 6: Throws Syntax error when matrix is empty [[]] or []*/
int length = mat.length() - 1; //not checking for last index it should be postfix
final char prefix = this.format.getPrefix();
final char postfix = this.format.getPostfix();
final String ERROR = "invalid syntax of matrix " + mat;
if (mat.charAt(0) != prefix || mat.charAt(length) != postfix)throw new NumberFormatException(ERROR);
int i = 1;
final char rowPrefix = this.format.getRowPrefix();
final char rowPostfix = this.format.getRowPostfix();
final char columnSeparator = this.format.getColumnSeparator();
int rows = 0;
int columns = 0;
int preRowColumns = 0;
int start = -1;
int depth = 0;
boolean isOpen = false;
char curr, prev = prefix;
ArrayList<String> indices = new ArrayList<>();//Container for individual element
for (; i < length ;i++)
{
curr = mat.charAt(i);
if (curr == rowPrefix)
{
if (isOpen)throw new NumberFormatException(ERROR);//Row already open [[2,[3]]
/*
* Distance between Matrix Prefix and row prefix should be 1
* and Distance between row prefix and row postfix should be 1*/
else if (rows != 0 && prev != rowPostfix)throw new NumberFormatException(ERROR);
else if (rows == 0 && prev != prefix)throw new NumberFormatException(ERROR);
start = i + 1; //do not include row prefix itself
isOpen = true;
}
else if (curr == rowPostfix)
{
//[[,1,,32]]
if (!isOpen || prev == columnSeparator || prev == rowPrefix)throw new NumberFormatException(ERROR);
columns++;
if (columns != preRowColumns && preRowColumns != 0)throw new NumberFormatException(ERROR);
indices.add(mat.substring(start, i));
start = -1;
isOpen = false;
preRowColumns = columns;
columns = 0;
rows++;
}
else if (curr == columnSeparator)
{
if (depth != 0)continue;
if (prev == columnSeparator || prev == prefix || prev == postfix || prev == rowPrefix)throw new NumberFormatException(ERROR);
columns++;
indices.add(mat.substring(start, i));
start = i + 1; //do not include column separator itself
}
else if (curr == prefix || curr == postfix)throw new NumberFormatException(ERROR);//Nested matrix detected
else if (curr == '(')depth++; //any separator in expression brackets do not count [[mean(1,2,3,4),2][[3,5]]
else if (curr == ')')depth--; //TODO use expression brackets
prev = curr;
}
if(isOpen || rows == 0 || preRowColumns == 0)throw new NumberFormatException(ERROR);
for (i = 0, length = indices.size(); i < length; i++)
{
//Evaluator method is static and taking String as argument and returning String
indices.set(i, (String)evaluator.invoke(null, indices.get(i)));//invoke method to solve expression at any index e.g [[2+3,3*9][4,5]]
}
/*
* Currently I do not have any Matrix class to represent result*/
}
private void parseNumber(String number) throws NumberFormatException
{
/* Input: 2.3e+2+2.3e-3i, 2.3e-3i+2.3e+2, i2.3e-3+2.3e+2, 2.3e+2+i2.3e-3
* Intended output:
* (2.3e+2, 2.3e-3)
*
*
* 1: Syntax Checking
* 2: Supports any valid number*/
//TODO simplify code and improve performance
int length = number.length();
int start = -1, indexOfI = -1, indexOfPoint = -1, indexOfE = -1, i = 0;
final char imSign = 'i';
char curr, prev = 0;
boolean startWithSign = false, useful = false;
String real = null, imag = null;
final String ERROR = "syntax error in " + number;
for (; i < length; i++)
{
curr = number.charAt(i);
if (curr >= '0' && curr <= '9')
{
if (start == -1) start = i;
useful = true;
}
else if (curr == '.')
{
//Contains point for two times or more or contain point after E
if (indexOfPoint != -1 || indexOfE != -1) throw new NumberFormatException(ERROR);
else indexOfPoint = i;
if (start == -1)
{
start = i;
useful = false; //if literal only contains "." or "-." or "-.i" etc
}
else if (prev == imSign || prev == '+' || prev == '-') useful = false;
}
else if (curr == 'e' || curr == 'E')
{
if (start == -1 || indexOfE != -1) throw new NumberFormatException(ERROR);
else indexOfE = i;
}
else if (curr == imSign)
{
if (indexOfI != -1 || prev == 'e' || prev == 'E') throw new NumberFormatException(ERROR);
else indexOfI = i;
if (start == -1) start = i;
else if (prev != '.') useful = true;
}
else if (curr == '+' || curr == '-')
{
if (prev == '+' || prev == '-') throw new NumberFormatException(ERROR);
else if (start == -1)
{
startWithSign = true;
start = i;
}
else if (!useful) throw new NumberFormatException(ERROR);
else if ((indexOfI != -1) && !((indexOfI == i - 1) || (startWithSign && indexOfI == start + 1) || (!startWithSign && indexOfI == start)))throw new NumberFormatException(ERROR);
else if (!(prev == 'e' || prev == 'E'))
{
if (imag == null && indexOfI != -1) imag = number.substring(start, i);
else if (real == null && indexOfI == -1) real = number.substring(start, i);
else throw new NumberFormatException(ERROR);
start = i;
indexOfI = -1;
indexOfE = -1;
indexOfPoint = -1;
startWithSign = true;
useful = false;
}
}
else throw new NumberFormatException(ERROR);
prev = curr;
}
if (start != -1)
{
if (prev == '+' || prev == '-' || prev == 'e' || prev == 'E') throw new NumberFormatException(ERROR);
else if (!useful) throw new NumberFormatException(ERROR);
else if ((indexOfI != -1) && !((indexOfI == i - 1) || (startWithSign && indexOfI == start + 1) || (!startWithSign && indexOfI == start)))throw new NumberFormatException(ERROR);
else if (imag == null && indexOfI != -1) imag = number.substring(start);
else if (real == null && indexOfI == -1) real = number.substring(start);
else throw new NumberFormatException(ERROR);
}
else throw new NumberFormatException(ERROR);
if (real == null) real = "0";
curr = imag != null ? imag.charAt(0) : 0;
if (imag == null) imag = "0";
else if ((imag.length() == 1) || ((imag.length() == 2) && (curr == '+' || curr == '-')))imag = imag.replace(new String(new char[]{imSign}), "1");
else imag = imag.replace(new String(new char[]{imSign}), "");
//return new Literal(new Complex(Double.parseDouble(real), Double.parseDouble(imag)));
}
}
MatrixFormat.java
package com.kmstudios.evaluator;
public class MatrixFormat
{
private final char[] format;
public MatrixFormat(char prefix, char postfix, char rowPrefix, char rowPostfix, char columnSeparator)
{
format = new char[5];
if ((prefix == '[' && postfix == ']') || (prefix == '{' && postfix == '}') || (prefix == '<' && postfix == '>') || (prefix == '(' && postfix == ')'))
{
this.format[0] = prefix;
this.format[1] = postfix;
} else throw new IllegalArgumentException("cannot set matrix brackets of type " + prefix + postfix);
if ((rowPrefix == '[' && rowPostfix == ']') || (rowPrefix == '{' && rowPostfix == '}') || (rowPrefix == '<' && rowPostfix == '>') || (rowPrefix == '(' && rowPostfix == ')'))
{
this.format[2] = rowPrefix;
this.format[3] = rowPostfix;
} else throw new IllegalArgumentException("cannot set matrix row brackets of type " + rowPrefix + rowPostfix);
if (columnSeparator == ',' || columnSeparator == ';' || columnSeparator == ':') this.format[4] = columnSeparator;
else throw new IllegalArgumentException("cannot set matrix column separator of type: " + columnSeparator);
}
public char getPrefix()
{
return this.format[0];
}
public char getPostfix()
{
return this.format[1];
}
public char getRowPrefix()
{
return this.format[2];
}
public char getRowPostfix()
{
return this.format[3];
}
public char getColumnSeparator()
{
return this.format[4];
}
}