Skip to content

Commit

Permalink
Merge pull request #24 from notKamui/dev
Browse files Browse the repository at this point in the history
Added support for builder pattern
  • Loading branch information
notKamui authored Feb 9, 2021
2 parents 295e709 + 204c5ed commit d3aff0f
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 31 deletions.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ properly, with a DSL (Domain Specific Language):
Keval will use the built-in operators, function and constants if you choose not to define any new resource ; but if you
choose to do so, you need to include them manually. You may also choose to use Keval as an extension function.

You can use it in four ways:
You can use it in several ways:

```Kotlin

Expand Down Expand Up @@ -139,6 +139,31 @@ Keval { // DSL instance
The advantage of using `Keval {}` is that you may keep an instance of it in a variable so that you can call as
many `eval` as you need.

In concordance with creating a Keval instance, you can also add resources like this:

```Kotlin
val kvl = Keval()
.withDefault() // includes default resources // it is unnecessary here since Keval() with no DSL already does it
.withOperator( // includes a new binary operator
';', // symbol
3, // precedence
true // isLeftAssociative
) { a, b -> a.pow(2) + b.pow(2) } // implementation
.withFunction( // includes a new function
"max", // name
2 // arity
) { max(it[0], it[1]) } // implementation
.withConstant( // includes a new constant
"PHI", // name
1.618 // value
)

