我们如何计算由 Kotlin 中的字符串表示的布尔表达式?


val exp = "( 0 == 1 && 10 > 11 ) || ( 10 < 9 ) && ( 10 == 10)"
val result: Boolean = evaluate(exp) //result = true/false

如何在 Android (Kotlin( 中编写一个简单的程序来计算上述字符串并获得布尔结果? 我不想使用像JEL or JEval, Js Eval or any other library这样的完整评估器,因为它们对于此特定要求来说太大了。

Preconditions :
Operators supported : < > == && || 
Works only on digits

也不想使用ScriptEngineManager()

注意:javax.script 软件包在 Android 上不可用。

以下解析器是 100% 独立的,不需要任何库。当然,它可以通过 1000 种方式和一种方式进行改进,但它显示了原理(而且写起来很有趣(。

sealed class Token
data class BooleanComposition(val c: Char) : Token()
data class NumberComparison(val c: Char) : Token()
data class Number(val value: Int) : Token()
object ParenLeft : Token()
object ParenRight : Token()
object EOF : Token()
sealed class Expression
data class BooleanTerm(val numExpr: NumberExpression) : Expression()
data class BooleanExpression(val b1: Expression, val cmp: (Boolean, Boolean) -> Boolean, val b2: Expression) : Expression()
data class NumberExpression(val n1: Int, val cmp: (Int, Int) -> Boolean, val n2: Int) : Expression()
fun and(b1: Boolean, b2: Boolean) = b1 && b2
fun or(b1: Boolean, b2: Boolean) = b1 || b2
fun lessThan(n1: Int, n2: Int): Boolean = n1 < n2
fun greaterThan(n1: Int, n2: Int): Boolean = n1 > n2
fun equal(n1: Int, n2: Int): Boolean = n1 == n2
fun scan(inputString: String): List<Token> = sequence {
var index = 0
while (index < inputString.length) {
val c = inputString[index]
if (c == '&' || c == '|' || c == '=') {
check(inputString[++index] == c)
}
when (c) {
in '0'..'9' -> {
var end = index
while (end < inputString.length && inputString[end].isDigit()) {
end += 1
}
val numToken = Number(inputString.substring(index, end).toInt())
index = end - 1
yield(numToken)
}
'&', '|' -> yield(BooleanComposition(c))
'=', '<', '>' -> yield(NumberComparison(c))
'(' -> yield(ParenLeft)
')' -> yield(ParenRight)
else -> check(c.isWhitespace())
}
index += 1
}
yield(EOF)
}.toList()
fun parse(inputTokens: List<Token>): Expression {
var index = 0
fun expression(): Expression {
val lastExpression = when (val firstToken = inputTokens[index++]) {
is ParenLeft -> {
val nestedExpression = expression()
val closingToken = inputTokens[index++]
check(closingToken is ParenRight) { "Found $closingToken" }
nestedExpression
}
is Number -> {
val opToken = inputTokens[index++]
check(opToken is NumberComparison)
val op = when (opToken.c) {
'<' -> ::lessThan
'>' -> ::greaterThan
'=' -> ::equal
else -> error("Bad op $opToken")
}
val secondToken = inputTokens[index++]
check(secondToken is Number)
NumberExpression(firstToken.value, op, secondToken.value)
}
else -> error("Parse error on $firstToken")
}
return when (val lookAhead = inputTokens[index]) {
is EOF, is ParenRight -> lastExpression // pushback
is BooleanComposition -> {
// use lookAhead
index += 1
val op = when (lookAhead.c) {
'&' -> ::and
'|' -> ::or
else -> error("Bad op $lookAhead")
}
val secondExpression = expression()
BooleanExpression(lastExpression, op, secondExpression)
}
else -> error("Parse error on $lookAhead")
}
}
return expression()
}
fun evaluate(expr: Expression): Boolean = when (expr) {
is BooleanTerm -> evaluate(expr.numExpr)
is BooleanExpression -> expr.cmp.invoke(evaluate(expr.b1), evaluate(expr.b2))
is NumberExpression -> expr.cmp.invoke(expr.n1, expr.n2)
}
fun main() {
// scan . parse . evaluate ("( 0 == 1 && 10 > 11 ) || ( 10 < 9 ) && ( 10 == 10)")
val soExample = evaluate(parse(scan("( 0 == 1 && 10 > 11 ) || ( 10 < 9 ) && ( 10 == 10)")))
println(soExample)
}

在Android上没有内置选项。您必须使用一些第三方解释器或编写自己的解释器。

我最终使用了JEXL:它非常小(~400KB jar(并且易于使用。

Gradle 依赖项:

compile 'org.apache.commons:commons-jexl3:3.1'

用法(我创建的包装器(:

class ExpressionEvaluator {
private val jexlEngine = JexlBuilder().create()
private val jexlContext = MapContext()
fun evaluate(expression: String): Any? = try {
val jexlExpression = jexlEngine.createExpression(expression)
jexlExpression.evaluate(jexlContext)
} catch (e: JexlException) {
Timber.w("Could not evaluate expression '$expression'") //just logs
null
}
fun evaluateAsBoolean(expression: String): Boolean? {
val boolean = evaluate(expression) as? Boolean
if (boolean == null) {
Timber.w("Could not evaluate expression '$expression' as Boolean") //just logs
}
return boolean
}
}

最新更新