From e81a1ef27bd52975763691ba3efe5e91e825cd2a Mon Sep 17 00:00:00 2001 From: Sergey Solovyev Date: Mon, 26 Sep 2011 13:03:54 +0400 Subject: [PATCH] postfix functions --- .../android/calculator/CalculatorModel.java | 9 +- .../android/calculator/Preprocessor.java | 125 +---------- .../calculator/ToJsclPreprocessor.java | 202 ++++++++++++++++++ .../android/calculator/math/Factorial.java | 54 +++++ .../android/calculator/math/Functions.java | 7 +- .../calculator/math/MathEntityType.java | 11 +- .../calculator/ToJsclPreprocessorTest.java | 44 ++++ 7 files changed, 327 insertions(+), 125 deletions(-) create mode 100644 src/main/java/org/solovyev/android/calculator/ToJsclPreprocessor.java create mode 100644 src/main/java/org/solovyev/android/calculator/math/Factorial.java create mode 100644 src/test/java/org/solovyev/android/calculator/ToJsclPreprocessorTest.java diff --git a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java index 99615ebd..d87a0a2b 100644 --- a/src/main/java/org/solovyev/android/calculator/CalculatorModel.java +++ b/src/main/java/org/solovyev/android/calculator/CalculatorModel.java @@ -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 { diff --git a/src/main/java/org/solovyev/android/calculator/Preprocessor.java b/src/main/java/org/solovyev/android/calculator/Preprocessor.java index 35ad3735..cf30460c 100644 --- a/src/main/java/org/solovyev/android/calculator/Preprocessor.java +++ b/src/main/java/org/solovyev/android/calculator/Preprocessor.java @@ -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 { - - 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); } diff --git a/src/main/java/org/solovyev/android/calculator/ToJsclPreprocessor.java b/src/main/java/org/solovyev/android/calculator/ToJsclPreprocessor.java new file mode 100644 index 00000000..d1d6d1b5 --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/ToJsclPreprocessor.java @@ -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 { + + 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 { + + 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 + "\");"; + } +} diff --git a/src/main/java/org/solovyev/android/calculator/math/Factorial.java b/src/main/java/org/solovyev/android/calculator/math/Factorial.java new file mode 100644 index 00000000..03cfba3f --- /dev/null +++ b/src/main/java/org/solovyev/android/calculator/math/Factorial.java @@ -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); + } +} diff --git a/src/main/java/org/solovyev/android/calculator/math/Functions.java b/src/main/java/org/solovyev/android/calculator/math/Functions.java index 67d4e3d5..4907efe6 100644 --- a/src/main/java/org/solovyev/android/calculator/math/Functions.java +++ b/src/main/java/org/solovyev/android/calculator/math/Functions.java @@ -36,5 +36,10 @@ public interface Functions { String SQRT_SIGN = "√"; String SQRT = "sqrt"; - public static final List 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 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 allPostfix = Arrays.asList(FACT, DEGREE); } diff --git a/src/main/java/org/solovyev/android/calculator/math/MathEntityType.java b/src/main/java/org/solovyev/android/calculator/math/MathEntityType.java index 95d50f6f..b6db3d24 100644 --- a/src/main/java/org/solovyev/android/calculator/math/MathEntityType.java +++ b/src/main/java/org/solovyev/android/calculator/math/MathEntityType.java @@ -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 constants = Arrays.asList('e', 'π', 'i'); - public static final List dots = Arrays.asList('.', ','); + public static final List dots = Arrays.asList('.'); public static final List unaryOperations = Arrays.asList('-', '=', '!'); public static final List binaryOperations = Arrays.asList('-', '+', '*', '×', '∙', '/', '^' ); - public static final List functions = Functions.all; + public static final List prefixFunctions = Functions.allPrefix; + + public static final List postfixFunctions = Functions.allPostfix; public static final List 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) ) { diff --git a/src/test/java/org/solovyev/android/calculator/ToJsclPreprocessorTest.java b/src/test/java/org/solovyev/android/calculator/ToJsclPreprocessorTest.java new file mode 100644 index 00000000..6a2a0f58 --- /dev/null +++ b/src/test/java/org/solovyev/android/calculator/ToJsclPreprocessorTest.java @@ -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)); + } +}