/*
* Copyright 2013 serso aka se.solovyev
*
* 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.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Contact details
*
* Email: se.solovyev@gmail.com
* Site: http://se.solovyev.org
*/
package org.solovyev.android.calculator.math;
import android.support.annotation.NonNull;
import jscl.JsclMathEngine;
import jscl.NumeralBase;
import jscl.math.function.Constants;
import org.solovyev.android.Check;
import org.solovyev.android.calculator.App;
import org.solovyev.android.calculator.Engine;
import org.solovyev.android.calculator.ParseException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public enum MathType {
numeral_base(50, true, false, MathGroupType.number, new ArrayList<String>()) {
{
for (NumeralBase numeralBase : NumeralBase.values()) {
tokens.add(numeralBase.getJsclPrefix());
}
}
},
dot(200, true, true, MathGroupType.number, ".") {
@Override
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != digit;
}
},
grouping_separator(250, false, false, MathGroupType.number, "'", " ") {
@Override
public int processToJscl(@Nonnull StringBuilder result, int i, @Nonnull String match) throws ParseException {
return i;
}
},
power_10(300, false, false, MathGroupType.number, "E"),
postfix_function(400, false, true, MathGroupType.function) {
@Nonnull
@Override
public List<String> getTokens(@NonNull Engine engine) {
return engine.getPostfixFunctionsRegistry().getNames();
}
},
unary_operation(500, false, false, MathGroupType.operation, "−", "-", "=") {
@Nullable
@Override
protected String getSubstituteToJscl(@Nonnull String match) {
if (match.equals("−")) {
return "-";
} else {
return null;
}
}
@Nullable
@Override
protected String getSubstituteFromJscl(@Nonnull String match) {
if (match.equals("-")) {
return "−";
} else {
return null;
}
}
},
binary_operation(600, false, false, MathGroupType.operation, "−", "-", "+", "*", "×", "∙", "/", "^") {
@Nullable
@Override
protected String getSubstituteFromJscl(@Nonnull String match) {
if (match.equals("-")) {
return "−";
} else {
return null;
}
}
@Override
protected String getSubstituteToJscl(@Nonnull String match) {
switch (match) {
case "×":
case "∙":
return "*";
case "−":
return "-";
default:
return null;
}
}
},
open_group_symbol(800, true, false, MathGroupType.other, "(", "[", "{") {
@Override
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != function && mathTypeBefore != operator;
}
@Override
protected String getSubstituteToJscl(@Nonnull String match) {
return "(";
}
},
close_group_symbol(900, false, true, MathGroupType.other, ")", "]", "}") {
@Override
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
return false;
}
@Override
protected String getSubstituteToJscl(@Nonnull String match) {
return ")";
}
},
function(1000, true, true, MathGroupType.function) {
@Nonnull
@Override
public List<String> getTokens(@NonNull Engine engine) {
return engine.getFunctionsRegistry().getNames();
}
@Nonnull
@Override
public List<String> getTokens() {
Check.shouldNotHappen();
return super.getTokens();
}
},
operator(1050, true, true, MathGroupType.function) {
@Nonnull
@Override
public List<String> getTokens(@NonNull Engine engine) {
return engine.getOperatorsRegistry().getNames();
}
@Nonnull
@Override
public List<String> getTokens() {
Check.shouldNotHappen();
return super.getTokens();
}
},
constant(1100, true, true, MathGroupType.other) {
@Nonnull
@Override
public List<String> getTokens(@NonNull Engine engine) {
return engine.getVariablesRegistry().getNames();
}
@Nonnull
@Override
public List<String> getTokens() {
Check.shouldNotHappen();
return super.getTokens();
}
@Override
protected String getSubstituteFromJscl(@Nonnull String match) {
return Constants.INF_2.getName().equals(match) ? MathType.INFINITY : super.getSubstituteFromJscl(match);
}
},
digit(1125, true, true, MathGroupType.number, new ArrayList<String>()) {
{
for (Character character : NumeralBase.hex.getAcceptableCharacters()) {
tokens.add(character.toString());
}
}
@Override
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
return super.isNeedMultiplicationSignBefore(mathTypeBefore) && mathTypeBefore != digit && mathTypeBefore != dot /*&& mathTypeBefore != numeral_base*/;
}
},
comma(1150, false, false, MathGroupType.other, ","),
text(1200, false, false, MathGroupType.other) {
@Override
public int processToJscl(@Nonnull StringBuilder result, int i, @Nonnull String match) {
if (match.length() > 0) {
result.append(match.charAt(0));
}
return i;
}
};
public static final List<String> groupSymbols = Arrays.asList("()", "[]", "{}");
public final static Character EXPONENT = 'E';
public static final String E = "e";
public static final String C = "c";
public final static String NAN = "NaN";
public final static String INFINITY = "∞";
public final static String INFINITY_JSCL = "Infinity";
private static List<MathType> mathTypesByPriority;
@Nonnull
protected final List<String> tokens;
@Nonnull
private final Integer priority;
private final boolean needMultiplicationSignBefore;
private final boolean needMultiplicationSignAfter;
@Nonnull
private final MathGroupType groupType;
MathType(@Nonnull Integer priority,
boolean needMultiplicationSignBefore,
boolean needMultiplicationSignAfter,
@Nonnull MathGroupType groupType,
@Nonnull String... tokens) {
this(priority, needMultiplicationSignBefore, needMultiplicationSignAfter, groupType, Arrays.asList(tokens));
}
MathType(@Nonnull Integer priority,
boolean needMultiplicationSignBefore,
boolean needMultiplicationSignAfter,
@Nonnull MathGroupType groupType,
@Nonnull List<String> tokens) {
this.priority = priority;
this.needMultiplicationSignBefore = needMultiplicationSignBefore;
this.needMultiplicationSignAfter = needMultiplicationSignAfter;
this.groupType = groupType;
this.tokens = tokens;
}
/**
* Method determines mathematical entity type for text substring starting from ith index
*
* @param text analyzed text
* @param i index which points to start of substring
* @param hexMode true if current mode if HEX
* @param engine math engine
* @return math entity type of substring starting from ith index of specified text
*/
@Nonnull
public static Result getType(@Nonnull String text, int i, boolean hexMode, @Nonnull Engine engine) {
return getType(text, i, hexMode, new Result(), engine);
}
@Nonnull
public static Result getType(@Nonnull String text, int i, boolean hexMode, @Nonnull Result result, @Nonnull Engine engine) {
if (i < 0) {
throw new IllegalArgumentException("I must be more or equals to 0.");
} else if (i >= text.length() && i != 0) {
throw new IllegalArgumentException("I must be less than size of text.");
} else if (i == 0 && text.length() == 0) {
return result.set(MathType.text, text);
}
final List<MathType> mathTypes = getMathTypesByPriority();
for (int j = 0; j < mathTypes.size(); j++) {
final MathType mathType = mathTypes.get(j);
final String s = App.find(mathType.getTokens(engine), text, i);
if (s == null) {
continue;
}
if (s.length() > 1) {
if (mathType == function) {
final int nextToken = i + s.length();
if (nextToken < text.length()) {
// function must have an open group symbol after its name
if (MathType.open_group_symbol.getTokens().contains(text.substring(nextToken, nextToken + 1))) {
return result.set(function, s);
}
} else if (nextToken == text.length()) {
// or its name should finish the expression
return result.set(function, s);
}
continue;
}
return result.set(mathType, s);
}
if (hexMode || JsclMathEngine.getInstance().getNumeralBase() == NumeralBase.hex) {
final Character ch = s.charAt(0);
if (NumeralBase.hex.getAcceptableCharacters().contains(ch)) {
return result.set(MathType.digit, s);
}
}
if (mathType == MathType.grouping_separator) {
if (i + 1 < text.length() &&
MathType.digit.getTokens().contains(text.substring(i + 1, i + 2)) &&
i - 1 >= 0 && MathType.digit.getTokens().contains(text.substring(i - 1, i))) {
return result.set(mathType, s);
}
continue;
}
return result.set(mathType, s);
}
return result.set(MathType.text, text.substring(i));
}
@Nonnull
private static List<MathType> getMathTypesByPriority() {
if (mathTypesByPriority == null) {
final List<MathType> result = Arrays.asList(MathType.values());
java.util.Collections.sort(result, new Comparator<MathType>() {
@Override
public int compare(MathType l, MathType r) {
return l.priority.compareTo(r.priority);
}
});
mathTypesByPriority = result;
}
return mathTypesByPriority;
}
@Nonnull
public MathGroupType getGroupType() {
return groupType;
}
@Nonnull
public List<String> getTokens(@Nonnull Engine engine) {
return getTokens();
}
@Nonnull
public List<String> getTokens() {
return tokens;
}
private boolean isNeedMultiplicationSignAfter() {
return needMultiplicationSignAfter;
}
public boolean isNeedMultiplicationSignBefore(@Nonnull MathType mathTypeBefore) {
return needMultiplicationSignBefore && mathTypeBefore.isNeedMultiplicationSignAfter();
}
public int processToJscl(@Nonnull StringBuilder result, int i, @Nonnull String match) throws ParseException {
final String substitute = getSubstituteToJscl(match);
result.append(substitute == null ? match : substitute);
return returnI(i, match);
}
protected int returnI(int i, @Nonnull String match) {
if (match.length() > 1) {
return i + match.length() - 1;
} else {
return i;
}
}
@Nullable
protected String getSubstituteFromJscl(@Nonnull String match) {
return null;
}
@Nullable
protected String getSubstituteToJscl(@Nonnull String match) {
return null;
}
public static boolean isOpenGroupSymbol(char c) {
return c == '(' || c == '[' || c == '{';
}
public static boolean isCloseGroupSymbol(char c) {
return c == ')' || c == ']' || c == '}';
}
public enum MathGroupType {
function,
number,
operation,
other
}
public static class Result {
@Nonnull
public MathType type;
@Nonnull
public String match;
public Result(@Nonnull MathType type, @Nonnull String match) {
this.type = type;
this.match = match;
}
@Nonnull
Result set(@Nonnull MathType type, @Nonnull String match) {
this.type = type;
this.match = match;
return this;
}
public Result() {
this(MathType.text, "");
}
public int processToJscl(@Nonnull StringBuilder result, int i) throws ParseException {
return type.processToJscl(result, i, match);
}
}
public static class Results {
@Nonnull
private final List<Result> list = new ArrayList<>();
@Nonnull
public Result obtain() {
if (list.isEmpty()) {
return new Result();
}
return list.remove(list.size() - 1);
}
public void release(@Nullable Result result) {
if (result == null) {
return;
}
list.add(result);
}
}
}