diff --git a/README.md b/README.md
index 7080c757e..7b78d4531 100644
--- a/README.md
+++ b/README.md
@@ -259,8 +259,8 @@ repositories {
}
dependencies {
- api("space.kscience:kmath-core:0.3.0-dev-3")
- // api("kscience.kmath:kmath-core-jvm:0.3.0-dev-3") for jvm-specific version
+ api("space.kscience:kmath-core:0.3.0-dev-4")
+ // api("kscience.kmath:kmath-core-jvm:0.3.0-dev-4") for jvm-specific version
}
```
diff --git a/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt b/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt
new file mode 100644
index 000000000..a250ad800
--- /dev/null
+++ b/examples/src/main/kotlin/space/kscience/kmath/ast/astRendering.kt
@@ -0,0 +1,21 @@
+package space.kscience.kmath.ast
+
+import space.kscience.kmath.ast.rendering.FeaturedMathRendererWithPostProcess
+import space.kscience.kmath.ast.rendering.LatexSyntaxRenderer
+import space.kscience.kmath.ast.rendering.MathMLSyntaxRenderer
+import space.kscience.kmath.ast.rendering.renderWithStringBuilder
+
+public fun main() {
+ val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath()
+ val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst)
+ println("MathSyntax:")
+ println(syntax)
+ println()
+ val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("LaTeX:")
+ println(latex)
+ println()
+ val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("MathML:")
+ println(mathML)
+}
diff --git a/kmath-ast/README.md b/kmath-ast/README.md
index ff954b914..44faa5cd5 100644
--- a/kmath-ast/README.md
+++ b/kmath-ast/README.md
@@ -12,7 +12,7 @@ Abstract syntax tree expression representation and related optimizations.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-ast:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -23,7 +23,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-ast:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-ast:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -35,7 +35,7 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-ast:0.3.0-dev-3")
+ implementation("space.kscience:kmath-ast:0.3.0-dev-4")
}
```
@@ -111,3 +111,39 @@ var executable = function (constants, arguments) {
#### Known issues
- This feature uses `eval` which can be unavailable in several environments.
+
+## Rendering expressions
+
+kmath-ast also includes an extensible engine to display expressions in LaTeX or MathML syntax.
+
+Example usage:
+
+```kotlin
+import space.kscience.kmath.ast.*
+import space.kscience.kmath.ast.rendering.*
+
+public fun main() {
+ val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath()
+ val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst)
+ val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("LaTeX:")
+ println(latex)
+ println()
+ val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("MathML:")
+ println(mathML)
+}
+```
+
+Result LaTeX:
+
+![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D)
+
+Result MathML (embedding MathML is not allowed by GitHub Markdown):
+
+```html
+ex-sin-12x2×1010+x3-12
+```
+
+It is also possible to create custom algorithms of render, and even add support of other markup languages
+(see API reference).
diff --git a/kmath-ast/docs/README-TEMPLATE.md b/kmath-ast/docs/README-TEMPLATE.md
index db071adb4..9ed44d584 100644
--- a/kmath-ast/docs/README-TEMPLATE.md
+++ b/kmath-ast/docs/README-TEMPLATE.md
@@ -78,3 +78,39 @@ var executable = function (constants, arguments) {
#### Known issues
- This feature uses `eval` which can be unavailable in several environments.
+
+## Rendering expressions
+
+kmath-ast also includes an extensible engine to display expressions in LaTeX or MathML syntax.
+
+Example usage:
+
+```kotlin
+import space.kscience.kmath.ast.*
+import space.kscience.kmath.ast.rendering.*
+
+public fun main() {
+ val mst = "exp(sqrt(x))-asin(2*x)/(2e10+x^3)/(-12)".parseMath()
+ val syntax = FeaturedMathRendererWithPostProcess.Default.render(mst)
+ val latex = LatexSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("LaTeX:")
+ println(latex)
+ println()
+ val mathML = MathMLSyntaxRenderer.renderWithStringBuilder(syntax)
+ println("MathML:")
+ println(mathML)
+}
+```
+
+Result LaTeX:
+
+![](http://chart.googleapis.com/chart?cht=tx&chl=e%5E%7B%5Csqrt%7Bx%7D%7D-%5Cfrac%7B%5Cfrac%7B%5Coperatorname%7Bsin%7D%5E%7B-1%7D%5C,%5Cleft(2%5C,x%5Cright)%7D%7B2%5Ctimes10%5E%7B10%7D%2Bx%5E%7B3%7D%7D%7D%7B-12%7D)
+
+Result MathML (embedding MathML is not allowed by GitHub Markdown):
+
+```html
+ex-sin-12x2×1010+x3-12
+```
+
+It is also possible to create custom algorithms of render, and even add support of other markup languages
+(see API reference).
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt
new file mode 100644
index 000000000..914da6d9f
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/LatexSyntaxRenderer.kt
@@ -0,0 +1,128 @@
+package space.kscience.kmath.ast.rendering
+
+/**
+ * [SyntaxRenderer] implementation for LaTeX.
+ *
+ * The generated string is a valid LaTeX fragment to be used in the Math Mode.
+ *
+ * Example usage:
+ *
+ * ```
+ * \documentclass{article}
+ * \begin{document}
+ * \begin{equation}
+ * %code generated by the syntax renderer
+ * \end{equation}
+ * \end{document}
+ * ```
+ *
+ * @author Iaroslav Postovalov
+ */
+public object LatexSyntaxRenderer : SyntaxRenderer {
+ public override fun render(node: MathSyntax, output: Appendable): Unit = output.run {
+ fun render(syntax: MathSyntax) = render(syntax, output)
+
+ when (node) {
+ is NumberSyntax -> append(node.string)
+ is SymbolSyntax -> append(node.string)
+
+ is OperatorNameSyntax -> {
+ append("\\operatorname{")
+ append(node.name)
+ append('}')
+ }
+
+ is SpecialSymbolSyntax -> when (node.kind) {
+ SpecialSymbolSyntax.Kind.INFINITY -> append("\\infty")
+ }
+
+ is OperandSyntax -> {
+ if (node.parentheses) append("\\left(")
+ render(node.operand)
+ if (node.parentheses) append("\\right)")
+ }
+
+ is UnaryOperatorSyntax -> {
+ render(node.prefix)
+ append("\\,")
+ render(node.operand)
+ }
+
+ is UnaryPlusSyntax -> {
+ append('+')
+ render(node.operand)
+ }
+
+ is UnaryMinusSyntax -> {
+ append('-')
+ render(node.operand)
+ }
+
+ is RadicalSyntax -> {
+ append("\\sqrt")
+ append('{')
+ render(node.operand)
+ append('}')
+ }
+
+ is SuperscriptSyntax -> {
+ render(node.left)
+ append("^{")
+ render(node.right)
+ append('}')
+ }
+
+ is SubscriptSyntax -> {
+ render(node.left)
+ append("_{")
+ render(node.right)
+ append('}')
+ }
+
+ is BinaryOperatorSyntax -> {
+ render(node.prefix)
+ append("\\left(")
+ render(node.left)
+ append(',')
+ render(node.right)
+ append("\\right)")
+ }
+
+ is BinaryPlusSyntax -> {
+ render(node.left)
+ append('+')
+ render(node.right)
+ }
+
+ is BinaryMinusSyntax -> {
+ render(node.left)
+ append('-')
+ render(node.right)
+ }
+
+ is FractionSyntax -> {
+ append("\\frac{")
+ render(node.left)
+ append("}{")
+ render(node.right)
+ append('}')
+ }
+
+ is RadicalWithIndexSyntax -> {
+ append("\\sqrt")
+ append('[')
+ render(node.left)
+ append(']')
+ append('{')
+ render(node.right)
+ append('}')
+ }
+
+ is MultiplicationSyntax -> {
+ render(node.left)
+ append(if (node.times) "\\times" else "\\,")
+ render(node.right)
+ }
+ }
+ }
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt
new file mode 100644
index 000000000..6f194be86
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathMLSyntaxRenderer.kt
@@ -0,0 +1,133 @@
+package space.kscience.kmath.ast.rendering
+
+/**
+ * [SyntaxRenderer] implementation for MathML.
+ *
+ * The generated XML string is a valid MathML instance.
+ *
+ * @author Iaroslav Postovalov
+ */
+public object MathMLSyntaxRenderer : SyntaxRenderer {
+ public override fun render(node: MathSyntax, output: Appendable) {
+ output.append("")
+ }
+
+ private fun render0(node: MathSyntax, output: Appendable): Unit = output.run {
+ fun tag(tagName: String, vararg attr: Pair, block: () -> Unit = {}) {
+ append('<')
+ append(tagName)
+
+ if (attr.isNotEmpty()) {
+ append(' ')
+ var count = 0
+
+ for ((name, value) in attr) {
+ if (++count > 1) append(' ')
+ append(name)
+ append("=\"")
+ append(value)
+ append('"')
+ }
+ }
+
+ append('>')
+ block()
+ append("")
+ append(tagName)
+ append('>')
+ }
+
+ fun render(syntax: MathSyntax) = render0(syntax, output)
+
+ when (node) {
+ is NumberSyntax -> tag("mn") { append(node.string) }
+ is SymbolSyntax -> tag("mi") { append(node.string) }
+ is OperatorNameSyntax -> tag("mo") { append(node.name) }
+
+ is SpecialSymbolSyntax -> when (node.kind) {
+ SpecialSymbolSyntax.Kind.INFINITY -> tag("mo") { append("∞") }
+ }
+
+ is OperandSyntax -> if (node.parentheses) {
+ tag("mfenced", "open" to "(", "close" to ")", "separators" to "") {
+ render(node.operand)
+ }
+ } else {
+ render(node.operand)
+ }
+
+ is UnaryOperatorSyntax -> {
+ render(node.prefix)
+ tag("mspace", "width" to "0.167em")
+ render(node.operand)
+ }
+
+ is UnaryPlusSyntax -> {
+ tag("mo") { append('+') }
+ render(node.operand)
+ }
+
+ is UnaryMinusSyntax -> {
+ tag("mo") { append("-") }
+ render(node.operand)
+ }
+
+ is RadicalSyntax -> tag("msqrt") { render(node.operand) }
+
+ is SuperscriptSyntax -> tag("msup") {
+ tag("mrow") { render(node.left) }
+ tag("mrow") { render(node.right) }
+ }
+
+ is SubscriptSyntax -> tag("msub") {
+ tag("mrow") { render(node.left) }
+ tag("mrow") { render(node.right) }
+ }
+
+ is BinaryOperatorSyntax -> {
+ render(node.prefix)
+
+ tag("mfenced", "open" to "(", "close" to ")", "separators" to "") {
+ render(node.left)
+ tag("mo") { append(',') }
+ render(node.right)
+ }
+ }
+
+ is BinaryPlusSyntax -> {
+ render(node.left)
+ tag("mo") { append('+') }
+ render(node.right)
+ }
+
+ is BinaryMinusSyntax -> {
+ render(node.left)
+ tag("mo") { append('-') }
+ render(node.right)
+ }
+
+ is FractionSyntax -> tag("mfrac") {
+ tag("mrow") {
+ render(node.left)
+ }
+
+ tag("mrow") {
+ render(node.right)
+ }
+ }
+
+ is RadicalWithIndexSyntax -> tag("mroot") {
+ tag("mrow") { render(node.right) }
+ tag("mrow") { render(node.left) }
+ }
+
+ is MultiplicationSyntax -> {
+ render(node.left)
+ if (node.times) tag("mo") { append("×") } else tag("mspace", "width" to "0.167em")
+ render(node.right)
+ }
+ }
+ }
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt
new file mode 100644
index 000000000..afdf12b04
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathRenderer.kt
@@ -0,0 +1,102 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST
+
+/**
+ * Renders [MST] to [MathSyntax].
+ *
+ * @author Iaroslav Postovalov
+ */
+public fun interface MathRenderer {
+ /**
+ * Renders [MST] to [MathSyntax].
+ */
+ public fun render(mst: MST): MathSyntax
+}
+
+/**
+ * Implements [MST] render process with sequence of features.
+ *
+ * @property features The applied features.
+ * @author Iaroslav Postovalov
+ */
+public open class FeaturedMathRenderer(public val features: List) : MathRenderer {
+ public override fun render(mst: MST): MathSyntax {
+ for (feature in features) feature.render(this, mst)?.let { return it }
+ throw UnsupportedOperationException("Renderer $this has no appropriate feature to render node $mst.")
+ }
+
+ /**
+ * Logical unit of [MST] rendering.
+ */
+ public fun interface RenderFeature {
+ /**
+ * Renders [MST] to [MathSyntax] in the context of owning renderer.
+ */
+ public fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax?
+ }
+}
+
+/**
+ * Extends [FeaturedMathRenderer] by adding post-processing stages.
+ *
+ * @property stages The applied stages.
+ * @author Iaroslav Postovalov
+ */
+public open class FeaturedMathRendererWithPostProcess(
+ features: List,
+ public val stages: List,
+) : FeaturedMathRenderer(features) {
+ public override fun render(mst: MST): MathSyntax {
+ val res = super.render(mst)
+ for (stage in stages) stage.perform(res)
+ return res
+ }
+
+ /**
+ * Logical unit of [MathSyntax] post-processing.
+ */
+ public fun interface PostProcessStage {
+ /**
+ * Performs the specified action over [MathSyntax].
+ */
+ public fun perform(node: MathSyntax)
+ }
+
+ public companion object {
+ /**
+ * The default setup of [FeaturedMathRendererWithPostProcess].
+ */
+ public val Default: FeaturedMathRendererWithPostProcess = FeaturedMathRendererWithPostProcess(
+ listOf(
+ // Printing known operations
+ BinaryPlus.Default,
+ BinaryMinus.Default,
+ UnaryPlus.Default,
+ UnaryMinus.Default,
+ Multiplication.Default,
+ Fraction.Default,
+ Power.Default,
+ SquareRoot.Default,
+ Exponential.Default,
+ InverseTrigonometricOperations.Default,
+
+ // Fallback option for unknown operations - printing them as operator
+ BinaryOperator.Default,
+ UnaryOperator.Default,
+
+ // Pretty printing for numerics
+ PrettyPrintFloats.Default,
+ PrettyPrintIntegers.Default,
+
+ // Printing terminal nodes as string
+ PrintNumeric,
+ PrintSymbolic,
+ ),
+ listOf(
+ SimplifyParentheses.Default,
+ BetterMultiplication,
+ )
+ )
+ }
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt
new file mode 100644
index 000000000..4c85adcfc
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/MathSyntax.kt
@@ -0,0 +1,326 @@
+package space.kscience.kmath.ast.rendering
+
+/**
+ * Mathematical typography syntax node.
+ *
+ * @author Iaroslav Postovalov
+ */
+public sealed class MathSyntax {
+ /**
+ * The parent node of this syntax node.
+ */
+ public var parent: MathSyntax? = null
+}
+
+/**
+ * Terminal node, which should not have any children nodes.
+ *
+ * @author Iaroslav Postovalov
+ */
+public sealed class TerminalSyntax : MathSyntax()
+
+/**
+ * Node containing a certain operation.
+ *
+ * @author Iaroslav Postovalov
+ */
+public sealed class OperationSyntax : MathSyntax() {
+ /**
+ * The operation token.
+ */
+ public abstract val operation: String
+}
+
+/**
+ * Unary node, which has only one child.
+ *
+ * @author Iaroslav Postovalov
+ */
+public sealed class UnarySyntax : OperationSyntax() {
+ /**
+ * The operand of this node.
+ */
+ public abstract val operand: MathSyntax
+}
+
+/**
+ * Binary node, which has only two children.
+ *
+ * @author Iaroslav Postovalov
+ */
+public sealed class BinarySyntax : OperationSyntax() {
+ /**
+ * The left-hand side operand.
+ */
+ public abstract val left: MathSyntax
+
+ /**
+ * The right-hand side operand.
+ */
+ public abstract val right: MathSyntax
+}
+
+/**
+ * Represents a number.
+ *
+ * @property string The digits of number.
+ * @author Iaroslav Postovalov
+ */
+public data class NumberSyntax(public var string: String) : TerminalSyntax()
+
+/**
+ * Represents a symbol.
+ *
+ * @property string The symbol.
+ * @author Iaroslav Postovalov
+ */
+public data class SymbolSyntax(public var string: String) : TerminalSyntax()
+
+/**
+ * Represents special typing for operator name.
+ *
+ * @property name The operator name.
+ * @see BinaryOperatorSyntax
+ * @see UnaryOperatorSyntax
+ * @author Iaroslav Postovalov
+ */
+public data class OperatorNameSyntax(public var name: String) : TerminalSyntax()
+
+/**
+ * Represents a usage of special symbols.
+ *
+ * @property kind The kind of symbol.
+ * @author Iaroslav Postovalov
+ */
+public data class SpecialSymbolSyntax(public var kind: Kind) : TerminalSyntax() {
+ /**
+ * The kind of symbol.
+ */
+ public enum class Kind {
+ /**
+ * The infinity (∞) symbol.
+ */
+ INFINITY,
+ }
+}
+
+/**
+ * Represents operand of a certain operator wrapped with parentheses or not.
+ *
+ * @property operand The operand.
+ * @property parentheses Whether the operand should be wrapped with parentheses.
+ * @author Iaroslav Postovalov
+ */
+public data class OperandSyntax(
+ public val operand: MathSyntax,
+ public var parentheses: Boolean,
+) : MathSyntax() {
+ init {
+ operand.parent = this
+ }
+}
+
+/**
+ * Represents unary, prefix operator syntax (like f x).
+ *
+ * @property prefix The prefix.
+ * @author Iaroslav Postovalov
+ */
+public data class UnaryOperatorSyntax(
+ public override val operation: String,
+ public var prefix: MathSyntax,
+ public override val operand: OperandSyntax,
+) : UnarySyntax() {
+ init {
+ operand.parent = this
+ }
+}
+
+/**
+ * Represents prefix, unary plus operator.
+ *
+ * @author Iaroslav Postovalov
+ */
+public data class UnaryPlusSyntax(
+ public override val operation: String,
+ public override val operand: OperandSyntax,
+) : UnarySyntax() {
+ init {
+ operand.parent = this
+ }
+}
+
+/**
+ * Represents prefix, unary minus operator.
+ *
+ * @author Iaroslav Postovalov
+ */
+public data class UnaryMinusSyntax(
+ public override val operation: String,
+ public override val operand: OperandSyntax,
+) : UnarySyntax() {
+ init {
+ operand.parent = this
+ }
+}
+
+/**
+ * Represents radical with a node inside it.
+ *
+ * @property operand The radicand.
+ * @author Iaroslav Postovalov
+ */
+public data class RadicalSyntax(
+ public override val operation: String,
+ public override val operand: MathSyntax,
+) : UnarySyntax() {
+ init {
+ operand.parent = this
+ }
+}
+
+/**
+ * Represents a syntax node with superscript (usually, for exponentiation).
+ *
+ * @property left The node.
+ * @property right The superscript.
+ * @author Iaroslav Postovalov
+ */
+public data class SuperscriptSyntax(
+ public override val operation: String,
+ public override val left: MathSyntax,
+ public override val right: MathSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents a syntax node with subscript.
+ *
+ * @property left The node.
+ * @property right The subscript.
+ * @author Iaroslav Postovalov
+ */
+public data class SubscriptSyntax(
+ public override val operation: String,
+ public override val left: MathSyntax,
+ public override val right: MathSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents binary, prefix operator syntax (like f(a, b)).
+ *
+ * @property prefix The prefix.
+ * @author Iaroslav Postovalov
+ */
+public data class BinaryOperatorSyntax(
+ public override val operation: String,
+ public var prefix: MathSyntax,
+ public override val left: MathSyntax,
+ public override val right: MathSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents binary, infix addition.
+ *
+ * @param left The augend.
+ * @param right The addend.
+ * @author Iaroslav Postovalov
+ */
+public data class BinaryPlusSyntax(
+ public override val operation: String,
+ public override val left: OperandSyntax,
+ public override val right: OperandSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents binary, infix subtraction.
+ *
+ * @param left The minuend.
+ * @param right The subtrahend.
+ * @author Iaroslav Postovalov
+ */
+public data class BinaryMinusSyntax(
+ public override val operation: String,
+ public override val left: OperandSyntax,
+ public override val right: OperandSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents fraction with numerator and denominator.
+ *
+ * @property left The numerator.
+ * @property right The denominator.
+ * @author Iaroslav Postovalov
+ */
+public data class FractionSyntax(
+ public override val operation: String,
+ public override val left: MathSyntax,
+ public override val right: MathSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents radical syntax with index.
+ *
+ * @property left The index.
+ * @property right The radicand.
+ * @author Iaroslav Postovalov
+ */
+public data class RadicalWithIndexSyntax(
+ public override val operation: String,
+ public override val left: MathSyntax,
+ public override val right: MathSyntax,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
+
+/**
+ * Represents binary, infix multiplication in the form of coefficient (2 x) or with operator (x×2).
+ *
+ * @property left The multiplicand.
+ * @property right The multiplier.
+ * @property times whether the times (×) symbol should be used.
+ * @author Iaroslav Postovalov
+ */
+public data class MultiplicationSyntax(
+ public override val operation: String,
+ public override val left: OperandSyntax,
+ public override val right: OperandSyntax,
+ public var times: Boolean,
+) : BinarySyntax() {
+ init {
+ left.parent = this
+ right.parent = this
+ }
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt
new file mode 100644
index 000000000..fcc79f76b
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/SyntaxRenderer.kt
@@ -0,0 +1,25 @@
+package space.kscience.kmath.ast.rendering
+
+/**
+ * Abstraction of writing [MathSyntax] as a string of an actual markup language. Typical implementation should
+ * involve traversal of MathSyntax with handling each its subtype.
+ *
+ * @author Iaroslav Postovalov
+ */
+public fun interface SyntaxRenderer {
+ /**
+ * Renders the [MathSyntax] to [output].
+ */
+ public fun render(node: MathSyntax, output: Appendable)
+}
+
+/**
+ * Calls [SyntaxRenderer.render] with given [node] and a new [StringBuilder] instance, and returns its content.
+ *
+ * @author Iaroslav Postovalov
+ */
+public fun SyntaxRenderer.renderWithStringBuilder(node: MathSyntax): String {
+ val sb = StringBuilder()
+ render(node, sb)
+ return sb.toString()
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt
new file mode 100644
index 000000000..6e66d3ca3
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/features.kt
@@ -0,0 +1,312 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST
+import space.kscience.kmath.ast.rendering.FeaturedMathRenderer.RenderFeature
+import space.kscience.kmath.operations.*
+import kotlin.reflect.KClass
+
+/**
+ * Prints any [MST.Symbolic] as a [SymbolSyntax] containing the [MST.Symbolic.value] of it.
+ *
+ * @author Iaroslav Postovalov
+ */
+public object PrintSymbolic : RenderFeature {
+ public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Symbolic) return null
+ return SymbolSyntax(string = node.value)
+ }
+}
+
+/**
+ * Prints any [MST.Numeric] as a [NumberSyntax] containing the [Any.toString] result of it.
+ *
+ * @author Iaroslav Postovalov
+ */
+public object PrintNumeric : RenderFeature {
+ public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Numeric) return null
+ return NumberSyntax(string = node.value.toString())
+ }
+}
+
+private fun printSignedNumberString(s: String): MathSyntax {
+ if (s.startsWith('-'))
+ return UnaryMinusSyntax(
+ operation = GroupOperations.MINUS_OPERATION,
+ operand = OperandSyntax(
+ operand = NumberSyntax(string = s.removePrefix("-")),
+ parentheses = true,
+ ),
+ )
+
+ return NumberSyntax(string = s)
+}
+
+/**
+ * Special printing for numeric types which are printed in form of
+ * *('-'? (DIGIT+ ('.' DIGIT+)? ('E' '-'? DIGIT+)? | 'Infinity')) | 'NaN'*.
+ *
+ * @property types The suitable types.
+ */
+public class PrettyPrintFloats(public val types: Set>) : RenderFeature {
+ public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Numeric || node.value::class !in types) return null
+ val toString = node.value.toString().removeSuffix(".0")
+
+ if ('E' in toString) {
+ val (beforeE, afterE) = toString.split('E')
+ val significand = beforeE.toDouble().toString().removeSuffix(".0")
+ val exponent = afterE.toDouble().toString().removeSuffix(".0")
+
+ return MultiplicationSyntax(
+ operation = RingOperations.TIMES_OPERATION,
+ left = OperandSyntax(operand = NumberSyntax(significand), parentheses = true),
+ right = OperandSyntax(
+ operand = SuperscriptSyntax(
+ operation = PowerOperations.POW_OPERATION,
+ left = NumberSyntax(string = "10"),
+ right = printSignedNumberString(exponent),
+ ),
+ parentheses = true,
+ ),
+ times = true,
+ )
+ }
+
+ if (toString.endsWith("Infinity")) {
+ val infty = SpecialSymbolSyntax(SpecialSymbolSyntax.Kind.INFINITY)
+
+ if (toString.startsWith('-'))
+ return UnaryMinusSyntax(
+ operation = GroupOperations.MINUS_OPERATION,
+ operand = OperandSyntax(operand = infty, parentheses = true),
+ )
+
+ return infty
+ }
+
+ return printSignedNumberString(toString)
+ }
+
+ public companion object {
+ /**
+ * The default instance containing [Float], and [Double].
+ */
+ public val Default: PrettyPrintFloats = PrettyPrintFloats(setOf(Float::class, Double::class))
+ }
+}
+
+/**
+ * Special printing for numeric types which are printed in form of *'-'? DIGIT+*.
+ *
+ * @property types The suitable types.
+ */
+public class PrettyPrintIntegers(public val types: Set>) : RenderFeature {
+ public override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Numeric || node.value::class !in types)
+ return null
+
+ return printSignedNumberString(node.value.toString())
+ }
+
+ public companion object {
+ /**
+ * The default instance containing [Byte], [Short], [Int], and [Long].
+ */
+ public val Default: PrettyPrintIntegers =
+ PrettyPrintIntegers(setOf(Byte::class, Short::class, Int::class, Long::class))
+ }
+}
+
+/**
+ * Abstract printing of unary operations which discards [MST] if their operation is not in [operations] or its type is
+ * not [MST.Unary].
+ *
+ * @param operations the allowed operations. If `null`, any operation is accepted.
+ */
+public abstract class Unary(public val operations: Collection?) : RenderFeature {
+ /**
+ * The actual render function.
+ */
+ protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax?
+
+ public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Unary || operations != null && node.operation !in operations) return null
+ return render0(renderer, node)
+ }
+}
+
+/**
+ * Abstract printing of unary operations which discards [MST] if their operation is not in [operations] or its type is
+ * not [MST.Binary].
+ *
+ * @property operations the allowed operations. If `null`, any operation is accepted.
+ */
+public abstract class Binary(public val operations: Collection?) : RenderFeature {
+ /**
+ * The actual render function.
+ */
+ protected abstract fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax?
+
+ public final override fun render(renderer: FeaturedMathRenderer, node: MST): MathSyntax? {
+ if (node !is MST.Binary || operations != null && node.operation !in operations) return null
+ return render0(renderer, node)
+ }
+}
+
+public class BinaryPlus(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryPlusSyntax(
+ operation = node.operation,
+ left = OperandSyntax(parent.render(node.left), true),
+ right = OperandSyntax(parent.render(node.right), true),
+ )
+
+ public companion object {
+ public val Default: BinaryPlus = BinaryPlus(setOf(GroupOperations.PLUS_OPERATION))
+ }
+}
+
+public class BinaryMinus(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryMinusSyntax(
+ operation = node.operation,
+ left = OperandSyntax(operand = parent.render(node.left), parentheses = true),
+ right = OperandSyntax(operand = parent.render(node.right), parentheses = true),
+ )
+
+ public companion object {
+ public val Default: BinaryMinus = BinaryMinus(setOf(GroupOperations.MINUS_OPERATION))
+ }
+}
+
+public class UnaryPlus(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryPlusSyntax(
+ operation = node.operation,
+ operand = OperandSyntax(operand = parent.render(node.value), parentheses = true),
+ )
+
+ public companion object {
+ public val Default: UnaryPlus = UnaryPlus(setOf(GroupOperations.PLUS_OPERATION))
+ }
+}
+
+public class UnaryMinus(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryMinusSyntax(
+ operation = node.operation,
+ operand = OperandSyntax(operand = parent.render(node.value), parentheses = true),
+ )
+
+ public companion object {
+ public val Default: UnaryMinus = UnaryMinus(setOf(GroupOperations.MINUS_OPERATION))
+ }
+}
+
+public class Fraction(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = FractionSyntax(
+ operation = node.operation,
+ left = parent.render(node.left),
+ right = parent.render(node.right),
+ )
+
+ public companion object {
+ public val Default: Fraction = Fraction(setOf(FieldOperations.DIV_OPERATION))
+ }
+}
+
+public class BinaryOperator(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = BinaryOperatorSyntax(
+ operation = node.operation,
+ prefix = OperatorNameSyntax(name = node.operation),
+ left = parent.render(node.left),
+ right = parent.render(node.right),
+ )
+
+ public companion object {
+ public val Default: BinaryOperator = BinaryOperator(null)
+ }
+}
+
+public class UnaryOperator(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax(
+ operation = node.operation,
+ prefix = OperatorNameSyntax(node.operation),
+ operand = OperandSyntax(parent.render(node.value), true),
+ )
+
+ public companion object {
+ public val Default: UnaryOperator = UnaryOperator(null)
+ }
+}
+
+public class Power(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = SuperscriptSyntax(
+ operation = node.operation,
+ left = OperandSyntax(parent.render(node.left), true),
+ right = OperandSyntax(parent.render(node.right), true),
+ )
+
+ public companion object {
+ public val Default: Power = Power(setOf(PowerOperations.POW_OPERATION))
+ }
+}
+
+public class SquareRoot(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax =
+ RadicalSyntax(operation = node.operation, operand = parent.render(node.value))
+
+ public companion object {
+ public val Default: SquareRoot = SquareRoot(setOf(PowerOperations.SQRT_OPERATION))
+ }
+}
+
+public class Exponential(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = SuperscriptSyntax(
+ operation = node.operation,
+ left = SymbolSyntax(string = "e"),
+ right = parent.render(node.value),
+ )
+
+ public companion object {
+ public val Default: Exponential = Exponential(setOf(ExponentialOperations.EXP_OPERATION))
+ }
+}
+
+public class Multiplication(operations: Collection?) : Binary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Binary): MathSyntax = MultiplicationSyntax(
+ operation = node.operation,
+ left = OperandSyntax(operand = parent.render(node.left), parentheses = true),
+ right = OperandSyntax(operand = parent.render(node.right), parentheses = true),
+ times = true,
+ )
+
+ public companion object {
+ public val Default: Multiplication = Multiplication(setOf(
+ RingOperations.TIMES_OPERATION,
+ ))
+ }
+}
+
+public class InverseTrigonometricOperations(operations: Collection?) : Unary(operations) {
+ public override fun render0(parent: FeaturedMathRenderer, node: MST.Unary): MathSyntax = UnaryOperatorSyntax(
+ operation = node.operation,
+ prefix = SuperscriptSyntax(
+ operation = PowerOperations.POW_OPERATION,
+ left = OperatorNameSyntax(name = node.operation.removePrefix("a")),
+ right = UnaryMinusSyntax(
+ operation = GroupOperations.MINUS_OPERATION,
+ operand = OperandSyntax(operand = NumberSyntax(string = "1"), parentheses = true),
+ ),
+ ),
+ operand = OperandSyntax(operand = parent.render(node.value), parentheses = true),
+ )
+
+ public companion object {
+ public val Default: InverseTrigonometricOperations = InverseTrigonometricOperations(setOf(
+ TrigonometricOperations.ACOS_OPERATION,
+ TrigonometricOperations.ASIN_OPERATION,
+ TrigonometricOperations.ATAN_OPERATION,
+ ExponentialOperations.ACOSH_OPERATION,
+ ExponentialOperations.ASINH_OPERATION,
+ ExponentialOperations.ATANH_OPERATION,
+ ))
+ }
+}
diff --git a/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt
new file mode 100644
index 000000000..c183f6ace
--- /dev/null
+++ b/kmath-ast/src/commonMain/kotlin/space/kscience/kmath/ast/rendering/stages.kt
@@ -0,0 +1,197 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.operations.FieldOperations
+import space.kscience.kmath.operations.GroupOperations
+import space.kscience.kmath.operations.PowerOperations
+import space.kscience.kmath.operations.RingOperations
+
+/**
+ * Removes unnecessary times (×) symbols from [MultiplicationSyntax].
+ *
+ * @author Iaroslav Postovalov
+ */
+public object BetterMultiplication : FeaturedMathRendererWithPostProcess.PostProcessStage {
+ public override fun perform(node: MathSyntax) {
+ when (node) {
+ is NumberSyntax -> Unit
+ is SymbolSyntax -> Unit
+ is OperatorNameSyntax -> Unit
+ is SpecialSymbolSyntax -> Unit
+ is OperandSyntax -> perform(node.operand)
+
+ is UnaryOperatorSyntax -> {
+ perform(node.prefix)
+ perform(node.operand)
+ }
+
+ is UnaryPlusSyntax -> perform(node.operand)
+ is UnaryMinusSyntax -> perform(node.operand)
+ is RadicalSyntax -> perform(node.operand)
+
+ is SuperscriptSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is SubscriptSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryOperatorSyntax -> {
+ perform(node.prefix)
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryPlusSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryMinusSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is FractionSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is RadicalWithIndexSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is MultiplicationSyntax -> {
+ node.times = node.right.operand is NumberSyntax && !node.right.parentheses
+ || node.left.operand is NumberSyntax && node.right.operand is FractionSyntax
+ || node.left.operand is NumberSyntax && node.right.operand is NumberSyntax
+ || node.left.operand is NumberSyntax && node.right.operand is SuperscriptSyntax && node.right.operand.left is NumberSyntax
+
+ perform(node.left)
+ perform(node.right)
+ }
+ }
+ }
+}
+
+/**
+ * Removes unnecessary parentheses from [OperandSyntax].
+ *
+ * @property precedenceFunction Returns the precedence number for syntax node. Higher number is lower priority.
+ * @author Iaroslav Postovalov
+ */
+public class SimplifyParentheses(public val precedenceFunction: (MathSyntax) -> Int) :
+ FeaturedMathRendererWithPostProcess.PostProcessStage {
+ public override fun perform(node: MathSyntax) {
+ when (node) {
+ is NumberSyntax -> Unit
+ is SymbolSyntax -> Unit
+ is OperatorNameSyntax -> Unit
+ is SpecialSymbolSyntax -> Unit
+
+ is OperandSyntax -> {
+ val isRightOfSuperscript =
+ (node.parent is SuperscriptSyntax) && (node.parent as SuperscriptSyntax).right === node
+
+ val precedence = precedenceFunction(node.operand)
+
+ val needParenthesesByPrecedence = when (val parent = node.parent) {
+ null -> false
+
+ is BinarySyntax -> {
+ val parentPrecedence = precedenceFunction(parent)
+
+ parentPrecedence < precedence ||
+ parentPrecedence == precedence && parentPrecedence != 0 && node === parent.right
+ }
+
+ else -> precedence > precedenceFunction(parent)
+ }
+
+ node.parentheses = !isRightOfSuperscript
+ && (needParenthesesByPrecedence || node.parent is UnaryOperatorSyntax)
+
+ perform(node.operand)
+ }
+
+ is UnaryOperatorSyntax -> {
+ perform(node.prefix)
+ perform(node.operand)
+ }
+
+ is UnaryPlusSyntax -> perform(node.operand)
+ is UnaryMinusSyntax -> {
+ perform(node.operand)
+ }
+ is RadicalSyntax -> perform(node.operand)
+
+ is SuperscriptSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is SubscriptSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryOperatorSyntax -> {
+ perform(node.prefix)
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryPlusSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is BinaryMinusSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is FractionSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is MultiplicationSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+
+ is RadicalWithIndexSyntax -> {
+ perform(node.left)
+ perform(node.right)
+ }
+ }
+ }
+
+ public companion object {
+ /**
+ * The default configuration of [SimplifyParentheses] where power is 1, multiplicative operations are 2,
+ * additive operations are 3.
+ */
+ public val Default: SimplifyParentheses = SimplifyParentheses {
+ when (it) {
+ is TerminalSyntax -> 0
+ is UnarySyntax -> 2
+
+ is BinarySyntax -> when (it.operation) {
+ PowerOperations.POW_OPERATION -> 1
+ RingOperations.TIMES_OPERATION -> 3
+ FieldOperations.DIV_OPERATION -> 3
+ GroupOperations.MINUS_OPERATION -> 4
+ GroupOperations.PLUS_OPERATION -> 4
+ else -> 0
+ }
+
+ else -> 0
+ }
+ }
+ }
+}
diff --git a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt
index 0bd9a386d..456a2ba07 100644
--- a/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt
+++ b/kmath-ast/src/jsMain/kotlin/space/kscience/kmath/estree/estree.kt
@@ -68,7 +68,7 @@ internal fun MST.compileWith(algebra: Algebra): Expression {
/**
* Compiles an [MST] to ESTree generated expression using given algebra.
*
- * @author Alexander Nozik.
+ * @author Iaroslav Postovalov
*/
public fun Algebra.expression(mst: MST): Expression =
mst.compileWith(this)
@@ -76,7 +76,7 @@ public fun Algebra.expression(mst: MST): Expression =
/**
* Optimizes performance of an [MstExpression] by compiling it into ESTree generated expression.
*
- * @author Alexander Nozik.
+ * @author Iaroslav Postovalov
*/
public fun MstExpression>.compile(): Expression =
mst.compileWith(algebra)
diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt
index 8875bd715..369fe136b 100644
--- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt
+++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/asm/asm.kt
@@ -73,7 +73,7 @@ internal fun MST.compileWith(type: Class, algebra: Algebra): Exp
/**
* Compiles an [MST] to ASM using given algebra.
*
- * @author Alexander Nozik.
+ * @author Alexander Nozik
*/
public inline fun Algebra.expression(mst: MST): Expression =
mst.compileWith(T::class.java, this)
@@ -81,7 +81,7 @@ public inline fun Algebra.expression(mst: MST): Expression<
/**
* Optimizes performance of an [MstExpression] using ASM codegen.
*
- * @author Alexander Nozik.
+ * @author Alexander Nozik
*/
public inline fun MstExpression>.compile(): Expression =
mst.compileWith(T::class.java, algebra)
diff --git a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt
index 9a38ce81a..8ecb0adda 100644
--- a/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt
+++ b/kmath-ast/src/jvmMain/kotlin/space/kscience/kmath/ast/parser.kt
@@ -21,7 +21,8 @@ import space.kscience.kmath.operations.RingOperations
/**
* better-parse implementation of grammar defined in the ArithmeticsEvaluator.g4.
*
- * @author Alexander Nozik and Iaroslav Postovalov
+ * @author Alexander Nozik
+ * @author Iaroslav Postovalov
*/
public object ArithmeticsEvaluator : Grammar() {
// TODO replace with "...".toRegex() when better-parse 0.4.1 is released
diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt
new file mode 100644
index 000000000..b10f7ed4e
--- /dev/null
+++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestFeatures.kt
@@ -0,0 +1,90 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST.Numeric
+import space.kscience.kmath.ast.rendering.TestUtils.testLatex
+import kotlin.test.Test
+
+internal class TestFeatures {
+ @Test
+ fun printSymbolic() = testLatex("x", "x")
+
+ @Test
+ fun printNumeric() {
+ val num = object : Number() {
+ override fun toByte(): Byte = throw UnsupportedOperationException()
+ override fun toChar(): Char = throw UnsupportedOperationException()
+ override fun toDouble(): Double = throw UnsupportedOperationException()
+ override fun toFloat(): Float = throw UnsupportedOperationException()
+ override fun toInt(): Int = throw UnsupportedOperationException()
+ override fun toLong(): Long = throw UnsupportedOperationException()
+ override fun toShort(): Short = throw UnsupportedOperationException()
+ override fun toString(): String = "foo"
+ }
+
+ testLatex(Numeric(num), "foo")
+ }
+
+ @Test
+ fun prettyPrintFloats() {
+ testLatex(Numeric(Double.NaN), "NaN")
+ testLatex(Numeric(Double.POSITIVE_INFINITY), "\\infty")
+ testLatex(Numeric(Double.NEGATIVE_INFINITY), "-\\infty")
+ testLatex(Numeric(1.0), "1")
+ testLatex(Numeric(-1.0), "-1")
+ testLatex(Numeric(1.42), "1.42")
+ testLatex(Numeric(-1.42), "-1.42")
+ testLatex(Numeric(1.1e10), "1.1\\times10^{10}")
+ testLatex(Numeric(1.1e-10), "1.1\\times10^{-10}")
+ testLatex(Numeric(-1.1e-10), "-1.1\\times10^{-10}")
+ testLatex(Numeric(-1.1e10), "-1.1\\times10^{10}")
+ }
+
+ @Test
+ fun prettyPrintIntegers() {
+ testLatex(Numeric(42), "42")
+ testLatex(Numeric(-42), "-42")
+ }
+
+ @Test
+ fun binaryPlus() = testLatex("2+2", "2+2")
+
+ @Test
+ fun binaryMinus() = testLatex("2-2", "2-2")
+
+ @Test
+ fun fraction() = testLatex("2/2", "\\frac{2}{2}")
+
+ @Test
+ fun binaryOperator() = testLatex("f(x, y)", "\\operatorname{f}\\left(x,y\\right)")
+
+ @Test
+ fun unaryOperator() = testLatex("f(x)", "\\operatorname{f}\\,\\left(x\\right)")
+
+ @Test
+ fun power() = testLatex("x^y", "x^{y}")
+
+ @Test
+ fun squareRoot() = testLatex("sqrt(x)", "\\sqrt{x}")
+
+ @Test
+ fun exponential() = testLatex("exp(x)", "e^{x}")
+
+ @Test
+ fun multiplication() = testLatex("x*1", "x\\times1")
+
+ @Test
+ fun inverseTrigonometry() {
+ testLatex("asin(x)", "\\operatorname{sin}^{-1}\\,\\left(x\\right)")
+ testLatex("asinh(x)", "\\operatorname{sinh}^{-1}\\,\\left(x\\right)")
+ testLatex("acos(x)", "\\operatorname{cos}^{-1}\\,\\left(x\\right)")
+ testLatex("acosh(x)", "\\operatorname{cosh}^{-1}\\,\\left(x\\right)")
+ testLatex("atan(x)", "\\operatorname{tan}^{-1}\\,\\left(x\\right)")
+ testLatex("atanh(x)", "\\operatorname{tanh}^{-1}\\,\\left(x\\right)")
+ }
+
+// @Test
+// fun unaryPlus() {
+// testLatex("+1", "+1")
+// testLatex("+1", "++1")
+// }
+}
diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt
new file mode 100644
index 000000000..9c1009042
--- /dev/null
+++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestLatex.kt
@@ -0,0 +1,65 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST
+import space.kscience.kmath.ast.rendering.TestUtils.testLatex
+import space.kscience.kmath.operations.GroupOperations
+import kotlin.test.Test
+
+internal class TestLatex {
+ @Test
+ fun number() = testLatex("42", "42")
+
+ @Test
+ fun symbol() = testLatex("x", "x")
+
+ @Test
+ fun operatorName() = testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)")
+
+ @Test
+ fun specialSymbol() = testLatex(MST.Numeric(Double.POSITIVE_INFINITY), "\\infty")
+
+ @Test
+ fun operand() {
+ testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)")
+ testLatex("1+1", "1+1")
+ }
+
+ @Test
+ fun unaryOperator() = testLatex("sin(1)", "\\operatorname{sin}\\,\\left(1\\right)")
+
+ @Test
+ fun unaryPlus() = testLatex(MST.Unary(GroupOperations.PLUS_OPERATION, MST.Numeric(1)), "+1")
+
+ @Test
+ fun unaryMinus() = testLatex("-x", "-x")
+
+ @Test
+ fun radical() = testLatex("sqrt(x)", "\\sqrt{x}")
+
+ @Test
+ fun superscript() = testLatex("x^y", "x^{y}")
+
+ @Test
+ fun subscript() = testLatex(SubscriptSyntax("", SymbolSyntax("x"), NumberSyntax("123")), "x_{123}")
+
+ @Test
+ fun binaryOperator() = testLatex("f(x, y)", "\\operatorname{f}\\left(x,y\\right)")
+
+ @Test
+ fun binaryPlus() = testLatex("x+x", "x+x")
+
+ @Test
+ fun binaryMinus() = testLatex("x-x", "x-x")
+
+ @Test
+ fun fraction() = testLatex("x/x", "\\frac{x}{x}")
+
+ @Test
+ fun radicalWithIndex() = testLatex(RadicalWithIndexSyntax("", SymbolSyntax("x"), SymbolSyntax("y")), "\\sqrt[x]{y}")
+
+ @Test
+ fun multiplication() {
+ testLatex("x*1", "x\\times1")
+ testLatex("1*x", "1\\,x")
+ }
+}
diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt
new file mode 100644
index 000000000..c9a462840
--- /dev/null
+++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestMathML.kt
@@ -0,0 +1,84 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST
+import space.kscience.kmath.ast.rendering.TestUtils.testMathML
+import space.kscience.kmath.operations.GroupOperations
+import kotlin.test.Test
+
+internal class TestMathML {
+ @Test
+ fun number() = testMathML("42", "42")
+
+ @Test
+ fun symbol() = testMathML("x", "x")
+
+ @Test
+ fun operatorName() = testMathML(
+ "sin(1)",
+ "sin1",
+ )
+
+ @Test
+ fun specialSymbol() = testMathML(MST.Numeric(Double.POSITIVE_INFINITY), "∞")
+
+ @Test
+ fun operand() {
+ testMathML(
+ "sin(1)",
+ "sin1",
+ )
+
+ testMathML("1+1", "1+1")
+ }
+
+ @Test
+ fun unaryOperator() = testMathML(
+ "sin(1)",
+ "sin1",
+ )
+
+ @Test
+ fun unaryPlus() =
+ testMathML(MST.Unary(GroupOperations.PLUS_OPERATION, MST.Numeric(1)), "+1")
+
+ @Test
+ fun unaryMinus() = testMathML("-x", "-x")
+
+ @Test
+ fun radical() = testMathML("sqrt(x)", "x")
+
+ @Test
+ fun superscript() = testMathML("x^y", "xy")
+
+ @Test
+ fun subscript() = testMathML(
+ SubscriptSyntax("", SymbolSyntax("x"), NumberSyntax("123")),
+ "x123",
+ )
+
+ @Test
+ fun binaryOperator() = testMathML(
+ "f(x, y)",
+ "fx,y",
+ )
+
+ @Test
+ fun binaryPlus() = testMathML("x+x", "x+x")
+
+ @Test
+ fun binaryMinus() = testMathML("x-x", "x-x")
+
+ @Test
+ fun fraction() = testMathML("x/x", "xx")
+
+ @Test
+ fun radicalWithIndex() =
+ testMathML(RadicalWithIndexSyntax("", SymbolSyntax("x"), SymbolSyntax("y")),
+ "yx")
+
+ @Test
+ fun multiplication() {
+ testMathML("x*1", "x×1")
+ testMathML("1*x", "1x")
+ }
+}
diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt
new file mode 100644
index 000000000..56a799c2c
--- /dev/null
+++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestStages.kt
@@ -0,0 +1,28 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.rendering.TestUtils.testLatex
+import kotlin.test.Test
+
+internal class TestStages {
+ @Test
+ fun betterMultiplication() {
+ testLatex("a*1", "a\\times1")
+ testLatex("1*(2/3)", "1\\times\\left(\\frac{2}{3}\\right)")
+ testLatex("1*1", "1\\times1")
+ testLatex("2e10", "2\\times10^{10}")
+ testLatex("2*x", "2\\,x")
+ testLatex("2*(x+1)", "2\\,\\left(x+1\\right)")
+ testLatex("x*y", "x\\,y")
+ }
+
+ @Test
+ fun parentheses() {
+ testLatex("(x+1)", "x+1")
+ testLatex("x*x*x", "x\\,x\\,x")
+ testLatex("(x+x)*x", "\\left(x+x\\right)\\,x")
+ testLatex("x+x*x", "x+x\\,x")
+ testLatex("x+x^x*x+x", "x+x^{x}\\,x+x")
+ testLatex("(x+x)^x+x*x", "\\left(x+x\\right)^{x}+x\\,x")
+ testLatex("x^(x+x)", "x^{x+x}")
+ }
+}
diff --git a/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt
new file mode 100644
index 000000000..e6359bcc9
--- /dev/null
+++ b/kmath-ast/src/jvmTest/kotlin/space/kscience/kmath/ast/rendering/TestUtils.kt
@@ -0,0 +1,41 @@
+package space.kscience.kmath.ast.rendering
+
+import space.kscience.kmath.ast.MST
+import space.kscience.kmath.ast.parseMath
+import kotlin.test.assertEquals
+
+internal object TestUtils {
+ private fun mathSyntax(mst: MST) = FeaturedMathRendererWithPostProcess.Default.render(mst)
+ private fun latex(mst: MST) = LatexSyntaxRenderer.renderWithStringBuilder(mathSyntax(mst))
+ private fun mathML(mst: MST) = MathMLSyntaxRenderer.renderWithStringBuilder(mathSyntax(mst))
+
+ internal fun testLatex(mst: MST, expectedLatex: String) = assertEquals(
+ expected = expectedLatex,
+ actual = latex(mst),
+ )
+
+ internal fun testLatex(expression: String, expectedLatex: String) = assertEquals(
+ expected = expectedLatex,
+ actual = latex(expression.parseMath()),
+ )
+
+ internal fun testLatex(expression: MathSyntax, expectedLatex: String) = assertEquals(
+ expected = expectedLatex,
+ actual = LatexSyntaxRenderer.renderWithStringBuilder(expression),
+ )
+
+ internal fun testMathML(mst: MST, expectedMathML: String) = assertEquals(
+ expected = "",
+ actual = mathML(mst),
+ )
+
+ internal fun testMathML(expression: String, expectedMathML: String) = assertEquals(
+ expected = "",
+ actual = mathML(expression.parseMath()),
+ )
+
+ internal fun testMathML(expression: MathSyntax, expectedMathML: String) = assertEquals(
+ expected = "",
+ actual = MathMLSyntaxRenderer.renderWithStringBuilder(expression),
+ )
+}
diff --git a/kmath-complex/README.md b/kmath-complex/README.md
index d7b2937fd..ec5bf289f 100644
--- a/kmath-complex/README.md
+++ b/kmath-complex/README.md
@@ -8,7 +8,7 @@ Complex and hypercomplex number systems in KMath.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-complex:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -19,7 +19,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-complex:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-complex:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -31,6 +31,6 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-complex:0.3.0-dev-3")
+ implementation("space.kscience:kmath-complex:0.3.0-dev-4")
}
```
diff --git a/kmath-core/README.md b/kmath-core/README.md
index 096c7d833..5e4f1765d 100644
--- a/kmath-core/README.md
+++ b/kmath-core/README.md
@@ -15,7 +15,7 @@ performance calculations to code generation.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-core:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -26,7 +26,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-core:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-core:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -38,6 +38,6 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-core:0.3.0-dev-3")
+ implementation("space.kscience:kmath-core:0.3.0-dev-4")
}
```
diff --git a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt
index 18fbf0fdd..817bc9f9c 100644
--- a/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt
+++ b/kmath-core/src/commonMain/kotlin/space/kscience/kmath/operations/BigInt.kt
@@ -18,7 +18,8 @@ private typealias TBase = ULong
/**
* Kotlin Multiplatform implementation of Big Integer numbers (KBigInteger).
*
- * @author Robert Drynkin (https://github.com/robdrynkin) and Peter Klimai (https://github.com/pklimai)
+ * @author Robert Drynkin
+ * @author Peter Klimai
*/
@OptIn(UnstableKMathAPI::class)
public object BigIntField : Field, NumbersAddOperations, ScaleOperations {
diff --git a/kmath-ejml/README.md b/kmath-ejml/README.md
index 2551703a4..1f13a03c5 100644
--- a/kmath-ejml/README.md
+++ b/kmath-ejml/README.md
@@ -9,7 +9,7 @@ EJML based linear algebra implementation.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-ejml:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -20,7 +20,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-ejml:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-ejml:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -32,6 +32,6 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-ejml:0.3.0-dev-3")
+ implementation("space.kscience:kmath-ejml:0.3.0-dev-4")
}
```
diff --git a/kmath-for-real/README.md b/kmath-for-real/README.md
index ad3d33062..f9c6ed3a0 100644
--- a/kmath-for-real/README.md
+++ b/kmath-for-real/README.md
@@ -9,7 +9,7 @@ Specialization of KMath APIs for Double numbers.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-for-real:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -20,7 +20,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-for-real:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-for-real:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -32,6 +32,6 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-for-real:0.3.0-dev-3")
+ implementation("space.kscience:kmath-for-real:0.3.0-dev-4")
}
```
diff --git a/kmath-functions/README.md b/kmath-functions/README.md
index 531e97a44..1e4b06e0f 100644
--- a/kmath-functions/README.md
+++ b/kmath-functions/README.md
@@ -10,7 +10,7 @@ Functions and interpolations.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-functions:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -21,7 +21,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-functions:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-functions:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -33,6 +33,6 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-functions:0.3.0-dev-3")
+ implementation("space.kscience:kmath-functions:0.3.0-dev-4")
}
```
diff --git a/kmath-nd4j/README.md b/kmath-nd4j/README.md
index 938d05c33..c8944f1ab 100644
--- a/kmath-nd4j/README.md
+++ b/kmath-nd4j/README.md
@@ -9,7 +9,7 @@ ND4J based implementations of KMath abstractions.
## Artifact:
-The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-3`.
+The Maven coordinates of this project are `space.kscience:kmath-nd4j:0.3.0-dev-4`.
**Gradle:**
```gradle
@@ -20,7 +20,7 @@ repositories {
}
dependencies {
- implementation 'space.kscience:kmath-nd4j:0.3.0-dev-3'
+ implementation 'space.kscience:kmath-nd4j:0.3.0-dev-4'
}
```
**Gradle Kotlin DSL:**
@@ -32,7 +32,7 @@ repositories {
}
dependencies {
- implementation("space.kscience:kmath-nd4j:0.3.0-dev-3")
+ implementation("space.kscience:kmath-nd4j:0.3.0-dev-4")
}
```