/* * 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); } } }