postfix functions
This commit is contained in:
parent
633aaf877d
commit
41ce099dd2
@ -25,19 +25,22 @@ public class CalculatorModel {
|
||||
|
||||
private int NUMBER_OF_FRACTION_DIGITS = 5;
|
||||
|
||||
@NotNull
|
||||
public Preprocessor preprocessor = new ToJsclPreprocessor();
|
||||
|
||||
public CalculatorModel() throws EvalError {
|
||||
interpreter = new Interpreter();
|
||||
|
||||
interpreter.eval(Preprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
|
||||
interpreter.eval(ToJsclPreprocessor.wrap(JsclOperation.importCommands, "/jscl/editorengine/commands"));
|
||||
}
|
||||
|
||||
public String evaluate(@NotNull JsclOperation operation, @NotNull String expression) throws EvalError, ParseException {
|
||||
|
||||
final String preprocessedExpression = Preprocessor.process(expression);
|
||||
final String preprocessedExpression = preprocessor.process(expression);
|
||||
|
||||
//Log.d(CalculatorModel.class.getName(), "Preprocessed expression: " + preprocessedExpression);
|
||||
|
||||
Object evaluationObject = interpreter.eval(Preprocessor.wrap(operation, preprocessedExpression));
|
||||
Object evaluationObject = interpreter.eval(ToJsclPreprocessor.wrap(operation, preprocessedExpression));
|
||||
String result = String.valueOf(evaluationObject).trim();
|
||||
|
||||
try {
|
||||
|
@ -1,125 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.Functions;
|
||||
import org.solovyev.android.calculator.math.MathEntityType;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
|
||||
public class Preprocessor {
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/26/11
|
||||
* Time: 12:12 PM
|
||||
*/
|
||||
public interface Preprocessor {
|
||||
|
||||
@NotNull
|
||||
public static String process(@NotNull String s) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
final StartWithFinder startsWithFinder = new StartWithFinder(s);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
checkMultiplicationSignBeforeFunction(sb, s, i);
|
||||
|
||||
if (MathEntityType.openGroupSymbols.contains(ch)) {
|
||||
sb.append('(');
|
||||
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
|
||||
sb.append(')');
|
||||
} else if (ch == 'π') {
|
||||
sb.append("pi");
|
||||
} else if (ch == '×' || ch == '∙') {
|
||||
sb.append("*");
|
||||
} else {
|
||||
startsWithFinder.setI(i);
|
||||
final String function = CollectionsUtils.get(MathEntityType.functions, startsWithFinder);
|
||||
if (function != null) {
|
||||
sb.append(toJsclFunction(function));
|
||||
i += function.length() - 1;
|
||||
} else if (ch == 'e') {
|
||||
sb.append("exp(1)");
|
||||
} else if (ch == 'i') {
|
||||
sb.append("sqrt(-1)");
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String toJsclFunction(@NotNull String function) {
|
||||
final String result;
|
||||
|
||||
if (function.equals(Functions.LN)) {
|
||||
result = Functions.LOG;
|
||||
} else if (function.equals(Functions.SQRT_SIGN)) {
|
||||
result = Functions.SQRT;
|
||||
} else {
|
||||
result = function;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class StartWithFinder implements Finder<String> {
|
||||
|
||||
private int i;
|
||||
|
||||
@NotNull
|
||||
private final String targetString;
|
||||
|
||||
private StartWithFinder(@NotNull String targetString) {
|
||||
this.targetString = targetString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFound(@Nullable String s) {
|
||||
return targetString.startsWith(s, i);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
|
||||
if (i > 0) {
|
||||
// get character before function
|
||||
char chBefore = s.charAt(i - 1);
|
||||
char ch = s.charAt(i);
|
||||
|
||||
final MathEntityType mathTypeBefore = MathEntityType.getType(String.valueOf(chBefore));
|
||||
final MathEntityType mathType = MathEntityType.getType(String.valueOf(ch));
|
||||
|
||||
if (mathTypeBefore != MathEntityType.binary_operation &&
|
||||
mathTypeBefore != MathEntityType.unary_operation &&
|
||||
mathTypeBefore != MathEntityType.function &&
|
||||
!MathEntityType.openGroupSymbols.contains(chBefore)) {
|
||||
|
||||
if (mathType == MathEntityType.constant) {
|
||||
sb.append("*");
|
||||
} else if (MathEntityType.openGroupSymbols.contains(ch) && mathTypeBefore != null) {
|
||||
sb.append("*");
|
||||
} else if (mathType == MathEntityType.digit && mathTypeBefore != MathEntityType.digit && mathTypeBefore != MathEntityType.dot) {
|
||||
sb.append("*");
|
||||
} else {
|
||||
for (String function : MathEntityType.functions) {
|
||||
if (s.startsWith(function, i)) {
|
||||
sb.append("*");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String wrap(@NotNull JsclOperation operation, @NotNull String s) {
|
||||
return operation.name() + "(\"" + s + "\");";
|
||||
}
|
||||
String process(@NotNull String s);
|
||||
}
|
||||
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2011. Created by serso aka se.solovyev.
|
||||
* For more information, please, contact se.solovyev@gmail.com
|
||||
*/
|
||||
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.solovyev.android.calculator.math.Functions;
|
||||
import org.solovyev.android.calculator.math.MathEntityType;
|
||||
import org.solovyev.common.utils.CollectionsUtils;
|
||||
import org.solovyev.common.utils.FilterType;
|
||||
import org.solovyev.common.utils.Finder;
|
||||
|
||||
public class ToJsclPreprocessor implements Preprocessor {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String process(@NotNull String s) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
if ( MathEntityType.getType(ch) == MathEntityType.postfix_function ) {
|
||||
int start = getPostfixFunctionStart(s, i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
final StartsWithFinder startsWithFinder = new StartsWithFinder(s);
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char ch = s.charAt(i);
|
||||
|
||||
checkMultiplicationSignBeforeFunction(sb, s, i);
|
||||
|
||||
if (MathEntityType.openGroupSymbols.contains(ch)) {
|
||||
sb.append('(');
|
||||
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
|
||||
sb.append(')');
|
||||
} else if (ch == 'π') {
|
||||
sb.append("pi");
|
||||
} else if (ch == '×' || ch == '∙') {
|
||||
sb.append("*");
|
||||
} else {
|
||||
startsWithFinder.setI(i);
|
||||
final String function = CollectionsUtils.get(MathEntityType.prefixFunctions, startsWithFinder);
|
||||
if (function != null) {
|
||||
sb.append(toJsclFunction(function));
|
||||
i += function.length() - 1;
|
||||
} else if (ch == 'e') {
|
||||
sb.append("exp(1)");
|
||||
} else if (ch == 'i') {
|
||||
sb.append("sqrt(-1)");
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public int getPostfixFunctionStart(@NotNull String s, int position) {
|
||||
assert s.length() > position;
|
||||
|
||||
int numberOfOpenGroups = 0;
|
||||
int result = position;
|
||||
for ( ; result >= 0; result-- ) {
|
||||
char ch = s.charAt(result);
|
||||
|
||||
final MathEntityType mathEntityType = MathEntityType.getType(ch);
|
||||
|
||||
if ( mathEntityType != null ) {
|
||||
if ( CollectionsUtils.contains(mathEntityType, MathEntityType.digit, MathEntityType.dot) ) {
|
||||
// continue
|
||||
} else if (MathEntityType.closeGroupSymbols.contains(ch)) {
|
||||
numberOfOpenGroups++;
|
||||
} else if (MathEntityType.openGroupSymbols.contains(ch)) {
|
||||
numberOfOpenGroups--;
|
||||
} else {
|
||||
if (stop(s, numberOfOpenGroups, result)) break;
|
||||
}
|
||||
} else {
|
||||
if (stop(s, numberOfOpenGroups, result)) break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean stop(String s, int numberOfOpenGroups, int i) {
|
||||
if ( numberOfOpenGroups == 0 ) {
|
||||
if (i > 0) {
|
||||
final EndsWithFinder endsWithFinder = new EndsWithFinder(s);
|
||||
endsWithFinder.setI(i+1);
|
||||
if ( !CollectionsUtils.contains(MathEntityType.prefixFunctions, FilterType.included, endsWithFinder) ) {
|
||||
MathEntityType type = MathEntityType.getType(s.charAt(i));
|
||||
if (type != null && type != MathEntityType.constant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String toJsclFunction(@NotNull String function) {
|
||||
final String result;
|
||||
|
||||
if (function.equals(Functions.LN)) {
|
||||
result = Functions.LOG;
|
||||
} else if (function.equals(Functions.SQRT_SIGN)) {
|
||||
result = Functions.SQRT;
|
||||
} else {
|
||||
result = function;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class StartsWithFinder implements Finder<String> {
|
||||
|
||||
private int i;
|
||||
|
||||
@NotNull
|
||||
private final String targetString;
|
||||
|
||||
private StartsWithFinder(@NotNull String targetString) {
|
||||
this.targetString = targetString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFound(@Nullable String s) {
|
||||
return targetString.startsWith(s, i);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndsWithFinder implements Finder<String> {
|
||||
|
||||
private int i;
|
||||
|
||||
@NotNull
|
||||
private final String targetString;
|
||||
|
||||
private EndsWithFinder(@NotNull String targetString) {
|
||||
this.targetString = targetString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFound(@Nullable String s) {
|
||||
return targetString.substring(0, i).endsWith(s);
|
||||
}
|
||||
|
||||
public void setI(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkMultiplicationSignBeforeFunction(@NotNull StringBuilder sb, @NotNull String s, int i) {
|
||||
if (i > 0) {
|
||||
// get character before function
|
||||
char chBefore = s.charAt(i - 1);
|
||||
char ch = s.charAt(i);
|
||||
|
||||
final MathEntityType mathTypeBefore = MathEntityType.getType(String.valueOf(chBefore));
|
||||
final MathEntityType mathType = MathEntityType.getType(String.valueOf(ch));
|
||||
|
||||
if (mathTypeBefore != MathEntityType.binary_operation &&
|
||||
mathTypeBefore != MathEntityType.unary_operation &&
|
||||
mathTypeBefore != MathEntityType.function &&
|
||||
!MathEntityType.openGroupSymbols.contains(chBefore)) {
|
||||
|
||||
if (mathType == MathEntityType.constant) {
|
||||
sb.append("*");
|
||||
} else if (MathEntityType.openGroupSymbols.contains(ch) && mathTypeBefore != null) {
|
||||
sb.append("*");
|
||||
} else if (mathType == MathEntityType.digit && mathTypeBefore != MathEntityType.digit && mathTypeBefore != MathEntityType.dot) {
|
||||
sb.append("*");
|
||||
} else {
|
||||
for (String function : MathEntityType.prefixFunctions) {
|
||||
if (s.startsWith(function, i)) {
|
||||
sb.append("*");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String wrap(@NotNull JsclOperation operation, @NotNull String s) {
|
||||
return operation.name() + "(\"" + s + "\");";
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package org.solovyev.android.calculator.math;
|
||||
|
||||
import jscl.math.Generic;
|
||||
import jscl.math.JSCLInteger;
|
||||
import jscl.math.NotIntegrableException;
|
||||
import jscl.math.Variable;
|
||||
import jscl.math.function.Function;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/26/11
|
||||
* Time: 12:58 PM
|
||||
*/
|
||||
public class Factorial extends Function {
|
||||
|
||||
public Factorial(Generic[] parameter) {
|
||||
super("fact", parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic evaluate() {
|
||||
return expressionValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic evalelem() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic evalsimp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic evalnum() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic antiderivative(int n) throws NotIntegrableException {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Generic derivative(int n) {
|
||||
throw new UnsupportedOperationException("Not implemented yet!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Variable newinstance() {
|
||||
return new Factorial(null);
|
||||
}
|
||||
}
|
@ -36,5 +36,10 @@ public interface Functions {
|
||||
String SQRT_SIGN = "√";
|
||||
String SQRT = "sqrt";
|
||||
|
||||
public static final List<String> all = Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP);
|
||||
public static final List<String> allPrefix = Arrays.asList(SIN, SINH, ASIN, ASINH, COS, COSH, ACOS, ACOSH, TAN, TANH, ATAN, ATANH, LOG, LN, MOD, SQRT, SQRT_SIGN, EXP);
|
||||
|
||||
Character FACT = '!';
|
||||
Character DEGREE = '°';
|
||||
|
||||
public static final List<Character> allPostfix = Arrays.asList(FACT, DEGREE);
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ public enum MathEntityType {
|
||||
constant,
|
||||
dot,
|
||||
function,
|
||||
postfix_function,
|
||||
unary_operation,
|
||||
binary_operation,
|
||||
group_symbols,
|
||||
@ -26,13 +27,15 @@ public enum MathEntityType {
|
||||
|
||||
public static final List<Character> constants = Arrays.asList('e', 'π', 'i');
|
||||
|
||||
public static final List<Character> dots = Arrays.asList('.', ',');
|
||||
public static final List<Character> dots = Arrays.asList('.');
|
||||
|
||||
public static final List<Character> unaryOperations = Arrays.asList('-', '=', '!');
|
||||
|
||||
public static final List<Character> binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' );
|
||||
|
||||
public static final List<String> functions = Functions.all;
|
||||
public static final List<String> prefixFunctions = Functions.allPrefix;
|
||||
|
||||
public static final List<Character> postfixFunctions = Functions.allPostfix;
|
||||
|
||||
public static final List<String> groupSymbols = Arrays.asList("[]", "()", "{}");
|
||||
|
||||
@ -57,7 +60,7 @@ public enum MathEntityType {
|
||||
}
|
||||
|
||||
if ( result == null ) {
|
||||
if ( functions.contains(s) ) {
|
||||
if ( prefixFunctions.contains(s) ) {
|
||||
result = MathEntityType.function;
|
||||
} else if ( groupSymbols.contains(s) ) {
|
||||
result = MathEntityType.group_symbols;
|
||||
@ -74,6 +77,8 @@ public enum MathEntityType {
|
||||
|
||||
if ( Character.isDigit(ch) ) {
|
||||
result = MathEntityType.digit;
|
||||
} else if ( postfixFunctions.contains(ch) ) {
|
||||
result = MathEntityType.postfix_function;
|
||||
} else if ( unaryOperations.contains(ch) ) {
|
||||
result = MathEntityType.unary_operation;
|
||||
} else if ( binaryOperations.contains(ch) ) {
|
||||
|
@ -0,0 +1,44 @@
|
||||
package org.solovyev.android.calculator;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* User: serso
|
||||
* Date: 9/26/11
|
||||
* Time: 12:13 PM
|
||||
*/
|
||||
public class ToJsclPreprocessorTest {
|
||||
|
||||
@Test
|
||||
public void testProcess() throws Exception {
|
||||
final ToJsclPreprocessor preprocessor = new ToJsclPreprocessor();
|
||||
|
||||
Assert.assertEquals( "sin(4)*cos(5)", preprocessor.process("sin(4)cos(5)"));
|
||||
Assert.assertEquals( "pi*sin(4)*pi*cos(sqrt(5))", preprocessor.process("πsin(4)πcos(√(5))"));
|
||||
Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5))", preprocessor.process("πsin(4)+πcos(√(5))"));
|
||||
Assert.assertEquals( "pi*sin(4)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4)+πcos(√(5+i))"));
|
||||
Assert.assertEquals( "pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("πsin(4.01)+πcos(√(5+i))"));
|
||||
Assert.assertEquals( "exp(1)^pi*sin(4.01)+pi*cos(sqrt(5+sqrt(-1)))", preprocessor.process("e^πsin(4.01)+πcos(√(5+i))"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostfixFunctionsProcessing() throws Exception {
|
||||
final ToJsclPreprocessor preprocessor = new ToJsclPreprocessor();
|
||||
|
||||
Assert.assertEquals(-1, preprocessor.getPostfixFunctionStart("5!", 0));
|
||||
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("!", 0));
|
||||
Assert.assertEquals(-1, preprocessor.getPostfixFunctionStart("5.4434234!", 8));
|
||||
Assert.assertEquals(1, preprocessor.getPostfixFunctionStart("2+5!", 2));
|
||||
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+5.4434234!", 13));
|
||||
Assert.assertEquals(14, preprocessor.getPostfixFunctionStart("2.23+5.4434234*5!", 15));
|
||||
Assert.assertEquals(14, preprocessor.getPostfixFunctionStart("2.23+5.4434234*5.1!", 17));
|
||||
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*5.1)!", 19));
|
||||
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*(5.1+1))!", 23));
|
||||
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+(5.4434234*sin(5.1+1))!", 26));
|
||||
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5)!", 5));
|
||||
Assert.assertEquals(0, preprocessor.getPostfixFunctionStart("sin(5sin(5sin(5)))!", 17));
|
||||
Assert.assertEquals(1, preprocessor.getPostfixFunctionStart("2+sin(5sin(5sin(5)))!", 19));
|
||||
Assert.assertEquals(4, preprocessor.getPostfixFunctionStart("2.23+sin(5.4434234*sin(5.1+1))!", 29));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user