diff --git a/core/base/src/main/java/com/sadellie/unitto/core/base/Token.kt b/core/base/src/main/java/com/sadellie/unitto/core/base/Token.kt index 0c5004dc..fbc9f5d6 100644 --- a/core/base/src/main/java/com/sadellie/unitto/core/base/Token.kt +++ b/core/base/src/main/java/com/sadellie/unitto/core/base/Token.kt @@ -126,7 +126,7 @@ object Token { } val expressionTokens by lazy { - Digit.allWithDot + Operator.all + Func.all + Const.all + Digit.allWithDot + Operator.all + Func.all + Const.all + DisplayOnly.engineeringE } val numberBaseTokens by lazy { diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FormatterExtensions.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FormatterExtensions.kt index 9acfe288..75bd31ef 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FormatterExtensions.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FormatterExtensions.kt @@ -54,11 +54,6 @@ fun String.formatExpression( ): String { var input = this - // Don't do anything to engineering string. - if (input.contains(Token.DisplayOnly.engineeringE)) { - return input.replace(Token.Digit.dot, formatterSymbols.fractional) - } - // Fractional if (input.contains(Token.DisplayOnly.fraction)) { // Only format integral part @@ -77,8 +72,9 @@ fun String.formatExpression( input = input.replace(it, it.formatNumber(formatterSymbols)) } - Token.sexyToUgly.forEach { (token, ugliness) -> - ugliness.forEach { uglySymbol -> + // Replace ugly symbols + Token.sexyToUgly.forEach { (token, uglySymbols) -> + uglySymbols.forEach { uglySymbol -> input = input.replace(uglySymbol, token) } } diff --git a/core/ui/src/test/java/com/sadellie/unitto/core/ui/FormatterExpressionTest.kt b/core/ui/src/test/java/com/sadellie/unitto/core/ui/FormatterExpressionTest.kt index 2a39284e..6179d129 100644 --- a/core/ui/src/test/java/com/sadellie/unitto/core/ui/FormatterExpressionTest.kt +++ b/core/ui/src/test/java/com/sadellie/unitto/core/ui/FormatterExpressionTest.kt @@ -25,6 +25,8 @@ import org.junit.Test private const val ENG_VALUE = "123E+21" private const val ENG_VALUE_FRACTIONAL = "123.3E+21" +private const val ENG_VALUE_EXPRESSION = "123E+21+(123456.789)" +private const val ENG_VALUE_FRACTIONAL_EXPRESSION = "123.3E+21+(123456.789)" private const val COMPLETE_VALUE = "123456.789" private const val INCOMPLETE_VALUE = "123456." private const val NO_FRACTIONAL_VALUE = "123456" @@ -41,6 +43,8 @@ class FormatterExpressionTest { fun String.format(): String = formatExpression(FormatterSymbols.Spaces) assertEquals("123E+21", ENG_VALUE.format()) assertEquals("123.3E+21", ENG_VALUE_FRACTIONAL.format()) + assertEquals("123E+21+(123 456.789)", ENG_VALUE_EXPRESSION.format()) + assertEquals("123.3E+21+(123 456.789)", ENG_VALUE_FRACTIONAL_EXPRESSION.format()) assertEquals("123 456.789", COMPLETE_VALUE.format()) assertEquals("123 456.", INCOMPLETE_VALUE.format()) assertEquals("123 456", NO_FRACTIONAL_VALUE.format()) @@ -56,6 +60,8 @@ class FormatterExpressionTest { fun String.format(): String = formatExpression(FormatterSymbols.Comma) assertEquals("123E+21", ENG_VALUE.format()) assertEquals("123.3E+21", ENG_VALUE_FRACTIONAL.format()) + assertEquals("123E+21+(123,456.789)", ENG_VALUE_EXPRESSION.format()) + assertEquals("123.3E+21+(123,456.789)", ENG_VALUE_FRACTIONAL_EXPRESSION.format()) assertEquals("123,456.789", COMPLETE_VALUE.format()) assertEquals("123,456.", INCOMPLETE_VALUE.format()) assertEquals("123,456", NO_FRACTIONAL_VALUE.format()) @@ -71,6 +77,8 @@ class FormatterExpressionTest { fun String.format(): String = formatExpression(FormatterSymbols.Period) assertEquals("123E+21", ENG_VALUE.format()) assertEquals("123,3E+21", ENG_VALUE_FRACTIONAL.format()) + assertEquals("123E+21+(123.456,789)", ENG_VALUE_EXPRESSION.format()) + assertEquals("123,3E+21+(123.456,789)", ENG_VALUE_FRACTIONAL_EXPRESSION.format()) assertEquals("123.456,789", COMPLETE_VALUE.format()) assertEquals("123.456,", INCOMPLETE_VALUE.format()) assertEquals("123.456", NO_FRACTIONAL_VALUE.format()) diff --git a/data/evaluatto/src/main/java/io/github/sadellie/evaluatto/Tokenizer.kt b/data/evaluatto/src/main/java/io/github/sadellie/evaluatto/Tokenizer.kt index 2939fa12..129ba237 100644 --- a/data/evaluatto/src/main/java/io/github/sadellie/evaluatto/Tokenizer.kt +++ b/data/evaluatto/src/main/java/io/github/sadellie/evaluatto/Tokenizer.kt @@ -20,8 +20,10 @@ package io.github.sadellie.evaluatto import com.sadellie.unitto.core.base.Token -sealed class TokenizerException(override val message: String) : Exception(message) { - class BadNumber : TokenizerException("Number has multiple commas in it") +sealed class TokenizerException(message: String) : Exception(message) { + class TooManyFractionSymbols : TokenizerException("Number has multiple commas in it") + class FailedToUnpackNumber : TokenizerException("Unexpected token before percentage") + class BadScientificNotation : TokenizerException("Expected plus or minus symbol after \"E\"") } class Tokenizer(private val streamOfTokens: String) { @@ -60,7 +62,7 @@ class Tokenizer(private val streamOfTokens: String) { .takeWhile { Token.Digit.allWithDot.contains(it.toString()) } if (number.count { it.toString() == Token.Digit.dot } > 1) { - throw TokenizerException.BadNumber() + throw TokenizerException.TooManyFractionSymbols() } return number @@ -74,13 +76,14 @@ class Tokenizer(private val streamOfTokens: String) { private fun List.repairLexicon(): List { return this .missingClosingBrackets() + .unpackNotation() .missingMultiply() - .unpackAlPercents() + .unpackAllPercents() // input like 80%80% should be treated as 80%*80%. // After unpacking we get (80/100)(80/100), the multiply is missing (!!!) // No, we can't unpack before fixing missing multiply. // Ideally we we need to add missing multiply for 80%80% - // In that case unpackAlPercents gets input with all operators 80%*80% in this case + // In that case unpackAllPercents gets input with all operators 80%*80% in this case // Can't be done right now since missingMultiply checks for tokens in front only .missingMultiply() } @@ -135,7 +138,7 @@ class Tokenizer(private val streamOfTokens: String) { return result } - private fun List.unpackAlPercents(): List { + private fun List.unpackAllPercents(): List { var result = this while (result.contains(Token.Operator.percent)) { val percIndex = result.indexOf(Token.Operator.percent) @@ -144,6 +147,38 @@ class Tokenizer(private val streamOfTokens: String) { return result } + private fun List.unpackNotation(): List { + // Transform 1E+7 ==> 1*10^7 + // Transform 1E-7 ==> 1/10^7 + val result = this.toMutableList() + val listIterator = result.listIterator() + + while (listIterator.hasNext()) { + if (listIterator.next() == Token.DisplayOnly.engineeringE) { + listIterator.remove() + + val tokenAfterE = try { + listIterator.next() + } catch (e: Exception) { + throw TokenizerException.BadScientificNotation() + } + + listIterator.remove() + + when (tokenAfterE) { + Token.Operator.minus -> listIterator.add(Token.Operator.divide) + Token.Operator.plus -> listIterator.add(Token.Operator.multiply) + else -> throw TokenizerException.BadScientificNotation() + } + + listIterator.add("10") + listIterator.add(Token.Operator.power) + } + } + + return result + } + private fun List.unpackPercentAt(percentIndex: Int): List { var cursor = percentIndex @@ -206,8 +241,9 @@ class Tokenizer(private val streamOfTokens: String) { // Just number if (tokenInFront.all { it in digits }) return listOf(tokenInFront) - // Not just a number. Probably expression in brackets. - if (tokenInFront != Token.Operator.rightBracket) throw Exception("Unexpected token before percentage") + // For cases like "100+(2+5)|%". The check above won't pass, so the next expected thing is + // a number in brackets. Anything else is not expected. + if (tokenInFront != Token.Operator.rightBracket) throw TokenizerException.FailedToUnpackNumber() // Start walking left until we get balanced brackets var cursor = pos - 1 diff --git a/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/ExpressionExceptionsTest.kt b/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/ExpressionExceptionsTest.kt index 11e57dc0..86c6afc5 100644 --- a/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/ExpressionExceptionsTest.kt +++ b/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/ExpressionExceptionsTest.kt @@ -38,7 +38,16 @@ class ExpressionExceptionsTest { fun `ugly ahh expression`() = assertExprFail(ExpressionException.BadExpression::class.java, "100+cos()") @Test - fun `ugly ahh expression2`() = assertExprFail(TokenizerException.BadNumber::class.java, "...") + fun `ugly ahh expression2`() = assertExprFail(TokenizerException.TooManyFractionSymbols::class.java, "...") + + @Test + fun `ugly ahh expression3`() = assertExprFail(TokenizerException.BadScientificNotation::class.java, "2.5E-") + + @Test + fun `ugly ahh expression4`() = assertExprFail(TokenizerException.BadScientificNotation::class.java, "2.5E") + + @Test + fun `ugly ahh expression5`() = assertExprFail(TokenizerException.BadScientificNotation::class.java, "2.5E÷") @Test fun `too big`() = assertExprFail(ExpressionException.TooBig::class.java, "999999!") diff --git a/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/FixLexiconTest.kt b/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/FixLexiconTest.kt index 068a1990..de4df510 100644 --- a/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/FixLexiconTest.kt +++ b/data/evaluatto/src/test/java/io/github/sadellie/evaluatto/FixLexiconTest.kt @@ -130,4 +130,13 @@ class FixLexiconTest { assertLex("10+(2.÷100×(10))", "10+2.%") } + + @Test + fun `scientific notation`() { + assertLex("1.2×10^3", "1.2E+3") + + assertLex("1.2÷10^3", "1.2E−3") + + assertLex("1.2×10^3+4.5×10^6", "1.2E+3+4.5E+6") + } }