From 0ac4804a65cee79e1d00083e81ee37a5cffef4df Mon Sep 17 00:00:00 2001 From: EgorBron <71507444+EgorBron@users.noreply.github.com> Date: Mon, 14 Apr 2025 19:58:13 +0300 Subject: [PATCH 1/8] Add logical operators --- .../kotlin/com/notkamui/keval/KevalBuilder.kt | 74 +++++++++++++++++++ .../com/notkamui/keval/DSLResourcesTests.kt | 23 ++++++ 2 files changed, 97 insertions(+) diff --git a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt index 86a966e..d10bd27 100644 --- a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt +++ b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt @@ -17,6 +17,13 @@ class KevalBuilder internal constructor( resources += DEFAULT_RESOURCES } + /** + * Includes the logical operators to the current Keval instance. + */ + fun includeLogicalOperators(): KevalBuilder = apply { + resources += LOGICAL_OPERATORS + } + /** * Defines a binary operator for the current Keval instance. * @@ -178,6 +185,73 @@ class KevalBuilder internal constructor( "e" to KevalConstant(E) ) + private fun doubleToBoolean(value: Double) = value != 0.0 + private fun booleanToDouble(value: Boolean) = if (value) 1.0 else 0.0 + private fun booleanOperation(a: Double, b: Double, operation: (Boolean, Boolean) -> Boolean) = + booleanToDouble(operation(doubleToBoolean(a), doubleToBoolean(b))) + + val LOGICAL_OPERATORS: Map = mapOf( + // unary operators + "!" to KevalUnaryOperator(true) { booleanToDouble(!doubleToBoolean(it)) }, + + // binary operators + // we probably should increase the precedence of all operators in favor of & and | + "&" to KevalBinaryOperator(1, true) { a, b -> + booleanOperation(a, b) { x, y -> + x && y + } + }, + "|" to KevalBinaryOperator(1, true) { a, b -> + booleanOperation(a, b) { x, y -> + x || y + } + }, + "=" to KevalBinaryOperator(0, true) { a, b -> + booleanOperation(a, b) { x, y -> + x == y + } + }, + ">" to KevalBinaryOperator(0, true) { a, b -> + booleanToDouble(a > b) + }, + "<" to KevalBinaryOperator(0, true) { a, b -> + booleanToDouble(a < b) + }, + + // functions + "not" to KevalFunction(1) { booleanToDouble(!doubleToBoolean(it[0])) }, + "and" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> x && y } + }, + "or" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> x || y } + }, + "nor" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> !(x || y) } + }, + "xor" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> (x && !y) || (!x && y) } + }, + "ne" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> x != y } + }, + "eq" to KevalFunction(2) { + booleanOperation(it[0], it[1]) { x, y -> x == y } + }, + "gt" to KevalFunction(2) { + booleanToDouble(it[0] > it[1]) + }, + "lt" to KevalFunction(2) { + booleanToDouble(it[0] < it[1]) + }, + "ge" to KevalFunction(2) { + booleanToDouble(it[0] >= it[1]) + }, + "le" to KevalFunction(2) { + booleanToDouble(it[0] <= it[1]) + }, + ) + /** * Builder representation of a binary operator. * diff --git a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt index 6dd7465..d54167f 100644 --- a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt +++ b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt @@ -247,4 +247,27 @@ class DLSTest { } assertEquals(3.0, k.eval("1+2"), "1+2") } + + @Test + fun checkLogicalOperations() { + val k = Keval.create { + includeDefault() + includeLogicalOperators() + function { + name = "isPositive" + arity = 1 + implementation = { args -> if (args[0] > 0.0) 1.0 else 0.0 } + } + binaryOperator { + symbol = '#' + implementation = { a, b -> if (a > b) 1.0 else 0.0 } + precedence = 3 + isLeftAssociative = true + } + } + + val expr = "((2 > 1) & ne(4, 3)) | (isPositive(-5) = 1) | (10 # 5)" + println(expr.replace(" ", "")[10]) + assertEquals(1.0, k.eval(expr), expr) + } } From 280ab4965b63174e98d097a3e63f49bec95481e3 Mon Sep 17 00:00:00 2001 From: EgorBron <71507444+EgorBron@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:09:09 +0300 Subject: [PATCH 2/8] Changed logical operators to functions (tests failing) due to notKamui/Keval#55 tests for logical functions fails to pass --- .../kotlin/com/notkamui/keval/KevalBuilder.kt | 97 ++++++------------- .../com/notkamui/keval/DSLResourcesTests.kt | 26 ++++- 2 files changed, 50 insertions(+), 73 deletions(-) diff --git a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt index d10bd27..90c6eda 100644 --- a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt +++ b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt @@ -17,13 +17,6 @@ class KevalBuilder internal constructor( resources += DEFAULT_RESOURCES } - /** - * Includes the logical operators to the current Keval instance. - */ - fun includeLogicalOperators(): KevalBuilder = apply { - resources += LOGICAL_OPERATORS - } - /** * Defines a binary operator for the current Keval instance. * @@ -180,6 +173,23 @@ class KevalBuilder internal constructor( "floor" to KevalFunction(1) { floor(it[0]) }, "round" to KevalFunction(1) { round(it[0]) }, + // logical functions + "not" to KevalFunction(1) { booleanToDouble(!doubleToBoolean(it[0])) }, + "and" to KevalFunction(null) { it.reduceBoolean { a, b -> a && b } }, + "nand" to KevalFunction(null) { it.reduceBoolean(true) { a, b -> a && b } }, + "or" to KevalFunction(null) { it.reduceBoolean { a, b -> a || b } }, + "nor" to KevalFunction(null) { it.reduceBoolean(true) { a, b -> a || b } }, + "xor" to KevalFunction(null) { it.reduceBoolean { a, b -> a xor b } }, + "xnor" to KevalFunction(null) { it.reduceBoolean(true) { a, b -> a xor b } }, + "imply" to KevalFunction(2) { booleanOperation(it) { a, b -> !a || b } }, + "nimply" to KevalFunction(2) { booleanOperation(it) { a, b -> a && !b } }, + "eq" to KevalFunction(null) { booleanToDouble(it.all { e -> e == it[0] }) }, + "ne" to KevalFunction(null) { booleanToDouble(it.distinct().size == it.size) }, + "gt" to KevalFunction(2) { booleanToDouble(it[0] > it[1]) }, + "lt" to KevalFunction(2) { booleanToDouble(it[0] < it[1]) }, + "ge" to KevalFunction(2) { booleanToDouble(it[0] >= it[1]) }, + "le" to KevalFunction(2) { booleanToDouble(it[0] <= it[1]) }, + // constants "PI" to KevalConstant(PI), "e" to KevalConstant(E) @@ -187,70 +197,17 @@ class KevalBuilder internal constructor( private fun doubleToBoolean(value: Double) = value != 0.0 private fun booleanToDouble(value: Boolean) = if (value) 1.0 else 0.0 - private fun booleanOperation(a: Double, b: Double, operation: (Boolean, Boolean) -> Boolean) = - booleanToDouble(operation(doubleToBoolean(a), doubleToBoolean(b))) - - val LOGICAL_OPERATORS: Map = mapOf( - // unary operators - "!" to KevalUnaryOperator(true) { booleanToDouble(!doubleToBoolean(it)) }, - - // binary operators - // we probably should increase the precedence of all operators in favor of & and | - "&" to KevalBinaryOperator(1, true) { a, b -> - booleanOperation(a, b) { x, y -> - x && y - } - }, - "|" to KevalBinaryOperator(1, true) { a, b -> - booleanOperation(a, b) { x, y -> - x || y + private fun booleanOperation(array: DoubleArray, operation: (Boolean, Boolean) -> Boolean) = + booleanToDouble(operation(doubleToBoolean(array[0]), doubleToBoolean(array[1]))) + private fun DoubleArray.viaBoolean(operation: List.() -> Boolean) = + booleanToDouble(operation(map(::doubleToBoolean))) + private fun DoubleArray.reduceBoolean(invert: Boolean = false, operation: (Boolean, Boolean) -> Boolean) = + viaBoolean { + reduce(operation).let { + if (invert) !it + else it } - }, - "=" to KevalBinaryOperator(0, true) { a, b -> - booleanOperation(a, b) { x, y -> - x == y - } - }, - ">" to KevalBinaryOperator(0, true) { a, b -> - booleanToDouble(a > b) - }, - "<" to KevalBinaryOperator(0, true) { a, b -> - booleanToDouble(a < b) - }, - - // functions - "not" to KevalFunction(1) { booleanToDouble(!doubleToBoolean(it[0])) }, - "and" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> x && y } - }, - "or" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> x || y } - }, - "nor" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> !(x || y) } - }, - "xor" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> (x && !y) || (!x && y) } - }, - "ne" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> x != y } - }, - "eq" to KevalFunction(2) { - booleanOperation(it[0], it[1]) { x, y -> x == y } - }, - "gt" to KevalFunction(2) { - booleanToDouble(it[0] > it[1]) - }, - "lt" to KevalFunction(2) { - booleanToDouble(it[0] < it[1]) - }, - "ge" to KevalFunction(2) { - booleanToDouble(it[0] >= it[1]) - }, - "le" to KevalFunction(2) { - booleanToDouble(it[0] <= it[1]) - }, - ) + } /** * Builder representation of a binary operator. diff --git a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt index d54167f..a5b6f69 100644 --- a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt +++ b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt @@ -248,11 +248,11 @@ class DLSTest { assertEquals(3.0, k.eval("1+2"), "1+2") } + // this test fails due to wrong handling of nested calls @Test fun checkLogicalOperations() { val k = Keval.create { includeDefault() - includeLogicalOperators() function { name = "isPositive" arity = 1 @@ -266,8 +266,28 @@ class DLSTest { } } - val expr = "((2 > 1) & ne(4, 3)) | (isPositive(-5) = 1) | (10 # 5)" - println(expr.replace(" ", "")[10]) + /*val expr = """ + and( + not(lt(5, 3)), + or( + gt(4,2), + xor( + eq(1, 1, 1), + ne(1, 2, 3) + ) + ), + nand( + ge(5, 5), + le(3, 4) + ), + nor( + imply(1, 0), + nimply(1, 1) + ), + xnor(1, 1) + ) + """.trimIndent()*/ + val expr = "and(not(lt(1, 2)), 1)" assertEquals(1.0, k.eval(expr), expr) } } From 1081b61ea9e93549359fbd1ada3b06d4e48a7b04 Mon Sep 17 00:00:00 2001 From: EgorBron <71507444+EgorBron@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:23:57 +0300 Subject: [PATCH 3/8] Fixed nested calls issue resolves notKamui/Keval#55 --- .../kotlin/com/notkamui/keval/Tokenizer.kt | 3 +++ .../kotlin/com/notkamui/keval/ASTTest.kt | 8 +++++++ .../com/notkamui/keval/DSLResourcesTests.kt | 7 +++--- .../com/notkamui/keval/TokenizerTest.kt | 24 +++++++++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/com/notkamui/keval/Tokenizer.kt b/src/commonMain/kotlin/com/notkamui/keval/Tokenizer.kt index b76a11f..fd25330 100644 --- a/src/commonMain/kotlin/com/notkamui/keval/Tokenizer.kt +++ b/src/commonMain/kotlin/com/notkamui/keval/Tokenizer.kt @@ -38,6 +38,9 @@ private fun Sequence.normalizeTokens(symbols: Map } token == ")" -> TokenType.RPAREN.also { + if (functionAtCount.last() == parenthesesCount) { + functionAtCount.removeLast() + } parenthesesCount -= 1 ret.add(token) } diff --git a/src/commonTest/kotlin/com/notkamui/keval/ASTTest.kt b/src/commonTest/kotlin/com/notkamui/keval/ASTTest.kt index 71c68c0..3bb70e3 100644 --- a/src/commonTest/kotlin/com/notkamui/keval/ASTTest.kt +++ b/src/commonTest/kotlin/com/notkamui/keval/ASTTest.kt @@ -45,4 +45,12 @@ class ASTTest { val node = ValueNode(9.0) assertEquals(9.0, node.eval()) } + + @Test + fun testNestedFunctionNodes() { + val node1 = FunctionNode({ a -> a.sum() }, listOf(ValueNode(3.0), ValueNode(4.0))) + val node2 = FunctionNode({ a -> a.sum() }, listOf(node1, ValueNode(5.0))) + val mainNode = FunctionNode({ a -> a.sum() }, listOf(node2, node1)) + assertEquals(19.0, mainNode.eval()) + } } diff --git a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt index a5b6f69..ac90513 100644 --- a/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt +++ b/src/commonTest/kotlin/com/notkamui/keval/DSLResourcesTests.kt @@ -266,7 +266,7 @@ class DLSTest { } } - /*val expr = """ + val expr = """ and( not(lt(5, 3)), or( @@ -278,7 +278,7 @@ class DLSTest { ), nand( ge(5, 5), - le(3, 4) + not(le(3, 4)) ), nor( imply(1, 0), @@ -286,8 +286,7 @@ class DLSTest { ), xnor(1, 1) ) - """.trimIndent()*/ - val expr = "and(not(lt(1, 2)), 1)" + """.trimIndent() assertEquals(1.0, k.eval(expr), expr) } } diff --git a/src/commonTest/kotlin/com/notkamui/keval/TokenizerTest.kt b/src/commonTest/kotlin/com/notkamui/keval/TokenizerTest.kt index 0901b5d..d92c930 100644 --- a/src/commonTest/kotlin/com/notkamui/keval/TokenizerTest.kt +++ b/src/commonTest/kotlin/com/notkamui/keval/TokenizerTest.kt @@ -103,4 +103,28 @@ class TokenizerTest { val nodes = "f(((1)))".tokenize(k.resourcesView()) assertEquals("f(((1)))", nodes.joinToString(separator = "")) } + + @Test + fun checkNestedFunctions() { + val k = Keval.create { + includeDefault() + function { + name = "f" + arity = 1 + implementation = { args -> args[0] } + } + function { + name = "s" + implementation = { args -> args.sum() } + } + function { + name = "a" + arity = 2 + implementation = { args -> args[0] + args[1] } + } + } + + val nodes = "f(s(a(1,2),3))".tokenize(k.resourcesView()) + assertEquals("f(s(a(1,2),3))", nodes.joinToString(separator = "")) + } } From a4ce5b946d3f4ea36fd12c5dd2b9af60b0159d0f Mon Sep 17 00:00:00 2001 From: notKamui <46132319+notKamui@users.noreply.github.com> Date: Fri, 18 Apr 2025 08:34:31 +0200 Subject: [PATCH 4/8] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 30ac7cf..392ee5c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,22 @@ Keval has support for functions of variable arity: - Ceiling `ceil(expr)` (where 'expr' is an expression) - Floor `floor(expr)` (where 'expr' is an expression) - Round `round(expr)` (where 'expr' is an expression) +- Built-in boolean operations (0.0 is equivalent to false, anything else is equivalent to true): + - Invert `not(bool)` (where 'bool' is a boolean) + - And `and(bool...)` (where 'bool...' is any number of booleans) + - Not And `nand(bool...)` (where 'bool...' is any number of booleans) + - Or `or(bool...)` (where 'bool...' is any number of booleans) + - Not Or `nor(bool...)` (where 'bool...' is any number of booleans) + - Exclusive Or `xor(bool...)` (where 'bool...' is any number of booleans) + - Exlusive Not Or `xnor(bool...)` (where 'bool...' is any number of booleans) + - Implies `imply(bool_a, bool_b)` (where 'bool_a' and 'bool_b' are booleans) + - Does Not Imply `nimply(bool_a, bool_b)` (where 'bool_a' and 'bool_b' are booleans) + - Equals `eq(expr...)` (where 'expr...' is any number of expressions; returns a boolean) + - Does Not Equal `ne(expr...)` (where 'expr...' is any number of expressions; returns a boolean) + - Greater Than `gt(expr_a, expr_b)` (where 'expr_a' and 'expr_b' are expressions; returns a boolean) + - Greater Or Equal To `ge(expr_a, expr_b)` (where 'expr_a' and 'expr_b' are expressions; returns a boolean) + - Less Than `lt(expr_a, expr_b)` (where 'expr_a' and 'expr_b' are expressions; returns a boolean) + - Less Or Equal To `le(expr_a, expr_b)` (where 'expr_a' and 'expr_b' are expressions; returns a boolean) Keval has support for constants, it has two built-in constant: From 71741029fb5179b05f4379a723ac3c0933f69e3b Mon Sep 17 00:00:00 2001 From: EgorBron <71507444+EgorBron@users.noreply.github.com> Date: Mon, 21 Apr 2025 20:06:41 +0300 Subject: [PATCH 5/8] Add more functions closes #56 --- .../kotlin/com/notkamui/keval/KevalBuilder.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt index 90c6eda..4280cf8 100644 --- a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt +++ b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt @@ -1,6 +1,7 @@ package com.notkamui.keval import kotlin.math.* +import kotlin.random.Random /** * This class is used to build a Keval instance with custom operators, functions, and constants. @@ -156,9 +157,11 @@ class KevalBuilder internal constructor( // functions "neg" to KevalFunction(1) { -it[0] }, + "sign" to KevalFunction(1) { if (it[0] < 0) -1.0 else if (it[0] > 0) 1.0 else 0.0 }, "abs" to KevalFunction(1) { it[0].absoluteValue }, "sqrt" to KevalFunction(1) { sqrt(it[0]) }, "cbrt" to KevalFunction(1) { cbrt(it[0]) }, + "nthrt" to KevalFunction(2) { it[1].pow(1 / it[0]) }, "exp" to KevalFunction(1) { exp(it[0]) }, "ln" to KevalFunction(1) { ln(it[0]) }, "log10" to KevalFunction(1) { log10(it[0]) }, @@ -172,8 +175,40 @@ class KevalBuilder internal constructor( "ceil" to KevalFunction(1) { ceil(it[0]) }, "floor" to KevalFunction(1) { floor(it[0]) }, "round" to KevalFunction(1) { round(it[0]) }, + "trunc" to KevalFunction(1) { it[0].toInt().toDouble() }, + "min" to KevalFunction(null) { it.min() }, + "max" to KevalFunction(null) { it.max() }, + "sum" to KevalFunction(null) { it.sum() }, + "avg" to KevalFunction(null) { it.average() }, + "median" to KevalFunction(null) { it.sorted()[it.size / 2] }, + "percentile" to KevalFunction(null) { + if (it.size > 1) throw KevalInvalidArgumentException("percentile requires at least 2 values") + val perc = it[0] + if (perc in 0.0..100.0) throw KevalInvalidArgumentException("percentile must be between 0 and 100") + val sorted = it.sorted() + val index = (perc / 100 * (sorted.size - 1)).toInt() + sorted[index] + }, + "rand" to KevalFunction(null) { + when (it.size) { + 0 -> Random.Default.nextDouble() + 1 -> (0..it[0].toInt()).random().toDouble() + else -> it.random() + } + }, + "randRange" to KevalFunction(3) { + val start = it[0] + val end = it[1] + val step = it[2] + + if (step > 0) throw KevalInvalidArgumentException("step must be greater than 0") + val numberOfSteps = ((end - start) / step).toInt() + val randomStepIndex = Random.nextInt(0, numberOfSteps + 1) + start + randomStepIndex * step + }, // logical functions + "bool" to KevalFunction(1) { booleanToDouble(it[0] != 0.0) }, "not" to KevalFunction(1) { booleanToDouble(!doubleToBoolean(it[0])) }, "and" to KevalFunction(null) { it.reduceBoolean { a, b -> a && b } }, "nand" to KevalFunction(null) { it.reduceBoolean(true) { a, b -> a && b } }, From de5819a154746ad1c3edfe1b3797eeaf48752c04 Mon Sep 17 00:00:00 2001 From: Egor Bron <71507444+EgorBron@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:15:01 +0300 Subject: [PATCH 6/8] Fix the new functions (apply suggestions from code review) Co-authored-by: notKamui <46132319+notKamui@users.noreply.github.com> --- src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt index 4280cf8..203a6ca 100644 --- a/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt +++ b/src/commonMain/kotlin/com/notkamui/keval/KevalBuilder.kt @@ -182,11 +182,11 @@ class KevalBuilder internal constructor( "avg" to KevalFunction(null) { it.average() }, "median" to KevalFunction(null) { it.sorted()[it.size / 2] }, "percentile" to KevalFunction(null) { - if (it.size > 1) throw KevalInvalidArgumentException("percentile requires at least 2 values") + if (it.size <= 1) throw KevalInvalidArgumentException("percentile requires at least 2 values") val perc = it[0] - if (perc in 0.0..100.0) throw KevalInvalidArgumentException("percentile must be between 0 and 100") + if (perc !in 0.0..100.0) throw KevalInvalidArgumentException("percentile must be between 0 and 100") val sorted = it.sorted() - val index = (perc / 100 * (sorted.size - 1)).toInt() + val index = ((perc / 100) * sorted.size).toInt() sorted[index] }, "rand" to KevalFunction(null) { From 1c977ea0775fb0d3ca5a8f17c40179cfe2ae9c9e Mon Sep 17 00:00:00 2001 From: Egor Bron <71507444+EgorBron@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:26:39 +0300 Subject: [PATCH 7/8] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 392ee5c..3a6cfe6 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,11 @@ Keval has support for all classic unary operators: Keval has support for functions of variable arity: - Negate/Oppose `neg(expr)` (where 'expr' is an expression) +- Expression sign `sign(expr)` (where 'expr' is an expression) - Absolute `abs(expr)` (where 'expr' is an expression) - Square root `sqrt(expr)` (where 'expr' is an expression) - Cube root `cbrt(expr)` (where 'expr' is an expression) +- Root of nth power `nthrt(expr_a, expr_pow)` (where 'expr_a' is an expression and 'expr_pow' is target power) - Exponential `exp(expr)` (where 'expr' is an expression) - Natural logarithm `ln(expr)` (where 'expr' is an expression) - Base 10 logarithm `log10(expr)` (where 'expr' is an expression) @@ -79,7 +81,13 @@ Keval has support for functions of variable arity: - Ceiling `ceil(expr)` (where 'expr' is an expression) - Floor `floor(expr)` (where 'expr' is an expression) - Round `round(expr)` (where 'expr' is an expression) +- Truncate decimal part `trunc(expr)` (where 'expr' is an expression) +- Minimal/maximal/average/median value `min(expr...)`/`max(expr...)`/`avg(expr...)`/`median(expr...)` (where 'expr...' is any number of expressions to get results from) +- Percentile `percentile(expr_perc, expr_a)` (where 'expr_a' is the expression to get percentile from, and 'expr_perc' is the percent value) +- Random number generator/selector `rand(expr...)` (where 'expr...' is any number of expressions; either generates a random double (zero arguments), or generates a random integer up to expr (one argument), or selects a random value of given arguments (two or more arguments) +- Random number between 'start' and 'end' with 'step' `randRange(expr_start, expr_end, expr_step)` - Built-in boolean operations (0.0 is equivalent to false, anything else is equivalent to true): + - Convert to "boolean" form (0/1) `bool(expr)` (where 'expr' is an expression) - Invert `not(bool)` (where 'bool' is a boolean) - And `and(bool...)` (where 'bool...' is any number of booleans) - Not And `nand(bool...)` (where 'bool...' is any number of booleans) From ba836cd4989441bb214440d25ca105fa250e33e6 Mon Sep 17 00:00:00 2001 From: notKamui <46132319+notKamui@users.noreply.github.com> Date: Sat, 23 May 2026 14:22:38 +0200 Subject: [PATCH 8/8] ci fix --- .github/workflows/runTests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/runTests.yaml b/.github/workflows/runTests.yaml index 533cebc..aed7538 100644 --- a/.github/workflows/runTests.yaml +++ b/.github/workflows/runTests.yaml @@ -2,7 +2,7 @@ name: Run Tests -on: [push,pull_request] +on: [pull_request] jobs: test: