From 2751cee9267b95a7370cba9e0565f56c3b29d7cf Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 12 Jun 2020 16:56:58 +0300 Subject: [PATCH] MST expression --- kmath-ast/build.gradle.kts | 5 ++ .../kotlin/scientifik/kmath/ast/MST.kt | 62 +++++++++++++++++++ .../scientifik/kmath/ast/MSTExpression.kt | 19 ++++++ .../scientifik/kmath/ast/MathSyntaxTree.kt | 61 ------------------ .../kotlin/scientifik/kmath/ast/asm.kt | 2 +- .../kotlin/scientifik/kmath/ast/parser.kt | 56 +++++++++++++++++ .../kotlin/scietifik/kmath/ast/ParserTest.kt | 17 +++++ .../kmath/expressions/Expression.kt | 6 ++ .../expressions/functionalExpressions.kt | 17 ++++- .../scientifik/kmath/operations/Complex.kt | 6 ++ 10 files changed, 186 insertions(+), 65 deletions(-) create mode 100644 kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt create mode 100644 kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MSTExpression.kt delete mode 100644 kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MathSyntaxTree.kt create mode 100644 kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/parser.kt create mode 100644 kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt diff --git a/kmath-ast/build.gradle.kts b/kmath-ast/build.gradle.kts index f17809381..88540e7b8 100644 --- a/kmath-ast/build.gradle.kts +++ b/kmath-ast/build.gradle.kts @@ -13,4 +13,9 @@ kotlin.sourceSets { implementation("com.github.h0tk3y.betterParse:better-parse-multiplatform:0.4.0-alpha-3") } } + jvmMain{ + dependencies{ + implementation("com.github.h0tk3y.betterParse:better-parse-jvm:0.4.0-alpha-3") + } + } } \ No newline at end of file diff --git a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt new file mode 100644 index 000000000..eba2c3343 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MST.kt @@ -0,0 +1,62 @@ +package scientifik.kmath.ast + +import scientifik.kmath.operations.NumericAlgebra +import scientifik.kmath.operations.RealField + +/** + * A Mathematical Syntax Tree node for mathematical expressions + */ +sealed class MST { + + /** + * A node containing unparsed string + */ + data class Singular(val value: String) : MST() + + /** + * A node containing a number + */ + data class Numeric(val value: Number) : MST() + + /** + * A node containing an unary operation + */ + data class Unary(val operation: String, val value: MST) : MST() { + companion object { + const val ABS_OPERATION = "abs" + //TODO add operations + } + } + + /** + * A node containing binary operation + */ + data class Binary(val operation: String, val left: MST, val right: MST) : MST() { + companion object + } +} + +//TODO add a function with positional arguments + +//TODO add a function with named arguments + +fun NumericAlgebra.evaluate(node: MST): T { + return when (node) { + is MST.Numeric -> number(node.value) + is MST.Singular -> raw(node.value) + is MST.Unary -> unaryOperation(node.operation, evaluate(node.value)) + is MST.Binary -> when { + node.left is MST.Numeric && node.right is MST.Numeric -> { + val number = RealField.binaryOperation( + node.operation, + node.left.value.toDouble(), + node.right.value.toDouble() + ) + number(number) + } + node.left is MST.Numeric -> leftSideNumberOperation(node.operation, node.left.value, evaluate(node.right)) + node.right is MST.Numeric -> rightSideNumberOperation(node.operation, evaluate(node.left), node.right.value) + else -> binaryOperation(node.operation, evaluate(node.left), evaluate(node.right)) + } + } +} \ No newline at end of file diff --git a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MSTExpression.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MSTExpression.kt new file mode 100644 index 000000000..c0f124a35 --- /dev/null +++ b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MSTExpression.kt @@ -0,0 +1,19 @@ +package scientifik.kmath.ast + +import scientifik.kmath.expressions.Expression +import scientifik.kmath.operations.NumericAlgebra + +/** + * The expression evaluates MST on-flight + */ +class MSTExpression(val algebra: NumericAlgebra, val mst: MST) : Expression { + + /** + * Substitute algebra raw value + */ + private inner class InnerAlgebra(val arguments: Map) : NumericAlgebra by algebra { + override fun raw(value: String): T = arguments[value] ?: super.raw(value) + } + + override fun invoke(arguments: Map): T = InnerAlgebra(arguments).evaluate(mst) +} \ No newline at end of file diff --git a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MathSyntaxTree.kt b/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MathSyntaxTree.kt deleted file mode 100644 index b43ef705d..000000000 --- a/kmath-ast/src/commonMain/kotlin/scientifik/kmath/ast/MathSyntaxTree.kt +++ /dev/null @@ -1,61 +0,0 @@ -package scientifik.kmath.ast - -import scientifik.kmath.operations.NumericAlgebra -import scientifik.kmath.operations.RealField - -/** - * A syntax tree node for mathematical expressions - */ -sealed class MathSyntaxTree - -/** - * A node containing unparsed string - */ -data class SingularNode(val value: String) : MathSyntaxTree() - -/** - * A node containing a number - */ -data class NumberNode(val value: Number) : MathSyntaxTree() - -/** - * A node containing an unary operation - */ -data class UnaryNode(val operation: String, val value: MathSyntaxTree) : MathSyntaxTree() { - companion object { - const val ABS_OPERATION = "abs" - //TODO add operations - } -} - -/** - * A node containing binary operation - */ -data class BinaryNode(val operation: String, val left: MathSyntaxTree, val right: MathSyntaxTree) : MathSyntaxTree() { - companion object -} - -//TODO add a function with positional arguments - -//TODO add a function with named arguments - -fun NumericAlgebra.compile(node: MathSyntaxTree): T { - return when (node) { - is NumberNode -> number(node.value) - is SingularNode -> raw(node.value) - is UnaryNode -> unaryOperation(node.operation, compile(node.value)) - is BinaryNode -> when { - node.left is NumberNode && node.right is NumberNode -> { - val number = RealField.binaryOperation( - node.operation, - node.left.value.toDouble(), - node.right.value.toDouble() - ) - number(number) - } - node.left is NumberNode -> leftSideNumberOperation(node.operation, node.left.value, compile(node.right)) - node.right is NumberNode -> rightSideNumberOperation(node.operation, compile(node.left), node.right.value) - else -> binaryOperation(node.operation, compile(node.left), compile(node.right)) - } - } -} \ No newline at end of file diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/asm.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/asm.kt index 6c4325859..c01648fb0 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/asm.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/asm.kt @@ -20,4 +20,4 @@ inline fun > A.asm( block: AsmExpressionAlgebra.() -> AsmExpression ): Expression = TODO() -inline fun > A.asm(ast: MathSyntaxTree): Expression = asm { compile(ast) } \ No newline at end of file +inline fun > A.asm(ast: MST): Expression = asm { evaluate(ast) } \ No newline at end of file diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/parser.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/parser.kt new file mode 100644 index 000000000..dcab1c972 --- /dev/null +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/ast/parser.kt @@ -0,0 +1,56 @@ +package scientifik.kmath.ast + +import com.github.h0tk3y.betterParse.combinators.* +import com.github.h0tk3y.betterParse.grammar.Grammar +import com.github.h0tk3y.betterParse.grammar.parseToEnd +import com.github.h0tk3y.betterParse.grammar.parser +import com.github.h0tk3y.betterParse.grammar.tryParseToEnd +import com.github.h0tk3y.betterParse.parser.ParseResult +import com.github.h0tk3y.betterParse.parser.Parser +import scientifik.kmath.operations.FieldOperations +import scientifik.kmath.operations.PowerOperations +import scientifik.kmath.operations.RingOperations +import scientifik.kmath.operations.SpaceOperations + +private object ArithmeticsEvaluator : Grammar() { + val num by token("-?[\\d.]+(?:[eE]-?\\d+)?") + val lpar by token("\\(") + val rpar by token("\\)") + val mul by token("\\*") + val pow by token("\\^") + val div by token("/") + val minus by token("-") + val plus by token("\\+") + val ws by token("\\s+", ignore = true) + + val number: Parser by num use { MST.Numeric(text.toDouble()) } + + val term: Parser by number or + (skip(minus) and parser(this::term) map { MST.Unary(SpaceOperations.MINUS_OPERATION, it) }) or + (skip(lpar) and parser(this::rootParser) and skip(rpar)) + + val powChain by leftAssociative(term, pow) { a, _, b -> + MST.Binary(PowerOperations.POW_OPERATION, a, b) + } + + val divMulChain: Parser by leftAssociative(powChain, div or mul use { type }) { a, op, b -> + if (op == div) { + MST.Binary(FieldOperations.DIV_OPERATION, a, b) + } else { + MST.Binary(RingOperations.TIMES_OPERATION, a, b) + } + } + + val subSumChain: Parser by leftAssociative(divMulChain, plus or minus use { type }) { a, op, b -> + if (op == plus) { + MST.Binary(SpaceOperations.PLUS_OPERATION, a, b) + } else { + MST.Binary(SpaceOperations.MINUS_OPERATION, a, b) + } + } + + override val rootParser: Parser by subSumChain +} + +fun String.tryParseMath(): ParseResult = ArithmeticsEvaluator.tryParseToEnd(this) +fun String.parseMath(): MST = ArithmeticsEvaluator.parseToEnd(this) \ No newline at end of file diff --git a/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt new file mode 100644 index 000000000..6849d24b8 --- /dev/null +++ b/kmath-ast/src/jvmTest/kotlin/scietifik/kmath/ast/ParserTest.kt @@ -0,0 +1,17 @@ +package scietifik.kmath.ast + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import scientifik.kmath.ast.evaluate +import scientifik.kmath.ast.parseMath +import scientifik.kmath.operations.Complex +import scientifik.kmath.operations.ComplexField + +internal class ParserTest{ + @Test + fun parsedExpression(){ + val mst = "2+2*(2+2)".parseMath() + val res = ComplexField.evaluate(mst) + assertEquals(Complex(10.0,0.0), res) + } +} \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt index df5c981f5..eaf3cd1d7 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/Expression.kt @@ -7,6 +7,12 @@ import scientifik.kmath.operations.Algebra */ interface Expression { operator fun invoke(arguments: Map): T + + companion object { + operator fun invoke(block: (Map) -> T): Expression = object : Expression { + override fun invoke(arguments: Map): T = block(arguments) + } + } } operator fun Expression.invoke(vararg pairs: Pair): T = invoke(mapOf(*pairs)) diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/functionalExpressions.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/functionalExpressions.kt index c3861d256..6d7676c25 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/functionalExpressions.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/expressions/functionalExpressions.kt @@ -66,8 +66,7 @@ open class FunctionalExpressionField( val field: Field ) : Field>, ExpressionAlgebra>, FunctionalExpressionSpace(field) { - override val one: Expression - get() = const(this.field.one) + override val one: Expression = ConstantExpression(this.field.one) fun const(value: Double): Expression = const(field.run { one * value }) @@ -80,4 +79,16 @@ open class FunctionalExpressionField( operator fun T.times(arg: Expression) = arg * this operator fun T.div(arg: Expression) = arg / this -} \ No newline at end of file +} + +/** + * Create a functional expression on this [Space] + */ +fun Space.buildExpression(block: FunctionalExpressionSpace.() -> Expression): Expression = + FunctionalExpressionSpace(this).run(block) + +/** + * Create a functional expression on this [Field] + */ +fun Field.buildExpression(block: FunctionalExpressionField.() -> Expression): Expression = + FunctionalExpressionField(this).run(block) \ No newline at end of file diff --git a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt index e33e4078e..01aef824d 100644 --- a/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt +++ b/kmath-core/src/commonMain/kotlin/scientifik/kmath/operations/Complex.kt @@ -50,6 +50,12 @@ object ComplexField : ExtendedField { operator fun Complex.minus(d: Double) = add(this, -d.toComplex()) operator fun Double.times(c: Complex) = Complex(c.re * this, c.im * this) + + override fun raw(value: String): Complex = if (value == "i") { + i + } else { + super.raw(value) + } } /**