kvl.eval("2*max(2, 3) ; 4 + PHI^2")
```

This can be combined with creating an instance with a DSL (i.e. `Keval {}`).
***This is an especially useful syntax for Java users, since DSLs generally don't translate well over it.***

Creating a resource with a name that already exists will overwrite the previous one.

Keval assumes products/multiplications, and as such, the * symbol/name ***cannot*** be overwritten, and is the only
Expand Down
2 changes: 1 addition & 1 deletion keval/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.net.URL

group = "com.notkamui.libs"
version = "0.7.2"
version = "0.7.3"

plugins {
kotlin("jvm") version "1.4.30"
Expand Down
140 changes: 111 additions & 29 deletions keval/src/main/kotlin/com/notkamui/keval/Keval.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,96 @@ package com.notkamui.keval
* Wrapper class for Keval,
* Contains a companion object with the evaluation method
*
* @property generator is the DSL generator of Keval (defaults to the default resources)
* @param generator is the DSL generator of Keval (defaults to the default resources)
*/
class Keval(
private val generator: KevalDSL.() -> Unit = { includeDefault() }
class Keval
@Throws(KevalDSLException::class)
constructor(
generator: KevalDSL.() -> Unit = { includeDefault() }
) {
companion object {
/**
* Evaluates a mathematical expression to a double value
*
* @param mathExpression is the expression to evaluate
* @return the value of the expression
* @throws KevalInvalidSymbolException in case there's an invalid operator in the expression
* @throws KevalInvalidExpressionException in case the expression is invalid (i.e. mismatched parenthesis)
* @throws KevalZeroDivisionException in case of a zero division
*/
@Throws(
KevalInvalidSymbolException::class,
KevalInvalidSymbolException::class,
KevalZeroDivisionException::class
)
fun eval(
mathExpression: String,
): Double {
return mathExpression.toAbstractSyntaxTree(KevalDSL.DEFAULT_RESOURCES).eval()
private val kevalDSL = KevalDSL()

init {
kevalDSL.generator()
}

/**
* Composes a binary operator to Keval
*
* @param symbol is the symbol which represents the operator
* @param precedence is the precedence of the operator
* @param isLeftAssociative is true when the operator is left associative, false otherwise
* @param implementation is the actual implementation of the operator
* @return the Keval instance
* @throws KevalDSLException if at least one of the field isn't set properly
*/
@Throws(KevalDSLException::class)
fun withOperator(
symbol: Char,
precedence: Int,
isLeftAssociative: Boolean,
implementation: (Double, Double) -> Double
): Keval {
kevalDSL.operator {
this.symbol = symbol
this.precedence = precedence
this.isLeftAssociative = isLeftAssociative
this.implementation = implementation
}
return this
}

/**
* Composes a function to Keval
*
* @param name is the identifier which represents the function
* @param arity is the arity of the function (how many arguments it takes)
* @param implementation is the actual implementation of the function
* @return the Keval instance
* @throws KevalDSLException if at least one of the field isn't set properly
*/
@Throws(KevalDSLException::class)
fun withFunction(
name: String,
arity: Int,
implementation: (DoubleArray) -> Double
): Keval {
kevalDSL.function {
this.name = name
this.arity = arity
this.implementation = implementation
}
return this
}

/**
* Composes a constant to Keval
*
* @param name is the identifier which represents the constant
* @param value is the value of the constant
* @return the Keval instance
* @throws KevalDSLException if at least one of the field isn't set properly
*/
@Throws(KevalDSLException::class)
fun withConstant(
name: String,
value: Double
): Keval {
kevalDSL.constant {
this.name = name
this.value = value
}
return this
}

/**
* Composes the default resources to Keval
*
* @return the Keval instance
*/
fun withDefault(): Keval {
kevalDSL.includeDefault()
return this
}

/**
Expand All @@ -39,26 +104,43 @@ class Keval(
* @throws KevalInvalidSymbolException in case there's an invalid operator in the expression
* @throws KevalInvalidExpressionException in case the expression is invalid (i.e. mismatched parenthesis)
* @throws KevalZeroDivisionException in case of a zero division
* @throws KevalDSLException if at least one of the field isn't set properly
*/
@Throws(
KevalInvalidSymbolException::class,
KevalInvalidSymbolException::class,
KevalZeroDivisionException::class,
KevalDSLException::class
KevalZeroDivisionException::class
)
fun eval(
mathExpression: String,
): Double {
val resources = KevalDSL()
resources.generator()

// The tokenizer assumes multiplication, hence disallowing overriding `*` operator
val operators = resources.resources
val operators = kevalDSL.resources
.plus("*" to KevalBinaryOperator(3, true) { a, b -> a * b })

return mathExpression.toAbstractSyntaxTree(operators).eval()
}

companion object {
/**
* Evaluates a mathematical expression to a double value
*
* @param mathExpression is the expression to evaluate
* @return the value of the expression
* @throws KevalInvalidSymbolException in case there's an invalid operator in the expression
* @throws KevalInvalidExpressionException in case the expression is invalid (i.e. mismatched parenthesis)
* @throws KevalZeroDivisionException in case of a zero division
*/
@Throws(
KevalInvalidSymbolException::class,
KevalInvalidSymbolException::class,
KevalZeroDivisionException::class
)
fun eval(
mathExpression: String,
): Double {
return mathExpression.toAbstractSyntaxTree(KevalDSL.DEFAULT_RESOURCES).eval()
}
}
}

/**
Expand Down
36 changes: 36 additions & 0 deletions keval/src/test/kotlin/com/notkamui/keval/DSLResourcesTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,40 @@ class DLSTest {
kvl.eval("ab_c(1)")
)
}

@Test
fun checkWith() {
val kvl = Keval()
.withDefault()
.withOperator(
';',
3,
isLeftAssociative = true,
::hypotenuse
).withFunction(
"max",
2
) { max(it[0], it[1]) }
.withConstant("PHI", 1.618)

assertEquals(
9.0,
kvl.eval("max(4, 2) + 5")
)

assertEquals(
5.0,
kvl.eval("neg(5) + 10")
)

assertEquals(
8.85663593,
(kvl.eval("((3;4)-1.2);8") * 10.0.pow(8)).toInt() / 10.0.pow(8)
)

assertEquals(
2.6179240000000004,
kvl.eval("PHI^2")
)
}
}

0 comments on commit d3aff0f

Please sign in to comment.