[orx-expression-evaluator] Switch to antlr-kotlin for parser generation and make it a common kotlin module
This commit is contained in:
2
orx-jvm/orx-expression-evaluator/.gitignore
vendored
2
orx-jvm/orx-expression-evaluator/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*.tokens
|
||||
gen/
|
||||
@@ -1,93 +0,0 @@
|
||||
# orx-expression-evaluator
|
||||
|
||||
Tools to evaluate strings containing mathematical expressions.
|
||||
|
||||
# Expression evaluator
|
||||
|
||||
```kotlin
|
||||
val expression = "x + y"
|
||||
val constants = mapOf("x" to 1.0, "y" to 2.0)
|
||||
evaluateExpression(expression, constants)
|
||||
```
|
||||
## Built-in expression functions
|
||||
|
||||
Unary functions:
|
||||
* `abs(x)`
|
||||
* `acos(x)`
|
||||
* `asin(x)`
|
||||
* `atan(x)`
|
||||
* `ceil(x)`
|
||||
* `cos(x)`
|
||||
* `degrees(x)`
|
||||
* `exps(x)`
|
||||
* `floor(x)`
|
||||
* `radians(x)`
|
||||
* `round(x)`
|
||||
* `saturate(x)`, clamp x to [0.0, 1.0]
|
||||
* `sqrt(x)`
|
||||
* `tan(x)`
|
||||
|
||||
Binary functions:
|
||||
* `atan2(x, y)`
|
||||
* `length(x, y)`, the Euclidean length of the vector (x,y)
|
||||
* `max(x, y)`,
|
||||
* `min(x, y)`,
|
||||
* `pow(x, n)`
|
||||
* `random(x, y)`, return a random number in [x, y)
|
||||
|
||||
Ternary functions:
|
||||
* `length(x, y, z)`, the Euclidean length of the vector (x, y, z)
|
||||
* `max(x, y, z)`
|
||||
* `min(x, y, z)`
|
||||
* `mix(l, r, f)`
|
||||
* `smoothstep(e0, e1, x)`
|
||||
* `sum(x, y, z)`
|
||||
|
||||
Quaternary functions:
|
||||
* `length(x, y, z, w)`, the Euclidean length of the vector (x, y, z)
|
||||
* `max(a, b, c, d)`
|
||||
* `min(a, b, c, d)`
|
||||
* `sum(a, b, c, d)`
|
||||
|
||||
Quinary functions:
|
||||
* `map(x0, x1, y0, y1, v)`
|
||||
* `max(a, b, c, d, e)`
|
||||
* `min(a, b, c, d, e)`
|
||||
* `sum(a, b, c, d, e)`
|
||||
|
||||
# Compiled functions
|
||||
|
||||
```kotlin
|
||||
val expression = "x * 5.0 + cos(x)"
|
||||
val f = compileFunction1(expression, "x")
|
||||
f(0.0)
|
||||
```
|
||||
|
||||
```kotlin
|
||||
val expression = "x * 5.0 + cos(x) * y"
|
||||
val f = compileFunction2(expression, "x", "y")
|
||||
f(0.0, 0.4)
|
||||
```
|
||||
|
||||
# Property delegates
|
||||
|
||||
```kotlin
|
||||
val constants = mutableMapOf("width" to 300.0)
|
||||
val settings = object {
|
||||
var xExpression = "cos(t) * 50.0 + width / 2.0"
|
||||
}
|
||||
val xFunction by watchingExpression1(settings::xExpression, "t", constants)
|
||||
|
||||
xFunction(1.0)
|
||||
```
|
||||
<!-- __demos__ -->
|
||||
## Demos
|
||||
### DemoExpressionEvaluator01
|
||||
[source code](src/demo/kotlin/DemoExpressionEvaluator01.kt)
|
||||
|
||||

|
||||
|
||||
### DemoExpressionEvaluator02
|
||||
[source code](src/demo/kotlin/DemoExpressionEvaluator02.kt)
|
||||
|
||||

