This commit is contained in:
Sad Ellie 2024-02-02 17:01:42 +03:00
parent 32c8f3184d
commit 4a499b8fd3
6 changed files with 75 additions and 17 deletions

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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())

View File

@ -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<String>.repairLexicon(): List<String> {
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<String>.unpackAlPercents(): List<String> {
private fun List<String>.unpackAllPercents(): List<String> {
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<String>.unpackNotation(): List<String> {
// 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<String>.unpackPercentAt(percentIndex: Int): List<String> {
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

View File

@ -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!")

View File

@ -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.2E3")
assertLex("1.2×10^3+4.5×10^6", "1.2E+3+4.5E+6")
}
}