|
||||
@@ -1,33 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
org.openrndr.extra.convention.`kotlin-jvm`
|
||||
antlr
|
||||
}
|
||||
|
||||
tasks.generateGrammarSource {
|
||||
maxHeapSize = "64m"
|
||||
arguments.addAll(listOf("-visitor", "-long-messages"))
|
||||
}
|
||||
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
antlr(libs.antlr.core)
|
||||
implementation(libs.antlr.runtime)
|
||||
implementation(libs.openrndr.application)
|
||||
implementation(libs.openrndr.math)
|
||||
implementation(libs.kotlin.coroutines)
|
||||
implementation(project(":orx-property-watchers"))
|
||||
implementation(project(":orx-noise"))
|
||||
testImplementation(libs.kluent)
|
||||
demoImplementation(project(":orx-jvm:orx-gui"))
|
||||
}
|
||||
|
||||
tasks.getByName("compileKotlin").dependsOn("generateGrammarSource")
|
||||
tasks.getByName("compileDemoKotlin").dependsOn("generateDemoGrammarSource")
|
||||
tasks.getByName("compileTestKotlin").dependsOn("generateTestGrammarSource")
|
||||
tasks.getByName("sourcesJar").dependsOn("generateGrammarSource")
|
||||
@@ -1,46 +0,0 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.expressions.evaluateExpression
|
||||
import org.openrndr.extra.gui.GUI
|
||||
import org.openrndr.extra.gui.addTo
|
||||
import org.openrndr.extra.parameters.TextParameter
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
val gui = GUI()
|
||||
gui.compartmentsCollapsedByDefault = false
|
||||
|
||||
val settings = object {
|
||||
@TextParameter("x expression", order = 10)
|
||||
var xExpression = "cos(t) * 50.0 + width / 2.0"
|
||||
|
||||
@TextParameter("y expression", order = 20)
|
||||
var yExpression = "sin(t) * 50.0 + height / 2.0"
|
||||
|
||||
@TextParameter("radius expression", order = 30)
|
||||
var radiusExpression = "cos(t) * 50.0 + 50.0"
|
||||
}.addTo(gui)
|
||||
|
||||
extend(gui)
|
||||
extend {
|
||||
//gui.visible = mouse.position.x < 200.0
|
||||
|
||||
val expressionContext =
|
||||
mapOf("t" to seconds, "width" to drawer.bounds.width, "height" to drawer.bounds.height)
|
||||
|
||||
fun eval(expression: String): Double =
|
||||
try {
|
||||
evaluateExpression(expression, expressionContext) ?: 0.0
|
||||
} catch (e: Throwable) {
|
||||
0.0
|
||||
}
|
||||
|
||||
val x = eval(settings.xExpression)
|
||||
val y = eval(settings.yExpression)
|
||||
val radius = eval(settings.radiusExpression)
|
||||
|
||||
drawer.circle(x, y, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import org.openrndr.application
|
||||
import org.openrndr.extra.expressions.evaluateExpression
|
||||
import org.openrndr.extra.expressions.watchingExpression1
|
||||
import org.openrndr.extra.gui.GUI
|
||||
import org.openrndr.extra.gui.addTo
|
||||
import org.openrndr.extra.parameters.TextParameter
|
||||
|
||||
/**
|
||||
* Improved version of DemoExpressionEvaluator01, it uses [watchingExpression1] to automatically convert an expression
|
||||
* string into a function with a parameter "t".
|
||||
*/
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
val gui = GUI()
|
||||
gui.compartmentsCollapsedByDefault = false
|
||||
|
||||
// the constants used in our expressions
|
||||
val constants = mutableMapOf("width" to drawer.width.toDouble(), "height" to drawer.height.toDouble())
|
||||
|
||||
val settings = object {
|
||||
@TextParameter("x expression", order = 10)
|
||||
var xExpression = "cos(t) * 50.0 + width / 2.0"
|
||||
|
||||
@TextParameter("y expression", order = 20)
|
||||
var yExpression = "sin(t) * 50.0 + height / 2.0"
|
||||
|
||||
@TextParameter("radius expression", order = 30)
|
||||
var radiusExpression = "cos(t) * 50.0 + 50.0"
|
||||
}.addTo(gui)
|
||||
|
||||
val xFunction by watchingExpression1(settings::xExpression, "t", constants)
|
||||
val yFunction by watchingExpression1(settings::yExpression, "t", constants)
|
||||
val radiusFunction by watchingExpression1(settings::radiusExpression, "t", constants)
|
||||
|
||||
extend(gui)
|
||||
extend {
|
||||
val x = xFunction(seconds)
|
||||
val y = yFunction(seconds)
|
||||
val radius = radiusFunction(seconds)
|
||||
drawer.circle(x, y, radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
lexer grammar KeyLangLexer;
|
||||
|
||||
channels { WHITESPACE }
|
||||
|
||||
// Whitespace
|
||||
NEWLINE : '\r\n' | '\r' | '\n' ;
|
||||
WS : [\t ]+ -> channel(WHITESPACE) ;
|
||||
|
||||
// Keywords
|
||||
INPUT : 'input' ;
|
||||
VAR : 'var' ;
|
||||
PRINT : 'print';
|
||||
AS : 'as';
|
||||
INT : 'Int';
|
||||
DECIMAL : 'Decimal';
|
||||
STRING : 'String';
|
||||
|
||||
// Identifiers
|
||||
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* | '`'[$_]*[A-Za-z0-9_-]*'`';
|
||||
FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ;
|
||||
|
||||
// Literals
|
||||
|
||||
DECLIT : [0-9][0-9]* '.' [0-9]+ ;
|
||||
INTLIT : '0'|[0-9][0-9]* ;
|
||||
|
||||
// Operators
|
||||
PLUS : '+' ;
|
||||
PERCENTAGE : '%' ;
|
||||
MINUS : '-' ;
|
||||
ASTERISK : '*' ;
|
||||
DIVISION : '/' ;
|
||||
ASSIGN : '=' ;
|
||||
LPAREN : '(' ;
|
||||
RPAREN : ')' ;
|
||||
|
||||
|
||||
COMMA : ',' ;
|
||||
|
||||
STRING_OPEN : '"' -> pushMode(MODE_IN_STRING);
|
||||
|
||||
UNMATCHED : . ;
|
||||
|
||||
mode MODE_IN_STRING;
|
||||
|
||||
ESCAPE_STRING_DELIMITER : '\\"' ;
|
||||
ESCAPE_SLASH : '\\\\' ;
|
||||
ESCAPE_NEWLINE : '\\n' ;
|
||||
ESCAPE_SHARP : '\\#' ;
|
||||
STRING_CLOSE : '"' -> popMode ;
|
||||
STRING_CONTENT : ~["\n\r\t\\#]+ ;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
parser grammar KeyLangParser;
|
||||
|
||||
options { tokenVocab=KeyLangLexer; }
|
||||
|
||||
keyLangFile : lines=line+ ;
|
||||
|
||||
line : statement (NEWLINE | EOF) ;
|
||||
|
||||
statement : inputDeclaration # inputDeclarationStatement
|
||||
| varDeclaration # varDeclarationStatement
|
||||
| assignment # assignmentStatement
|
||||
| print # printStatement
|
||||
| expression # expressionStatement ;
|
||||
|
||||
print : PRINT LPAREN expression RPAREN ;
|
||||
|
||||
inputDeclaration : INPUT type name=ID ;
|
||||
|
||||
varDeclaration : VAR assignment ;
|
||||
|
||||
assignment : ID ASSIGN expression ;
|
||||
|
||||
expression : INTLIT # intLiteral
|
||||
| DECLIT # decimalLiteral
|
||||
| ID LPAREN RPAREN # functionCall0Expression
|
||||
| ID LPAREN expression RPAREN # functionCall1Expression
|
||||
| ID LPAREN expression COMMA expression RPAREN # functionCall2Expression
|
||||
| ID LPAREN expression COMMA expression COMMA expression RPAREN # functionCall3Expression
|
||||
| ID LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN # functionCall4Expression
|
||||
| ID LPAREN expression COMMA expression COMMA expression COMMA expression COMMA expression RPAREN # functionCall5Expression
|
||||
| ID # valueReference
|
||||
| LPAREN expression RPAREN # parenExpression
|
||||
| MINUS expression # minusExpression
|
||||
| expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1
|
||||
| expression operator=(PLUS|MINUS) expression # binaryOperation2;
|
||||
|
||||
type : DECIMAL # decimal
|
||||
| INT # integer
|
||||
| STRING # string ;
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package org.openrndr.extra.expressions
|
||||
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker
|
||||
|
||||
/**
|
||||
* Compile a (Double)->Double function from an expression string
|
||||
* @param expression the expression string to be compiled
|
||||
* @param parameter0 the name of the first parameter
|
||||
* @param constants a map of named constant values that can be referred from the expression
|
||||
* @param functions a map of named functions that can be invoked from the expression
|
||||
* @param error in case the expression fails to compile or evaluate, this function is invoked instead
|
||||
*/
|
||||
fun compileFunction1(
|
||||
expression: String,
|
||||
parameter0: String,
|
||||
constants: Map<String, Double> = mapOf(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (Double) -> Double = { 0.0 },
|
||||
): (Double) -> Double {
|
||||
require(!constants.containsKey(parameter0))
|
||||
try {
|
||||
val root = expressionRoot(expression)
|
||||
val variables = mutableMapOf<String, Double>()
|
||||
variables.putAll(constants)
|
||||
val listener = ExpressionListener(functions, variables)
|
||||
|
||||
return { p0 ->
|
||||
variables[parameter0] = p0
|
||||
try {
|
||||
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||
listener.lastExpressionResult ?: error("no result")
|
||||
} catch (e: ExpressionException) {
|
||||
error(p0)
|
||||
}
|
||||
}
|
||||
} catch (e: ExpressionException) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a (Double, Double)->Double function from an expression string
|
||||
* @param expression the expression string to be compiled
|
||||
* @param parameter0 the name of the first parameter
|
||||
* @param parameter1 the name of the second parameter
|
||||
* @param constants a map of named constant values that can be referred from the expression
|
||||
* @param functions a map of named functions that can be invoked from the expression
|
||||
* @param error in case the expression fails to compile or evaluate, this function is invoked instead
|
||||
*/
|
||||
fun compileFunction2(
|
||||
expression: String,
|
||||
parameter0: String,
|
||||
parameter1: String,
|
||||
constants: Map<String, Double> = mapOf(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (p0: Double, p1: Double) -> Double = { _, _ -> 0.0 },
|
||||
): (p0: Double, p1: Double) -> Double {
|
||||
require(!constants.containsKey(parameter0))
|
||||
require(!constants.containsKey(parameter1))
|
||||
try {
|
||||
val root = expressionRoot(expression)
|
||||
val variables = mutableMapOf<String, Double>()
|
||||
variables.putAll(constants)
|
||||
val listener = ExpressionListener(functions, variables)
|
||||
|
||||
return { p0, p1 ->
|
||||
variables[parameter0] = p0
|
||||
variables[parameter1] = p1
|
||||
try {
|
||||
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||
listener.lastExpressionResult ?: error("no result")
|
||||
} catch (e: ExpressionException) {
|
||||
error(p0, p1)
|
||||
}
|
||||
}
|
||||
} catch (e: ExpressionException) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a (Double, Double, Double)->Double function from an expression string
|
||||
* @param expression the expression string to be compiled
|
||||
* @param parameter0 the name of the first parameter
|
||||
* @param parameter1 the name of the second parameter
|
||||
* @param parameter2 the name of the third parameter
|
||||
* @param constants a map of named constant values that can be referred from the expression
|
||||
* @param functions a map of named functions that can be invoked from the expression
|
||||
* @param error in case the expression fails to compile or evaluate, this function is invoked instead
|
||||
*/
|
||||
fun compileFunction3(
|
||||
expression: String,
|
||||
parameter0: String,
|
||||
parameter1: String,
|
||||
parameter2: String,
|
||||
constants: Map<String, Double> = mapOf(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (p0: Double, p1: Double, p2: Double) -> Double = { _, _, _ -> 0.0 }
|
||||
): (p0: Double, p1: Double, p2: Double) -> Double {
|
||||
require(!constants.containsKey(parameter0))
|
||||
require(!constants.containsKey(parameter1))
|
||||
require(!constants.containsKey(parameter2))
|
||||
|
||||
try {
|
||||
val root = expressionRoot(expression)
|
||||
val variables = mutableMapOf<String, Double>()
|
||||
variables.putAll(constants)
|
||||
val listener = ExpressionListener(functions, variables)
|
||||
|
||||
return { p0, p1, p2 ->
|
||||
variables[parameter0] = p0
|
||||
variables[parameter1] = p1
|
||||
variables[parameter2] = p2
|
||||
try {
|
||||
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||
listener.lastExpressionResult ?: error("no result")
|
||||
} catch (e: ExpressionException) {
|
||||
error(p0, p1, p2)
|
||||
}
|
||||
}
|
||||
} catch(e: ExpressionException) {
|
||||
return error
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.openrndr.extra.expressions
|
||||
|
||||
import org.openrndr.extra.propertywatchers.watchingProperty
|
||||
import kotlin.reflect.KProperty0
|
||||
|
||||
fun watchingExpression1(
|
||||
expressionProperty: KProperty0<String>,
|
||||
parameter0: String = "x",
|
||||
constants: Map<String, Double> = emptyMap(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (p0: Double) -> Double = { 0.0 }
|
||||
) =
|
||||
watchingProperty(expressionProperty) {
|
||||
compileFunction1(it, parameter0, constants, functions, error)
|
||||
}
|
||||
|
||||
fun watchingExpression2(
|
||||
expressionProperty: KProperty0<String>,
|
||||
parameter0: String = "x",
|
||||
parameter1: String = "y",
|
||||
constants: Map<String, Double> = emptyMap(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (p0: Double, p1: Double) -> Double = { _, _ -> 0.0 }
|
||||
) =
|
||||
watchingProperty(expressionProperty) {
|
||||
compileFunction2(it, parameter0, parameter1, constants, functions, error)
|
||||
}
|
||||
|
||||
fun watchingExpression3(
|
||||
expressionProperty: KProperty0<String>,
|
||||
parameter0: String = "x",
|
||||
parameter1: String = "y",
|
||||
parameter2: String = "z",
|
||||
constants: Map<String, Double> = emptyMap(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
error: (p0: Double, p1: Double, p2: Double) -> Double = { _, _, _ -> 0.0 }
|
||||
) =
|
||||
watchingProperty(expressionProperty) {
|
||||
compileFunction3(it, parameter0, parameter1, parameter2, constants, functions, error)
|
||||
}
|
||||
@@ -1,485 +0,0 @@
|
||||
package org.openrndr.extra.expressions
|
||||
|
||||
import KeyLangLexer
|
||||
import KeyLangParser
|
||||
import KeyLangParserBaseListener
|
||||
import org.antlr.v4.runtime.*
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker
|
||||
import org.antlr.v4.runtime.tree.TerminalNode
|
||||
|
||||
import org.openrndr.extra.noise.uniform
|
||||
import org.openrndr.math.*
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
typealias Function0 = () -> Double
|
||||
typealias Function1 = (Double) -> Double
|
||||
typealias Function2 = (Double, Double) -> Double
|
||||
typealias Function3 = (Double, Double, Double) -> Double
|
||||
typealias Function4 = (Double, Double, Double, Double) -> Double
|
||||
typealias Function5 = (Double, Double, Double, Double, Double) -> Double
|
||||
|
||||
class FunctionExtensions(
|
||||
val functions0: Map<String, Function0> = emptyMap(),
|
||||
val functions1: Map<String, Function1> = emptyMap(),
|
||||
val functions2: Map<String, Function2> = emptyMap(),
|
||||
val functions3: Map<String, Function3> = emptyMap(),
|
||||
val functions4: Map<String, Function4> = emptyMap(),
|
||||
val functions5: Map<String, Function5> = emptyMap()
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = FunctionExtensions()
|
||||
}
|
||||
}
|
||||
|
||||
internal enum class IDType {
|
||||
VARIABLE,
|
||||
FUNCTION0,
|
||||
FUNCTION1,
|
||||
FUNCTION2,
|
||||
FUNCTION3,
|
||||
FUNCTION4,
|
||||
FUNCTION5
|
||||
}
|
||||
|
||||
internal class ExpressionListener(
|
||||
val functions: FunctionExtensions = FunctionExtensions.EMPTY,
|
||||
val constants: Map<String, Double> = mapOf()
|
||||
) :
|
||||
KeyLangParserBaseListener() {
|
||||
val doubleStack = Stack<Double>()
|
||||
val functionStack = Stack<(DoubleArray) -> Double>()
|
||||
|
||||
|
||||
val idTypeStack = Stack<IDType>()
|
||||
var lastExpressionResult: Double? = null
|
||||
|
||||
val exceptionStack = Stack<ExpressionException>()
|
||||
|
||||
|
||||
override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) {
|
||||
ifError {
|
||||
throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}")
|
||||
}
|
||||
val result = doubleStack.pop()
|
||||
lastExpressionResult = result
|
||||
}
|
||||
|
||||
// override fun exitAssignment(ctx: KeyLangParser.AssignmentContext) {
|
||||
// val value = doubleStack.pop()
|
||||
// variables[ctx.ID()?.text ?: error("buh")] = value
|
||||
// }
|
||||
|
||||
override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) {
|
||||
val op = doubleStack.pop()
|
||||
doubleStack.push(-op)
|
||||
}
|
||||
|
||||
override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val right = doubleStack.pop()
|
||||
val left = doubleStack.pop()
|
||||
val result = when (val operator = ctx.operator?.type) {
|
||||
KeyLangParser.PLUS -> left + right
|
||||
KeyLangParser.MINUS -> left - right
|
||||
KeyLangParser.ASTERISK -> left * right
|
||||
KeyLangParser.DIVISION -> left / right
|
||||
KeyLangParser.PERCENTAGE -> mod(left, right)
|
||||
else -> error("operator '$operator' not implemented")
|
||||
}
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val left = doubleStack.pop()
|
||||
val right = doubleStack.pop()
|
||||
val result = when (val operator = ctx.operator?.type) {
|
||||
KeyLangParser.PLUS -> left + right
|
||||
KeyLangParser.MINUS -> right - left
|
||||
KeyLangParser.ASTERISK -> left * right
|
||||
KeyLangParser.DIVISION -> left / right
|
||||
else -> error("operator '$operator' not implemented")
|
||||
}
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) {
|
||||
idTypeStack.push(IDType.VARIABLE)
|
||||
}
|
||||
|
||||
override fun enterFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION0)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val result = function.invoke(doubleArrayOf())
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun enterFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION1)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val argument = doubleStack.pop()
|
||||
|
||||
val result = function.invoke(doubleArrayOf(argument))
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun enterFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION2)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val argument1 = doubleStack.pop()
|
||||
val argument0 = doubleStack.pop()
|
||||
|
||||
val result = function.invoke(doubleArrayOf(argument0, argument1))
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun enterFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION3)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val argument2 = doubleStack.pop()
|
||||
val argument1 = doubleStack.pop()
|
||||
val argument0 = doubleStack.pop()
|
||||
|
||||
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2))
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
override fun enterFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION4)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val argument3 = doubleStack.pop()
|
||||
val argument2 = doubleStack.pop()
|
||||
val argument1 = doubleStack.pop()
|
||||
val argument0 = doubleStack.pop()
|
||||
|
||||
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2, argument3))
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
|
||||
override fun enterFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) {
|
||||
idTypeStack.push(IDType.FUNCTION5)
|
||||
}
|
||||
|
||||
override fun exitFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) {
|
||||
ifError {
|
||||
pushError(it.message ?: "")
|
||||
return
|
||||
}
|
||||
|
||||
val function = functionStack.pop()
|
||||
val argument4 = doubleStack.pop()
|
||||
val argument3 = doubleStack.pop()
|
||||
val argument2 = doubleStack.pop()
|
||||
val argument1 = doubleStack.pop()
|
||||
val argument0 = doubleStack.pop()
|
||||
|
||||
val result = function.invoke(doubleArrayOf(argument0, argument1, argument2, argument3, argument4))
|
||||
doubleStack.push(result)
|
||||
}
|
||||
|
||||
private fun <T> errorValue(message: String, value: T): T {
|
||||
pushError(message)
|
||||
return value
|
||||
}
|
||||
|
||||
private fun pushError(message: String) {
|
||||
exceptionStack.push(ExpressionException(message))
|
||||
}
|
||||
|
||||
private inline fun ifError(f: (e: Throwable) -> Unit) {
|
||||
if (exceptionStack.isNotEmpty()) {
|
||||
val e = exceptionStack.pop()
|
||||
f(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitTerminal(node: TerminalNode) {
|
||||
val type = node.symbol?.type
|
||||
if (type == KeyLangParser.INTLIT) {
|
||||
doubleStack.push(node.text.toDouble())
|
||||
}
|
||||
if (type == KeyLangParser.DECLIT) {
|
||||
doubleStack.push(node.text.toDouble())
|
||||
}
|
||||
if (type == KeyLangParser.ID) {
|
||||
val name = node.text.replace("`", "")
|
||||
@Suppress("DIVISION_BY_ZERO")
|
||||
when (val idType = idTypeStack.pop()) {
|
||||
IDType.VARIABLE -> doubleStack.push(
|
||||
when (name) {
|
||||
"PI" -> PI
|
||||
else -> constants[name] ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0)
|
||||
}
|
||||
)
|
||||
|
||||
IDType.FUNCTION0 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"random" -> { _ -> Double.uniform(0.0, 1.0) }
|
||||
else -> functions.functions0[name]?.let { { _: DoubleArray -> it.invoke() } }
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}()'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
IDType.FUNCTION1 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"sqrt" -> { x -> sqrt(x[0]) }
|
||||
"radians" -> { x -> Math.toRadians(x[0]) }
|
||||
"degrees" -> { x -> Math.toDegrees(x[0]) }
|
||||
"cos" -> { x -> cos(x[0]) }
|
||||
"sin" -> { x -> sin(x[0]) }
|
||||
"tan" -> { x -> tan(x[0]) }
|
||||
"atan" -> { x -> atan(x[0]) }
|
||||
"acos" -> { x -> acos(x[0]) }
|
||||
"asin" -> { x -> asin(x[0]) }
|
||||
"exp" -> { x -> exp(x[0]) }
|
||||
"abs" -> { x -> abs(x[0]) }
|
||||
"floor" -> { x -> floor(x[0]) }
|
||||
"round" -> { x -> round(x[0]) }
|
||||
"ceil" -> { x -> ceil(x[0]) }
|
||||
"saturate" -> { x -> x[0].coerceIn(0.0, 1.0) }
|
||||
else -> functions.functions1[name]?.let { { x: DoubleArray -> it.invoke(x[0]) } }
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}(x0)'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
IDType.FUNCTION2 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"max" -> { x -> max(x[0], x[1]) }
|
||||
"min" -> { x -> min(x[0], x[1]) }
|
||||
"pow" -> { x -> x[0].pow(x[1]) }
|
||||
"mod" -> { x -> x[0].mod(x[1]) }
|
||||
"atan2" -> { x -> atan2(x[0], x[1]) }
|
||||
"random" -> { x -> Double.uniform(x[0], x[1]) }
|
||||
"length" -> { x -> Vector2(x[0], x[1]).length }
|
||||
else -> functions.functions2[name]?.let { { x: DoubleArray -> it.invoke(x[0], x[1]) } }
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}(x0, x1)'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
IDType.FUNCTION3 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"mix" -> { x -> mix(x[0], x[1], x[2]) }
|
||||
"min" -> { x -> x.minOrNull()!! }
|
||||
"max" -> { x -> x.maxOrNull()!! }
|
||||
"sum" -> { x -> x.sum() }
|
||||
"smoothstep" -> { x -> smoothstep(x[0], x[1], x[2]) }
|
||||
"length" -> { x -> Vector3(x[0], x[1], x[2]).length }
|
||||
else -> functions.functions3[name]?.let {
|
||||
{ x: DoubleArray ->
|
||||
it.invoke(
|
||||
x[0],
|
||||
x[1],
|
||||
x[2]
|
||||
)
|
||||
}
|
||||
}
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}(x0, x1, x2)'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
IDType.FUNCTION4 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"min" -> { x -> x.minOrNull()!! }
|
||||
"max" -> { x -> x.maxOrNull()!! }
|
||||
"sum" -> { x -> x.sum() }
|
||||
else -> functions.functions4[name]?.let {
|
||||
{ x: DoubleArray ->
|
||||
it.invoke(
|
||||
x[0],
|
||||
x[1],
|
||||
x[2],
|
||||
x[3]
|
||||
)
|
||||
}
|
||||
}
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}(x0, x1, x2, x3)'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
IDType.FUNCTION5 -> {
|
||||
val function: (DoubleArray) -> Double =
|
||||
when (name) {
|
||||
"min" -> { x -> x.minOrNull()!! }
|
||||
"max" -> { x -> x.maxOrNull()!! }
|
||||
"sum" -> { x -> x.sum() }
|
||||
"map" -> { x -> map(x[0], x[1], x[2], x[3], x[4]) }
|
||||
else -> functions.functions5[name]?.let {
|
||||
{ x: DoubleArray ->
|
||||
it.invoke(
|
||||
x[0],
|
||||
x[1],
|
||||
x[2],
|
||||
x[3],
|
||||
x[4]
|
||||
)
|
||||
}
|
||||
}
|
||||
?: errorValue(
|
||||
"unresolved function: '${name}(x0, x1, x2, x3, x4)'"
|
||||
) { _ -> error("this is the error function") }
|
||||
}
|
||||
functionStack.push(function)
|
||||
}
|
||||
|
||||
else -> error("unsupported id-type $idType")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionException(message: String) : RuntimeException(message)
|
||||
|
||||
fun evaluateExpression(
|
||||
expression: String,
|
||||
constants: Map<String, Double> = emptyMap(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||
): Double? {
|
||||
val lexer = KeyLangLexer(CharStreams.fromString(expression))
|
||||
val parser = KeyLangParser(CommonTokenStream(lexer))
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(object : BaseErrorListener() {
|
||||
override fun syntaxError(
|
||||
recognizer: Recognizer<*, *>?,
|
||||
offendingSymbol: Any?,
|
||||
line: Int,
|
||||
charPositionInLine: Int,
|
||||
msg: String?,
|
||||
e: RecognitionException?
|
||||
) {
|
||||
throw ExpressionException("parser error in expression: '$expression'; [line: $line, character: $charPositionInLine ${offendingSymbol?.let { ", near: $it" } ?: ""} ]")
|
||||
}
|
||||
})
|
||||
|
||||
val root = parser.keyLangFile()
|
||||
val listener = ExpressionListener(functions, constants)
|
||||
try {
|
||||
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||
} catch (e: ExpressionException) {
|
||||
throw ExpressionException(e.message ?: "")
|
||||
}
|
||||
return listener.lastExpressionResult
|
||||
}
|
||||
|
||||
fun compileExpression(
|
||||
expression: String,
|
||||
constants: Map<String, Double> = emptyMap(),
|
||||
functions: FunctionExtensions = FunctionExtensions.EMPTY
|
||||
): () -> Double {
|
||||
val lexer = KeyLangLexer(CharStreams.fromString(expression))
|
||||
val parser = KeyLangParser(CommonTokenStream(lexer))
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(object : BaseErrorListener() {
|
||||
override fun syntaxError(
|
||||
recognizer: Recognizer<*, *>?,
|
||||
offendingSymbol: Any?,
|
||||
line: Int,
|
||||
charPositionInLine: Int,
|
||||
msg: String?,
|
||||
e: RecognitionException?
|
||||
) {
|
||||
throw ExpressionException("parser error in expression: '$expression'; [line: $line, character: $charPositionInLine ${offendingSymbol?.let { ", near: $it" } ?: ""} ]")
|
||||
}
|
||||
})
|
||||
val root = parser.keyLangFile()
|
||||
val listener = ExpressionListener(functions, constants)
|
||||
|
||||
|
||||
return {
|
||||
try {
|
||||
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||
} catch (e: ExpressionException) {
|
||||
throw ExpressionException(e.message ?: "")
|
||||
}
|
||||
listener.lastExpressionResult ?: error("no result")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun expressionRoot(expression: String): KeyLangParser.KeyLangFileContext {
|
||||
val lexer = KeyLangLexer(CharStreams.fromString(expression))
|
||||
val parser = KeyLangParser(CommonTokenStream(lexer))
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(object : BaseErrorListener() {
|
||||
override fun syntaxError(
|
||||
recognizer: Recognizer<*, *>?,
|
||||
offendingSymbol: Any?,
|
||||
line: Int,
|
||||
charPositionInLine: Int,
|
||||
msg: String?,
|
||||
e: RecognitionException?
|
||||
) {
|
||||
throw ExpressionException("parser error in expression: '$expression'; [line: $line, character: $charPositionInLine ${offendingSymbol?.let { ", near: $it" } ?: ""} ]")
|
||||
}
|
||||
})
|
||||
return parser.keyLangFile()
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import org.amshove.kluent.invoking
|
||||
import org.amshove.kluent.`should throw`
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.openrndr.extra.expressions.ExpressionException
|
||||
import org.openrndr.extra.expressions.compileExpression
|
||||
|
||||
class TestCompiledExpression {
|
||||
@Test
|
||||
fun `a simple compiled expression`() {
|
||||
val expression = "someValue"
|
||||
val function = compileExpression(expression, constants = mutableMapOf("someValue" to 5.0))
|
||||
function().shouldBeEqualTo(5.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a compiled expression with updated context`() {
|
||||
val expression = "someValue"
|
||||
val context = mutableMapOf("someValue" to 5.0)
|
||||
val function = compileExpression(expression, constants = context)
|
||||
function().shouldBeEqualTo(5.0)
|
||||
context["someValue"] = 6.0
|
||||
function().shouldBeEqualTo(6.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an erroneous compiled expression`() {
|
||||
val expression = "1bork"
|
||||
invoking {
|
||||
compileExpression(expression, constants = mutableMapOf("someValue" to 5.0))
|
||||
} `should throw` ExpressionException::class
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import org.amshove.kluent.invoking
|
||||
import org.amshove.kluent.`should throw`
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.openrndr.extra.expressions.*
|
||||
|
||||
class TestCompiledFunctions {
|
||||
@Test
|
||||
fun `a simple compiled function1`() {
|
||||
val expression = "t"
|
||||
val function = compileFunction1(expression, "t")
|
||||
function(-5.0).shouldBeEqualTo(-5.0)
|
||||
function(5.0).shouldBeEqualTo(5.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a simple compiled function2`() {
|
||||
val expression = "x + y"
|
||||
val function = compileFunction2(expression, "x", "y")
|
||||
function(1.0, 2.0).shouldBeEqualTo(3.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a simple compiled function3`() {
|
||||
val expression = "x + y + z"
|
||||
val function = compileFunction3(expression, "x", "y", "z")
|
||||
function(1.0, 2.0, 3.0).shouldBeEqualTo(6.0)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.openrndr.extra.expressions.watchingExpression1
|
||||
import kotlin.test.Test
|
||||
|
||||
class TestExpressionDelegates {
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val state = object {
|
||||
var expression = "x * x"
|
||||
val function1 by watchingExpression1(::expression, "x")
|
||||
}
|
||||
state.function1(5.0).shouldBeEqualTo(25.0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import org.amshove.kluent.`should throw`
|
||||
import org.amshove.kluent.`with message`
|
||||
import org.amshove.kluent.invoking
|
||||
import org.openrndr.extra.expressions.ExpressionException
|
||||
import org.openrndr.extra.expressions.evaluateExpression
|
||||
|
||||
import kotlin.test.Test
|
||||
|
||||
class TestExpressionErrors {
|
||||
|
||||
@Test
|
||||
fun `an expression with non-sensible writing`() {
|
||||
val expression = ")("
|
||||
invoking {
|
||||
evaluateExpression(expression)
|
||||
} `should throw` ExpressionException::class `with message` "parser error in expression: ')('; [line: 1, character: 0 , near: [@0,0:0=')',<21>,1:0] ]"
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an expression with equality instead of assign`() {
|
||||
val expression = "a == 5"
|
||||
invoking {
|
||||
evaluateExpression(expression)
|
||||
} `should throw` ExpressionException::class `with message` "parser error in expression: 'a == 5'; [line: 1, character: 3 , near: [@3,3:3='=',<19>,1:3] ]"
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an expression trying to reassign a number`() {
|
||||
val expression = "3 = 5"
|
||||
invoking {
|
||||
evaluateExpression(expression)
|
||||
} `should throw` ExpressionException::class `with message` "parser error in expression: '3 = 5'; [line: 1, character: 2 , near: [@2,2:2='=',<19>,1:2] ]"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an expression that uses non-existing functions`() {
|
||||
val expression = "notExisting(5)"
|
||||
invoking {
|
||||
evaluateExpression(expression)
|
||||
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting(5)': unresolved function: 'notExisting(x0)'"
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an expression that uses non-existing variables`() {
|
||||
val expression = "notExisting + 4"
|
||||
invoking {
|
||||
evaluateExpression(expression)
|
||||
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting+4': unresolved variable: 'notExisting'"
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.amshove.kluent.shouldBeNear
|
||||
import org.openrndr.extra.expressions.FunctionExtensions
|
||||
import org.openrndr.extra.expressions.evaluateExpression
|
||||
|
||||
import kotlin.test.Test
|
||||
class TestExpressions {
|
||||
@Test
|
||||
fun `a value reference`() {
|
||||
val expression = "someValue"
|
||||
val result = evaluateExpression(expression, constants= mapOf("someValue" to 5.0))
|
||||
result?.shouldBeEqualTo(5.0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a backticked value reference`() {
|
||||
val expression = "`some-value`"
|
||||
val result = evaluateExpression(expression, constants= mapOf("some-value" to 5.0))
|
||||
result?.shouldBeEqualTo(5.0)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `a function call`() {
|
||||
val expression = "sqrt(4.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a function call with the name in backticks`() {
|
||||
val expression = "`sqrt`(4.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two function calls`() {
|
||||
val expression = "sqrt(4.0) * sqrt(4.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(4.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two argument max function call`() {
|
||||
val expression = "max(0.0, 4.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(4.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two argument min function call`() {
|
||||
val expression = "min(8.0, 4.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(4.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `three argument function call`() {
|
||||
val expression = "mix(8.0, 4.0, 0.5)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(6.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `five argument function call`() {
|
||||
val expression = "map(0.0, 1.0, 0.0, 8.0, 0.5)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(4.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two argument function call, where argument order matters`() {
|
||||
val expression = "pow(2.0, 3.0)"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(8.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nested function call`() {
|
||||
val expression = "sqrt(min(8.0, 4.0))"
|
||||
val result = evaluateExpression(expression)
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function0 call`() {
|
||||
val expression = "extension()"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions0 = mapOf("extension" to { 2.0 })))
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function1 call`(){
|
||||
val expression = "extension(1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions1 = mapOf("extension" to { x -> x * 2.0 })))
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function1 call with dashed name in backticks`(){
|
||||
val expression = "`extension-function`(1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions1 = mapOf("extension-function" to { x -> x * 2.0 })))
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function2 call`() {
|
||||
val expression = "extension(1.0, 1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions2 = mapOf("extension" to { x, y -> x + y })))
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function3 call`() {
|
||||
val expression = "extension(1.0, 1.0, 1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions3 = mapOf("extension" to { x, y, z -> x + y + z})))
|
||||
result?.shouldBeNear(3.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function4 call`() {
|
||||
val expression = "extension(1.0, 1.0, 1.0, 1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions4 = mapOf("extension" to { x, y, z, w -> x + y + z + w})))
|
||||
result?.shouldBeNear(4.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `extension function5 call`() {
|
||||
val expression = "extension(1.0, 1.0, 1.0, 1.0, 1.0)"
|
||||
val result = evaluateExpression(expression, functions = FunctionExtensions(functions5 = mapOf("extension" to { x, y, z, w, u -> x + y + z + w + u})))
|
||||
result?.shouldBeNear(5.0, 10E-6)
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import org.amshove.kluent.shouldBeNear
|
||||
import org.openrndr.extra.expressions.evaluateExpression
|
||||
import kotlin.test.Test
|
||||
|
||||
class TestOperators {
|
||||
@Test
|
||||
fun `an addition operation`() {
|
||||
val result = evaluateExpression("1 + 2")
|
||||
result?.shouldBeNear(3.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a subtraction operation`() {
|
||||
val result = evaluateExpression("1 - 2")
|
||||
result?.shouldBeNear(-1.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a modulus operation`() {
|
||||
val result = evaluateExpression("4 % 2")
|
||||
result?.shouldBeNear(0.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a multiplication operation`() {
|
||||
val result = evaluateExpression("4 * 2")
|
||||
result?.shouldBeNear(8.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a division operation`() {
|
||||
val result = evaluateExpression("4 / 2")
|
||||
result?.shouldBeNear(2.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a multiplication and addition operation`() {
|
||||
val result = evaluateExpression("4 * 2 + 1")
|
||||
result?.shouldBeNear(9.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an addition and multiplication`() {
|
||||
val result = evaluateExpression("4 + 2 * 3")
|
||||
result?.shouldBeNear(10.0, 10E-6)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unary minus`() {
|
||||
val result = evaluateExpression("-4.0")
|
||||
result?.shouldBeNear(-4.0, 10E-6)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ dependencies {
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(project(":orx-noise"))
|
||||
implementation(project(":orx-easing"))
|
||||
api(project(":orx-jvm:orx-expression-evaluator"))
|
||||
api(project(":orx-expression-evaluator"))
|
||||
demoImplementation(project(":orx-jvm:orx-panel"))
|
||||
testImplementation(libs.kluent)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ tasks.test {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":orx-jvm:orx-expression-evaluator"))
|
||||
implementation(project(":orx-expression-evaluator"))
|
||||
implementation(libs.openrndr.application)
|
||||
implementation(libs.openrndr.math)
|
||||
implementation(libs.kotlin.coroutines)
|
||||
|
||||
Reference in New Issue
Block a user