Add orx-expression-evaluator-typed
This commit is contained in:
13
orx-expression-evaluator-typed/README.md
Normal file
13
orx-expression-evaluator-typed/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# orx-expression-evaluator-typed
|
||||||
|
|
||||||
|
Tools to evaluate strings containing typed mathematical expressions.
|
||||||
|
|
||||||
|
# Expression evaluator
|
||||||
|
|
||||||
|
Supported types:
|
||||||
|
* `Double`
|
||||||
|
* `String`
|
||||||
|
* `Vector2`
|
||||||
|
* `Vector3`
|
||||||
|
* `Vector4`
|
||||||
|
* `ColorRGBa`
|
||||||
31
orx-expression-evaluator-typed/build.gradle.kts
Normal file
31
orx-expression-evaluator-typed/build.gradle.kts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
org.openrndr.extra.convention.`kotlin-multiplatform`
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.antlr.kotlin.runtime)
|
||||||
|
implementation(libs.openrndr.application)
|
||||||
|
implementation(libs.openrndr.math)
|
||||||
|
implementation(libs.kotlin.coroutines)
|
||||||
|
implementation(project(":orx-property-watchers"))
|
||||||
|
implementation(project(":orx-noise"))
|
||||||
|
implementation(project(":orx-expression-evaluator"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jvmDemo by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":orx-jvm:orx-gui"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val jvmTest by getting {
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.kluent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker
|
||||||
|
import org.openrndr.extra.expressions.ExpressionException
|
||||||
|
|
||||||
|
fun <T0, R> compileFunction1OrNull(
|
||||||
|
expression: String,
|
||||||
|
parameter0: String,
|
||||||
|
constants: (String)->Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): ((T0) -> R)? {
|
||||||
|
require(constants(parameter0) == null) {
|
||||||
|
"${parameter0} is in constants with value '${constants(parameter0)}"
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val root = org.openrndr.extra.expressions.typed.expressionRoot(expression)
|
||||||
|
|
||||||
|
var varP0: T0? = null
|
||||||
|
val variables = fun(p : String) : Any? {
|
||||||
|
return if (p == parameter0) {
|
||||||
|
varP0
|
||||||
|
} else {
|
||||||
|
constants(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val listener = TypedExpressionListener(functions, variables)
|
||||||
|
|
||||||
|
return { p0 ->
|
||||||
|
varP0 = p0
|
||||||
|
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||||
|
listener.lastExpressionResult as? R ?: error("no result")
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (e: ExpressionException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.transforms.scale
|
||||||
|
import org.openrndr.math.transforms.translate
|
||||||
|
import kotlin.math.abs as abs_
|
||||||
|
import kotlin.math.cos as cos_
|
||||||
|
import kotlin.math.sin as sin_
|
||||||
|
import kotlin.math.sqrt as sqrt_
|
||||||
|
|
||||||
|
internal fun vec2(x: Any): Vector2 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector2(x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any): Vector3 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector3(x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec4(x: Any): Vector4 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector4(x, x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun rgba(x: Any): ColorRGBa {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> ColorRGBa(x, x, x, 1.0)
|
||||||
|
is Vector3 -> ColorRGBa(x.x, x.y, x.z, 1.0)
|
||||||
|
is Vector4 -> ColorRGBa(x.x, x.y, x.z, x.w)
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun cos(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> cos_(x)
|
||||||
|
is Vector2 -> x.map { cos_(it) }
|
||||||
|
is Vector3 -> x.map { cos_(it) }
|
||||||
|
is Vector4 -> x.map { cos_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun sin(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> sin_(x)
|
||||||
|
is Vector2 -> x.map { sin_(it) }
|
||||||
|
is Vector3 -> x.map { sin_(it) }
|
||||||
|
is Vector4 -> x.map { sin_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun normalize(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Vector2 -> x.normalized
|
||||||
|
is Vector3 -> x.normalized
|
||||||
|
is Vector4 -> x.normalized
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun inverse(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Matrix44 -> x.inversed
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun transpose(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Matrix44 -> x.transposed
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun abs(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> abs_(x)
|
||||||
|
is Vector2 -> x.map { abs_(it) }
|
||||||
|
is Vector3 -> x.map { abs_(it) }
|
||||||
|
is Vector4 -> x.map { abs_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun scale(scale: Any): Matrix44 {
|
||||||
|
@Suppress("NAME_SHADOWING") val scale = when (scale) {
|
||||||
|
is Double -> Vector3(scale, scale, scale)
|
||||||
|
is Vector2 -> scale.xy1
|
||||||
|
is Vector3 -> scale
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.scale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun sqrt(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> sqrt_(x)
|
||||||
|
is Vector2 -> x.map { sqrt_(it) }
|
||||||
|
is Vector3 -> x.map { sqrt_(it) }
|
||||||
|
is Vector4 -> x.map { sqrt_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translate(translation: Any): Matrix44 {
|
||||||
|
@Suppress("NAME_SHADOWING") val translation = when (translation) {
|
||||||
|
is Vector2 -> translation.xy0
|
||||||
|
is Vector3 -> translation
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.translate(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction1(name: String, functions: Map<String, TypedFunction1>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec2" -> { x -> vec2(x[0]) }
|
||||||
|
"vec3" -> { x -> vec3(x[0]) }
|
||||||
|
"vec4" -> { x -> vec4(x[0]) }
|
||||||
|
|
||||||
|
"cos" -> { x -> cos(x[0]) }
|
||||||
|
"sin" -> { x -> sin(x[0]) }
|
||||||
|
"sqrt" -> { v -> sqrt(v[0]) }
|
||||||
|
"abs" -> { v -> abs(v[0]) }
|
||||||
|
"scale" -> { x -> scale(x[0]) }
|
||||||
|
"translate" -> { x -> translate(x[0]) }
|
||||||
|
"transpose" -> { x -> transpose(x[0]) }
|
||||||
|
"inverse" -> { x -> inverse(x[0]) }
|
||||||
|
"normalize" -> { x -> normalize(x[0]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.transforms.rotate
|
||||||
|
import org.openrndr.math.transforms.translate
|
||||||
|
import org.openrndr.math.min as min_
|
||||||
|
import org.openrndr.math.max as max_
|
||||||
|
|
||||||
|
import kotlin.math.max as max_
|
||||||
|
import kotlin.math.min as min_
|
||||||
|
|
||||||
|
internal fun rotate(axis: Any, angleInDegrees:Any): Matrix44 {
|
||||||
|
require(angleInDegrees is Double)
|
||||||
|
@Suppress("NAME_SHADOWING") val axis = when(axis) {
|
||||||
|
is Vector2 -> axis.xy0
|
||||||
|
is Vector3 -> axis
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.rotate(axis, angleInDegrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun min(x: Any, y: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double -> min_(x, y)
|
||||||
|
x is Vector2 && y is Vector2 -> min_(x, y)
|
||||||
|
x is Vector3 && y is Vector3 -> min_(x, y)
|
||||||
|
x is Vector4 && y is Vector4 -> min_(x, y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun max(x: Any, y: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double -> max_(x, y)
|
||||||
|
x is Vector2 && y is Vector2 -> max_(x, y)
|
||||||
|
x is Vector3 && y is Vector3 -> max_(x, y)
|
||||||
|
x is Vector4 && y is Vector4 -> max_(x, y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec2(x: Any, y: Any): Vector2 {
|
||||||
|
require(x is Double)
|
||||||
|
require(y is Double)
|
||||||
|
return Vector2(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any, y: Any): Vector3 = when {
|
||||||
|
x is Double && y is Vector2 -> {
|
||||||
|
Vector3(x, y.x, y.y)
|
||||||
|
}
|
||||||
|
x is Vector2 && y is Double -> {
|
||||||
|
Vector3(x.x, x.y, y)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
error("unsupported arguments, '$x' (${x::class}) '$y' (${y::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction2(name: String, functions: Map<String, TypedFunction2>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"min" -> { x -> min(x[0], x[1]) }
|
||||||
|
"max" -> { x -> max(x[0], x[1]) }
|
||||||
|
"vec2" -> { x -> vec2(x[0], x[1]) }
|
||||||
|
"vec3" -> { x -> vec3(x[0], x[1]) }
|
||||||
|
"rotate" -> { x -> rotate(x[0], x[1]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0], x[1]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.mix as mix_
|
||||||
|
|
||||||
|
internal fun mix(x: Any, y: Any, f: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector2 && y is Vector2 && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector3 && y is Vector3 && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector4 && y is Vector4 && f is Double -> mix_(x, y, f)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any, y: Any, z: Any): Vector3 {
|
||||||
|
require(x is Double && y is Double && z is Double)
|
||||||
|
return Vector3(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec4(x: Any, y: Any, z: Any): Vector4 {
|
||||||
|
return when {
|
||||||
|
x is Vector2 && y is Double && z is Double -> Vector4(x.x, x.y, y, z)
|
||||||
|
x is Double && y is Vector2 && z is Double -> Vector4(x, y.x, y.y, z)
|
||||||
|
x is Double && y is Double && z is Vector2 -> Vector4(x, y, z.x, z.y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction3(name: String, functions: Map<String, TypedFunction3>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec3" -> { x -> vec3(x[0], x[1], x[2]) }
|
||||||
|
"vec4" -> { x -> vec4(x[0], x[1], x[2]) }
|
||||||
|
"mix" -> { x -> mix(x[0], x[1], x[2]) }
|
||||||
|
else -> functions[name]?.let {
|
||||||
|
{ x: Array<Any> ->
|
||||||
|
it.invoke(x[0], x[1], x[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
|
||||||
|
internal fun vec4(x: Any, y: Any, z: Any, w: Any): Vector4 {
|
||||||
|
require(x is Double && y is Double && z is Double && w is Double)
|
||||||
|
return Vector4(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mat4(x: Any, y: Any, z: Any, w: Any): Matrix44 {
|
||||||
|
require(x is Vector4 && y is Vector4 && z is Vector4 && w is Vector4)
|
||||||
|
return Matrix44.fromColumnVectors(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun rgba(r: Any, g: Any, b: Any, a: Any): ColorRGBa {
|
||||||
|
require(r is Double && g is Double && b is Double && a is Double)
|
||||||
|
return ColorRGBa(r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun dispatchFunction4(name: String, functions: Map<String, TypedFunction4>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec4" -> { x -> vec4(x[0], x[1], x[2], x[3]) }
|
||||||
|
"mat4" -> { x -> mat4(x[0], x[1], x[2], x[3]) }
|
||||||
|
"rgba" -> { x -> rgba(x[0], x[1], x[2], x[3]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0], x[1], x[2], x[3]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
fun String.memberFunctions(n: String): ((Array<Any>) -> Any)? {
|
||||||
|
return when (n) {
|
||||||
|
"take" -> { n -> this.take((n[0] as Number).toInt()) }
|
||||||
|
"drop" -> { n -> this.drop((n[0] as Number).toInt()) }
|
||||||
|
"takeLast" -> { n -> this.takeLast((n[0] as Number).toInt()) }
|
||||||
|
"dropLast" -> { n -> this.takeLast((n[0] as Number).toInt()) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
|
||||||
|
internal fun String.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"length" -> this.length.toDouble()
|
||||||
|
"uppercase" -> this.uppercase()
|
||||||
|
"lowercase" -> this.lowercase()
|
||||||
|
"reversed" -> this.reversed()
|
||||||
|
else -> error("unknown property '$property'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector2.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"xx" -> xx
|
||||||
|
"yx" -> yx
|
||||||
|
"yy" -> yy
|
||||||
|
"xy" -> this
|
||||||
|
"xxx" -> Vector3(x, x, x)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector3.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"z" -> z
|
||||||
|
"xx" -> Vector2(x, x)
|
||||||
|
"yx" -> Vector2(y, x)
|
||||||
|
"yy" -> Vector2(y, y)
|
||||||
|
"xy" -> Vector2(x, y)
|
||||||
|
"zx" -> Vector2(z, x)
|
||||||
|
"xz" -> Vector2(x, z)
|
||||||
|
"xxx" -> Vector3(x, x, x)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector4.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"z" -> z
|
||||||
|
"xx" -> Vector2(x, x)
|
||||||
|
"yx" -> Vector2(y, x)
|
||||||
|
"yy" -> Vector2(y, y)
|
||||||
|
"xy" -> Vector2(x, y)
|
||||||
|
"zx" -> Vector2(z, x)
|
||||||
|
"xz" -> Vector2(x, z)
|
||||||
|
"xyz" -> Vector3(x, y, z)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ColorRGBa.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"r" -> r
|
||||||
|
"g" -> g
|
||||||
|
"b" -> b
|
||||||
|
"a" -> alpha
|
||||||
|
"linear" -> toLinear()
|
||||||
|
"srgb" -> toSRGB()
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Matrix44.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"inversed" -> inversed
|
||||||
|
"transposed" -> transposed
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,685 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.antlr.v4.kotlinruntime.*
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.TerminalNode
|
||||||
|
import org.openrndr.collections.pop
|
||||||
|
import org.openrndr.collections.push
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangLexer
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangParser
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangParserBaseListener
|
||||||
|
|
||||||
|
import org.openrndr.extra.noise.uniform
|
||||||
|
import org.openrndr.math.*
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
typealias TypedFunction0 = () -> Any
|
||||||
|
typealias TypedFunction1 = (Any) -> Any
|
||||||
|
typealias TypedFunction2 = (Any, Any) -> Any
|
||||||
|
typealias TypedFunction3 = (Any, Any, Any) -> Any
|
||||||
|
typealias TypedFunction4 = (Any, Any, Any, Any) -> Any
|
||||||
|
typealias TypedFunction5 = (Any, Any, Any, Any, Any) -> Any
|
||||||
|
|
||||||
|
|
||||||
|
private fun ArrayDeque<Any>.pushChecked(item: Any) {
|
||||||
|
// require(item is Double || item is Vector2 || item is Vector3 || item is Vector4 || item is Map<*, *> || item is Matrix44) {
|
||||||
|
//
|
||||||
|
// "$item ${item::class}"
|
||||||
|
// }
|
||||||
|
push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypedFunctionExtensions(
|
||||||
|
val functions0: Map<String, TypedFunction0> = emptyMap(),
|
||||||
|
val functions1: Map<String, TypedFunction1> = emptyMap(),
|
||||||
|
val functions2: Map<String, TypedFunction2> = emptyMap(),
|
||||||
|
val functions3: Map<String, TypedFunction3> = emptyMap(),
|
||||||
|
val functions4: Map<String, TypedFunction4> = emptyMap(),
|
||||||
|
val functions5: Map<String, TypedFunction5> = emptyMap()
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = TypedFunctionExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class IDType {
|
||||||
|
VARIABLE,
|
||||||
|
PROPERTY,
|
||||||
|
MEMBER_FUNCTION0,
|
||||||
|
MEMBER_FUNCTION1,
|
||||||
|
MEMBER_FUNCTION2,
|
||||||
|
MEMBER_FUNCTION3,
|
||||||
|
FUNCTION0,
|
||||||
|
FUNCTION1,
|
||||||
|
FUNCTION2,
|
||||||
|
FUNCTION3,
|
||||||
|
FUNCTION4,
|
||||||
|
FUNCTION5
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TypedExpressionListener(
|
||||||
|
val functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY,
|
||||||
|
val constants: (String) -> Any? = { null }
|
||||||
|
) :
|
||||||
|
KeyLangParserBaseListener() {
|
||||||
|
val valueStack = ArrayDeque<Any>()
|
||||||
|
val functionStack = ArrayDeque<(Array<Any>) -> Any>()
|
||||||
|
val propertyStack = ArrayDeque<String>()
|
||||||
|
|
||||||
|
val idTypeStack = ArrayDeque<IDType>()
|
||||||
|
var lastExpressionResult: Any? = null
|
||||||
|
|
||||||
|
val exceptionStack = ArrayDeque<ExpressionException>()
|
||||||
|
|
||||||
|
override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) {
|
||||||
|
ifError {
|
||||||
|
throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}")
|
||||||
|
}
|
||||||
|
val result = valueStack.pop()
|
||||||
|
lastExpressionResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) {
|
||||||
|
val op = valueStack.pop()
|
||||||
|
valueStack.pushChecked(
|
||||||
|
when (op) {
|
||||||
|
is Double -> -op
|
||||||
|
is Vector3 -> -op
|
||||||
|
is Vector2 -> -op
|
||||||
|
is Vector4 -> -op
|
||||||
|
is Matrix44 -> op * -1.0
|
||||||
|
else -> error("unsupported type")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.ASTERISK -> when {
|
||||||
|
left is Double && right is Double -> left * right
|
||||||
|
left is Vector2 && right is Vector2 -> left * right
|
||||||
|
left is Vector2 && right is Double -> left * right
|
||||||
|
left is Vector3 && right is Vector3 -> left * right
|
||||||
|
left is Vector3 && right is Double -> left * right
|
||||||
|
left is Vector4 && right is Vector4 -> left * right
|
||||||
|
left is Vector4 && right is Double -> left * right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left * right
|
||||||
|
left is Matrix44 && right is Vector4 -> left * right
|
||||||
|
left is Matrix44 && right is Double -> left * right
|
||||||
|
left is ColorRGBa && right is Double -> left * right
|
||||||
|
left is String && right is Double -> left.repeat(right.roundToInt())
|
||||||
|
else -> error("unsupported operands for * operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.DIVISION -> when {
|
||||||
|
left is Double && right is Double -> left / right
|
||||||
|
left is Vector2 && right is Vector2 -> left / right
|
||||||
|
left is Vector2 && right is Double -> left / right
|
||||||
|
left is Vector3 && right is Vector3 -> left / right
|
||||||
|
left is Vector3 && right is Double -> left / right
|
||||||
|
left is Vector4 && right is Vector4 -> left / right
|
||||||
|
left is Vector4 && right is Double -> left / right
|
||||||
|
left is ColorRGBa && right is Double -> left / right
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.PERCENTAGE -> when {
|
||||||
|
left is Double && right is Double -> left.mod(right)
|
||||||
|
left is Vector2 && right is Vector2 -> left.mod(right)
|
||||||
|
left is Vector3 && right is Vector3 -> left.mod(right)
|
||||||
|
left is Vector4 && right is Vector4 -> left.mod(right)
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("IMPLICIT_CAST_TO_ANY")
|
||||||
|
override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.PLUS -> when {
|
||||||
|
left is Double && right is Double -> left + right
|
||||||
|
left is Vector2 && right is Vector2 -> left + right
|
||||||
|
left is Vector3 && right is Vector3 -> left + right
|
||||||
|
left is Vector4 && right is Vector4 -> left + right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left + right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left + right
|
||||||
|
left is String && right is String -> left + right
|
||||||
|
else -> error("unsupported operands for + operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.MINUS -> when {
|
||||||
|
left is Double && right is Double -> left - right
|
||||||
|
left is Vector2 && right is Vector2 -> left - right
|
||||||
|
left is Vector3 && right is Vector3 -> left - right
|
||||||
|
left is Vector4 && right is Vector4 -> left - right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left - right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left - right
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitJoinOperation(ctx: KeyLangParser.JoinOperationContext) {
|
||||||
|
val right = (valueStack.pop() as Double).roundToInt()
|
||||||
|
val left = (valueStack.pop() as Double).roundToInt()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.AND -> right != 0 && left != 0
|
||||||
|
KeyLangLexer.Tokens.OR -> right != 0 || left != 0
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(if (result) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitComparisonOperation(ctx: KeyLangParser.ComparisonOperationContext) {
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.EQ -> when {
|
||||||
|
left is Double && right is Double -> left == right
|
||||||
|
left is Vector2 && right is Vector2 -> left == right
|
||||||
|
left is Vector3 && right is Vector3 -> left == right
|
||||||
|
left is Vector4 && right is Vector4 -> left == right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left == right
|
||||||
|
left is String && right is String -> left == right
|
||||||
|
else -> error("unsupported operands for == operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.LTEQ -> when {
|
||||||
|
left is Double && right is Double -> left <= right
|
||||||
|
else -> error("unsupported operands for <= operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.LT -> when {
|
||||||
|
left is Double && right is Double -> left < right
|
||||||
|
else -> error("unsupported operands for < operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.GTEQ -> when {
|
||||||
|
left is Double && right is Double -> left >= right
|
||||||
|
else -> error("unsupported operands for >= operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.GT -> when {
|
||||||
|
left is Double && right is Double -> left > right
|
||||||
|
else -> error("unsupported operands for > operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(if (result) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitNegateExpression(ctx: KeyLangParser.NegateExpressionContext) {
|
||||||
|
val operand = (valueStack.pop() as Double).roundToInt()
|
||||||
|
valueStack.pushChecked(if (operand == 0) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitTernaryExpression(ctx: KeyLangParser.TernaryExpressionContext) {
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
val comp = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (comp) {
|
||||||
|
is Double -> if (comp.roundToInt() != 0) left else right
|
||||||
|
else -> error("can't compare")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) {
|
||||||
|
idTypeStack.push(IDType.VARIABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(emptyArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(valueStack.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1, argument2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(arrayOf())
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument3 = valueStack.pop()
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3, argument4))
|
||||||
|
valueStack.pushChecked(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 enterPropReference(ctx: KeyLangParser.PropReferenceContext) {
|
||||||
|
idTypeStack.push(IDType.PROPERTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitPropReference(ctx: KeyLangParser.PropReferenceContext) {
|
||||||
|
val root = valueStack.pop()
|
||||||
|
var current = root
|
||||||
|
val property = propertyStack.pop()
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
current = when (current) {
|
||||||
|
is Map<*, *> -> current[property] ?: error("property '$property' not found")
|
||||||
|
is Function<*> -> (current as ((String) -> Any?)).invoke(property)
|
||||||
|
?: error("property '$property' not found")
|
||||||
|
|
||||||
|
is Vector2 -> current.property(property)
|
||||||
|
is Vector3 -> current.property(property)
|
||||||
|
is Vector4 -> current.property(property)
|
||||||
|
is ColorRGBa -> current.property(property)
|
||||||
|
is Matrix44 -> current.property(property)
|
||||||
|
else -> error("can't look up: ${current::class} '$current', root:'$root' ${ctx.text} ")
|
||||||
|
}
|
||||||
|
valueStack.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun visitTerminal(node: TerminalNode) {
|
||||||
|
|
||||||
|
val type = node.symbol.type
|
||||||
|
if (type == KeyLangParser.Tokens.INTLIT) {
|
||||||
|
valueStack.pushChecked(node.text.toDouble())
|
||||||
|
} else if (type == KeyLangParser.Tokens.DECLIT) {
|
||||||
|
valueStack.pushChecked(node.text.toDouble())
|
||||||
|
} else if (type == KeyLangParser.Tokens.STRING_CONTENT) {
|
||||||
|
valueStack.pushChecked(node.text)
|
||||||
|
} else if (type == KeyLangParser.Tokens.ID) {
|
||||||
|
val name = node.text.replace("`", "")
|
||||||
|
@Suppress("DIVISION_BY_ZERO")
|
||||||
|
when (val idType = idTypeStack.pop()) {
|
||||||
|
IDType.VARIABLE -> valueStack.pushChecked(
|
||||||
|
when (name) {
|
||||||
|
"PI" -> PI
|
||||||
|
else -> constants(name) ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
IDType.PROPERTY -> propertyStack.push(name)
|
||||||
|
|
||||||
|
IDType.FUNCTION0 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
when (name) {
|
||||||
|
"random" -> { _ -> Double.uniform(0.0, 1.0) }
|
||||||
|
else -> functions.functions0[name]?.let { { _: Array<Any> -> it.invoke() } }
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}()'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
}
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION0,
|
||||||
|
IDType.MEMBER_FUNCTION1,
|
||||||
|
IDType.MEMBER_FUNCTION2,
|
||||||
|
IDType.MEMBER_FUNCTION3 -> {
|
||||||
|
val receiver = valueStack.pop()
|
||||||
|
when (receiver) {
|
||||||
|
is String -> {
|
||||||
|
functionStack.push(
|
||||||
|
receiver.memberFunctions(name)
|
||||||
|
?: error("no member function '$receiver.$name()'")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ColorRGBa -> {
|
||||||
|
when (idType) {
|
||||||
|
IDType.MEMBER_FUNCTION1 -> {
|
||||||
|
functionStack.push(when (name) {
|
||||||
|
"shade" -> { x -> receiver.shade(x[0] as Double) }
|
||||||
|
"opacify" -> { x -> receiver.opacify(x[0] as Double) }
|
||||||
|
else -> error("no member function '$receiver.$name()'")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("no member function $idType '$receiver.$name()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
is Function<*> -> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
receiver as (String) -> Any
|
||||||
|
@Suppress("UNCHECKED_CAST") val function =
|
||||||
|
receiver.invoke(name) ?: error("no such function $name")
|
||||||
|
|
||||||
|
when (idType) {
|
||||||
|
IDType.MEMBER_FUNCTION0 -> {
|
||||||
|
function as () -> Any
|
||||||
|
functionStack.push({ function() })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION1 -> {
|
||||||
|
function as (Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION2 -> {
|
||||||
|
function as (Any, Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0], x[1]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION3 -> {
|
||||||
|
function as (Any, Any, Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0], x[1], x[2]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("receiver '${receiver}' not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION1 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction1(name, functions.functions1)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION2 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction2(name, functions.functions2)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0, x1)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION3 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction3(name, functions.functions3)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION4 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction4(name, functions.functions4)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("unsupported id-type $idType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionException(message: String) : RuntimeException(message)
|
||||||
|
|
||||||
|
fun evaluateTypedExpression(
|
||||||
|
expression: String,
|
||||||
|
constants: (String) -> Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): Any? {
|
||||||
|
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 = TypedExpressionListener(functions, constants)
|
||||||
|
try {
|
||||||
|
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||||
|
} catch (e: ExpressionException) {
|
||||||
|
throw ExpressionException(e.message ?: "")
|
||||||
|
}
|
||||||
|
return listener.lastExpressionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compileTypedExpression(
|
||||||
|
expression: String,
|
||||||
|
constants: (String) -> Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): () -> Any {
|
||||||
|
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 = TypedExpressionListener(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()
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package typed
|
||||||
|
|
||||||
|
import org.openrndr.extra.expressions.typed.compileFunction1OrNull
|
||||||
|
import org.openrndr.extra.noise.uniform
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class TestTypedCompiledExpression {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringLiteral() {
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Double, String>(""""hoi"""", "t")!!
|
||||||
|
val v = c(0.0)
|
||||||
|
assertEquals("hoi", v)
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Double, String>(""""hoi" + " " + "doei" * t""", "t")!!
|
||||||
|
val v = c(2.0)
|
||||||
|
assertEquals("hoi doeidoei", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Double, String>(""""hoi".take(t)""", "t")!!
|
||||||
|
val v = c(2.0)
|
||||||
|
assertEquals("ho", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testComparison() {
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Double, Double>("""t == t""", "t")!!
|
||||||
|
val v = c(0.0)
|
||||||
|
assertEquals(1.0, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testFunction1() {
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Double, Double>("x + 3.0", "x")!!
|
||||||
|
assertEquals(1.0 + 3.0, c(1.0))
|
||||||
|
assertEquals(2.0 + 3.0, c(2.0))
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Vector2, Double>("x.x + x.y", "x")!!
|
||||||
|
assertEquals(1.0 + 3.0, c(Vector2(1.0, 3.0)))
|
||||||
|
assertEquals(2.0 + 3.0, c(Vector2(2.0, 3.0)))
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val c = compileFunction1OrNull<Vector2, Double>("x.x + x.y", "x")!!
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
for (i in 0 until 1000) {
|
||||||
|
val r0 = Double.uniform(0.0, 1.0)
|
||||||
|
val r1 = Double.uniform(0.0, 1.0)
|
||||||
|
assertEquals(r0 + r1, c(Vector2(r0, r1)))
|
||||||
|
}
|
||||||
|
val end = System.currentTimeMillis()
|
||||||
|
println("that took ${end - start}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package typed
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.openrndr.extra.expressions.typed.evaluateTypedExpression
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TestTypedExpression {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTernary() {
|
||||||
|
println("result is: ${evaluateTypedExpression("2.0 > 0.5 ? 1.3 : 0.7")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testJoin() {
|
||||||
|
assertEquals(0.0, evaluateTypedExpression("1.0 && 0.0"))
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("1.0 && 1.0"))
|
||||||
|
assertEquals(0.0, evaluateTypedExpression("0.0 && 0.0"))
|
||||||
|
assertEquals(0.0, evaluateTypedExpression("0.0 && 1.0"))
|
||||||
|
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("1.0 || 0.0"))
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("1.0 || 1.0"))
|
||||||
|
assertEquals(0.0, evaluateTypedExpression("0.0 || 0.0"))
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("0.0 || 1.0"))
|
||||||
|
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("(0.0 || 1.0) && (1.0 || 0.0)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNegate() {
|
||||||
|
assertEquals(0.0, evaluateTypedExpression("!1.0"))
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("!0.0"))
|
||||||
|
assertEquals(1.0, evaluateTypedExpression("!!2.0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTyped() {
|
||||||
|
println(evaluateTypedExpression("vec2(1.0, 1.0) + vec2(1.0, 1.0)"))
|
||||||
|
println(evaluateTypedExpression("vec3(1.0, 1.0, 1.0) + vec3(2.0, 3.0, 4.0)"))
|
||||||
|
println(evaluateTypedExpression("vec3(1.0, 1.0, 1.0) * vec3(2.0, 3.0, 4.0)"))
|
||||||
|
println(evaluateTypedExpression("translate(vec3(1.0, 0.0, 0.0)) * mat4(vec4(1,0,0,0), vec4(0,1,0,0), vec4(0,0,1,0), vec4(0.0, 0.0, 0.0, 1.0))"))
|
||||||
|
println(evaluateTypedExpression("(translate(vec3(1.0, 0.0, 0.0)) * vec4(0.0, 0.0, 0.0, 1.0)).xyz"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Map<String, Any>.function(): (String) -> Any? {
|
||||||
|
return fun(p: String): Any? {
|
||||||
|
val v = this[p]
|
||||||
|
if (v is Map<*, *>) {
|
||||||
|
return (v as Map<String, Any>).function()
|
||||||
|
} else {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPropref() {
|
||||||
|
println(evaluateTypedExpression("a.b.c", constants = mapOf("a" to mapOf("b" to mapOf("c" to 8.0))).function()))
|
||||||
|
println(
|
||||||
|
evaluateTypedExpression(
|
||||||
|
"a.yx.yx.normalized * -5.0",
|
||||||
|
constants = mapOf("a" to Vector2(1.0, 2.0)).function()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
println(
|
||||||
|
evaluateTypedExpression(
|
||||||
|
"vec2(2.0, 3.0).normalized",
|
||||||
|
constants = mapOf("a" to Vector2(1.0, 2.0)).function()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMethodCall() {
|
||||||
|
println(
|
||||||
|
evaluateTypedExpression(
|
||||||
|
"a.b.c(5.0) + a.b.sum(3.0, 5.0)",
|
||||||
|
constants = mapOf(
|
||||||
|
"a" to
|
||||||
|
mapOf(
|
||||||
|
"b" to
|
||||||
|
mapOf(
|
||||||
|
"c" to { x: Double -> x * 5.0 },
|
||||||
|
"sum" to { x: Double, y: Double -> x + y }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).function()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
@Test
|
||||||
|
fun testMethodCallF() {
|
||||||
|
println(
|
||||||
|
evaluateTypedExpression(
|
||||||
|
"vec2(2.0, 3.0) * (a.b.c(5.0) + a.b.sum(3.0, 5.0))",
|
||||||
|
constants = { name: String ->
|
||||||
|
when (name) {
|
||||||
|
"a" -> { name: String ->
|
||||||
|
when (name) {
|
||||||
|
"b" -> { name: String ->
|
||||||
|
when (name) {
|
||||||
|
"c" -> { x: Double -> x * 5.0 }
|
||||||
|
"sum" -> { x: Double, y: Double -> x + y }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,14 +6,6 @@ channels { WHITESPACE }
|
|||||||
NEWLINE : '\r\n' | '\r' | '\n' ;
|
NEWLINE : '\r\n' | '\r' | '\n' ;
|
||||||
WS : [\t ]+ -> channel(WHITESPACE) ;
|
WS : [\t ]+ -> channel(WHITESPACE) ;
|
||||||
|
|
||||||
// Keywords
|
|
||||||
INPUT : 'input' ;
|
|
||||||
VAR : 'var' ;
|
|
||||||
PRINT : 'print';
|
|
||||||
AS : 'as';
|
|
||||||
INT : 'Int';
|
|
||||||
DECIMAL : 'Decimal';
|
|
||||||
STRING : 'String';
|
|
||||||
|
|
||||||
// Identifiers
|
// Identifiers
|
||||||
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* | '`'[$_]*[A-Za-z0-9_-]*'`';
|
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* | '`'[$_]*[A-Za-z0-9_-]*'`';
|
||||||
@@ -34,8 +26,21 @@ ASSIGN : '=' ;
|
|||||||
LPAREN : '(' ;
|
LPAREN : '(' ;
|
||||||
RPAREN : ')' ;
|
RPAREN : ')' ;
|
||||||
|
|
||||||
|
QUESTION_MARK : '?' ;
|
||||||
|
COLON : ':' ;
|
||||||
|
|
||||||
COMMA : ',' ;
|
COMMA : ',' ;
|
||||||
|
DOT : '.' ;
|
||||||
|
|
||||||
|
EQ : '==' ;
|
||||||
|
LT : '<' ;
|
||||||
|
LTEQ : '<=' ;
|
||||||
|
GT : '>=' ;
|
||||||
|
GTEQ : '>' ;
|
||||||
|
|
||||||
|
AND : '&&' ;
|
||||||
|
OR : '||' ;
|
||||||
|
NOT : '!' ;
|
||||||
|
|
||||||
STRING_OPEN : '"' -> pushMode(MODE_IN_STRING);
|
STRING_OPEN : '"' -> pushMode(MODE_IN_STRING);
|
||||||
|
|
||||||
|
|||||||
@@ -7,22 +7,16 @@ keyLangFile : lines=line+ ;
|
|||||||
|
|
||||||
line : statement (NEWLINE | EOF) ;
|
line : statement (NEWLINE | EOF) ;
|
||||||
|
|
||||||
statement : inputDeclaration # inputDeclarationStatement
|
statement :
|
||||||
| varDeclaration # varDeclarationStatement
|
expression # expressionStatement ;
|
||||||
| 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
|
expression : INTLIT # intLiteral
|
||||||
| DECLIT # decimalLiteral
|
| DECLIT # decimalLiteral
|
||||||
|
| expression DOT ID LPAREN RPAREN # memberFunctionCall0Expression
|
||||||
|
| expression DOT ID LPAREN expression RPAREN # memberFunctionCall1Expression
|
||||||
|
| expression DOT ID LPAREN expression COMMA expression RPAREN # memberFunctionCall2Expression
|
||||||
|
| expression DOT ID LPAREN expression COMMA expression COMMA expression RPAREN # memberFunctionCall3Expression
|
||||||
|
| expression DOT ID LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN # memberFunctionCall4Expression
|
||||||
| ID LPAREN RPAREN # functionCall0Expression
|
| ID LPAREN RPAREN # functionCall0Expression
|
||||||
| ID LPAREN expression RPAREN # functionCall1Expression
|
| ID LPAREN expression RPAREN # functionCall1Expression
|
||||||
| ID LPAREN expression COMMA expression RPAREN # functionCall2Expression
|
| ID LPAREN expression COMMA expression RPAREN # functionCall2Expression
|
||||||
@@ -30,13 +24,15 @@ expression : INTLIT # int
|
|||||||
| ID LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN # functionCall4Expression
|
| 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 LPAREN expression COMMA expression COMMA expression COMMA expression COMMA expression RPAREN # functionCall5Expression
|
||||||
| ID # valueReference
|
| ID # valueReference
|
||||||
|
| STRING_OPEN (parts+=stringLiteralContent)* STRING_CLOSE # stringLiteral
|
||||||
|
| expression DOT ID # propReference
|
||||||
| LPAREN expression RPAREN # parenExpression
|
| LPAREN expression RPAREN # parenExpression
|
||||||
| MINUS expression # minusExpression
|
| MINUS expression # minusExpression
|
||||||
|
| NOT expression # negateExpression
|
||||||
| expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1
|
| expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1
|
||||||
| expression operator=(PLUS|MINUS) expression # binaryOperation2;
|
| expression operator=(PLUS|MINUS) expression # binaryOperation2
|
||||||
|
| expression operator=(EQ|LT|LTEQ|GT|GTEQ) expression # comparisonOperation
|
||||||
type : DECIMAL # decimal
|
| expression operator=(AND|OR) expression # joinOperation
|
||||||
| INT # integer
|
| expression QUESTION_MARK expression COLON expression # ternaryExpression;
|
||||||
| STRING # string ;
|
|
||||||
|
|
||||||
|
|
||||||
|
stringLiteralContent : STRING_CONTENT;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker
|
||||||
|
import org.openrndr.extra.expressions.ExpressionException
|
||||||
|
|
||||||
|
fun <T0, R> compileFunction1OrNull(
|
||||||
|
expression: String,
|
||||||
|
parameter0: String,
|
||||||
|
constants: (String)->Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): ((T0) -> R)? {
|
||||||
|
require(constants(parameter0) == null) {
|
||||||
|
"${parameter0} is in constants with value '${constants(parameter0)}"
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val root = org.openrndr.extra.expressions.typed.expressionRoot(expression)
|
||||||
|
|
||||||
|
var varP0: T0? = null
|
||||||
|
val variables = fun(p : String) : Any? {
|
||||||
|
return if (p == parameter0) {
|
||||||
|
varP0
|
||||||
|
} else {
|
||||||
|
constants(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val listener = TypedExpressionListener(functions, variables)
|
||||||
|
|
||||||
|
return { p0 ->
|
||||||
|
varP0 = p0
|
||||||
|
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||||
|
listener.lastExpressionResult as? R ?: error("no result")
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (e: ExpressionException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.transforms.scale
|
||||||
|
import org.openrndr.math.transforms.translate
|
||||||
|
import kotlin.math.abs as abs_
|
||||||
|
import kotlin.math.cos as cos_
|
||||||
|
import kotlin.math.sin as sin_
|
||||||
|
import kotlin.math.sqrt as sqrt_
|
||||||
|
|
||||||
|
internal fun vec2(x: Any): Vector2 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector2(x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any): Vector3 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector3(x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec4(x: Any): Vector4 {
|
||||||
|
require(x is Double)
|
||||||
|
return Vector4(x, x, x, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun rgba(x: Any): ColorRGBa {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> ColorRGBa(x, x, x, 1.0)
|
||||||
|
is Vector3 -> ColorRGBa(x.x, x.y, x.z, 1.0)
|
||||||
|
is Vector4 -> ColorRGBa(x.x, x.y, x.z, x.w)
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun cos(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> cos_(x)
|
||||||
|
is Vector2 -> x.map { cos_(it) }
|
||||||
|
is Vector3 -> x.map { cos_(it) }
|
||||||
|
is Vector4 -> x.map { cos_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun sin(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> sin_(x)
|
||||||
|
is Vector2 -> x.map { sin_(it) }
|
||||||
|
is Vector3 -> x.map { sin_(it) }
|
||||||
|
is Vector4 -> x.map { sin_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun normalize(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Vector2 -> x.normalized
|
||||||
|
is Vector3 -> x.normalized
|
||||||
|
is Vector4 -> x.normalized
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun inverse(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Matrix44 -> x.inversed
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun transpose(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Matrix44 -> x.transposed
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun abs(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> abs_(x)
|
||||||
|
is Vector2 -> x.map { abs_(it) }
|
||||||
|
is Vector3 -> x.map { abs_(it) }
|
||||||
|
is Vector4 -> x.map { abs_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun scale(scale: Any): Matrix44 {
|
||||||
|
@Suppress("NAME_SHADOWING") val scale = when (scale) {
|
||||||
|
is Double -> Vector3(scale, scale, scale)
|
||||||
|
is Vector2 -> scale.xy1
|
||||||
|
is Vector3 -> scale
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.scale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun sqrt(x: Any): Any {
|
||||||
|
return when (x) {
|
||||||
|
is Double -> sqrt_(x)
|
||||||
|
is Vector2 -> x.map { sqrt_(it) }
|
||||||
|
is Vector3 -> x.map { sqrt_(it) }
|
||||||
|
is Vector4 -> x.map { sqrt_(it) }
|
||||||
|
else -> error("type not supported ${x::class.simpleName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun translate(translation: Any): Matrix44 {
|
||||||
|
@Suppress("NAME_SHADOWING") val translation = when (translation) {
|
||||||
|
is Vector2 -> translation.xy0
|
||||||
|
is Vector3 -> translation
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.translate(translation)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction1(name: String, functions: Map<String, TypedFunction1>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec2" -> { x -> vec2(x[0]) }
|
||||||
|
"vec3" -> { x -> vec3(x[0]) }
|
||||||
|
"vec4" -> { x -> vec4(x[0]) }
|
||||||
|
|
||||||
|
"cos" -> { x -> cos(x[0]) }
|
||||||
|
"sin" -> { x -> sin(x[0]) }
|
||||||
|
"sqrt" -> { v -> sqrt(v[0]) }
|
||||||
|
"abs" -> { v -> abs(v[0]) }
|
||||||
|
"scale" -> { x -> scale(x[0]) }
|
||||||
|
"translate" -> { x -> translate(x[0]) }
|
||||||
|
"transpose" -> { x -> transpose(x[0]) }
|
||||||
|
"inverse" -> { x -> inverse(x[0]) }
|
||||||
|
"normalize" -> { x -> normalize(x[0]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.transforms.rotate
|
||||||
|
import org.openrndr.math.transforms.translate
|
||||||
|
import org.openrndr.math.min as min_
|
||||||
|
import org.openrndr.math.max as max_
|
||||||
|
|
||||||
|
import kotlin.math.max as max_
|
||||||
|
import kotlin.math.min as min_
|
||||||
|
|
||||||
|
internal fun rotate(axis: Any, angleInDegrees:Any): Matrix44 {
|
||||||
|
require(angleInDegrees is Double)
|
||||||
|
@Suppress("NAME_SHADOWING") val axis = when(axis) {
|
||||||
|
is Vector2 -> axis.xy0
|
||||||
|
is Vector3 -> axis
|
||||||
|
else -> error("unsupported axis argument")
|
||||||
|
}
|
||||||
|
return Matrix44.rotate(axis, angleInDegrees)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun min(x: Any, y: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double -> min_(x, y)
|
||||||
|
x is Vector2 && y is Vector2 -> min_(x, y)
|
||||||
|
x is Vector3 && y is Vector3 -> min_(x, y)
|
||||||
|
x is Vector4 && y is Vector4 -> min_(x, y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun max(x: Any, y: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double -> max_(x, y)
|
||||||
|
x is Vector2 && y is Vector2 -> max_(x, y)
|
||||||
|
x is Vector3 && y is Vector3 -> max_(x, y)
|
||||||
|
x is Vector4 && y is Vector4 -> max_(x, y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec2(x: Any, y: Any): Vector2 {
|
||||||
|
require(x is Double)
|
||||||
|
require(y is Double)
|
||||||
|
return Vector2(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any, y: Any): Vector3 = when {
|
||||||
|
x is Double && y is Vector2 -> {
|
||||||
|
Vector3(x, y.x, y.y)
|
||||||
|
}
|
||||||
|
x is Vector2 && y is Double -> {
|
||||||
|
Vector3(x.x, x.y, y)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
error("unsupported arguments, '$x' (${x::class}) '$y' (${y::class}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction2(name: String, functions: Map<String, TypedFunction2>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"min" -> { x -> min(x[0], x[1]) }
|
||||||
|
"max" -> { x -> max(x[0], x[1]) }
|
||||||
|
"vec2" -> { x -> vec2(x[0], x[1]) }
|
||||||
|
"vec3" -> { x -> vec3(x[0], x[1]) }
|
||||||
|
"rotate" -> { x -> rotate(x[0], x[1]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0], x[1]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
import org.openrndr.math.mix as mix_
|
||||||
|
|
||||||
|
internal fun mix(x: Any, y: Any, f: Any): Any {
|
||||||
|
return when {
|
||||||
|
x is Double && y is Double && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector2 && y is Vector2 && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector3 && y is Vector3 && f is Double -> mix_(x, y, f)
|
||||||
|
x is Vector4 && y is Vector4 && f is Double -> mix_(x, y, f)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec3(x: Any, y: Any, z: Any): Vector3 {
|
||||||
|
require(x is Double && y is Double && z is Double)
|
||||||
|
return Vector3(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun vec4(x: Any, y: Any, z: Any): Vector4 {
|
||||||
|
return when {
|
||||||
|
x is Vector2 && y is Double && z is Double -> Vector4(x.x, x.y, y, z)
|
||||||
|
x is Double && y is Vector2 && z is Double -> Vector4(x, y.x, y.y, z)
|
||||||
|
x is Double && y is Double && z is Vector2 -> Vector4(x, y, z.x, z.y)
|
||||||
|
else -> error("unsupported arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun dispatchFunction3(name: String, functions: Map<String, TypedFunction3>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec3" -> { x -> vec3(x[0], x[1], x[2]) }
|
||||||
|
"vec4" -> { x -> vec4(x[0], x[1], x[2]) }
|
||||||
|
"mix" -> { x -> mix(x[0], x[1], x[2]) }
|
||||||
|
else -> functions[name]?.let {
|
||||||
|
{ x: Array<Any> ->
|
||||||
|
it.invoke(x[0], x[1], x[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
|
||||||
|
internal fun vec4(x: Any, y: Any, z: Any, w: Any): Vector4 {
|
||||||
|
require(x is Double && y is Double && z is Double && w is Double)
|
||||||
|
return Vector4(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mat4(x: Any, y: Any, z: Any, w: Any): Matrix44 {
|
||||||
|
require(x is Vector4 && y is Vector4 && z is Vector4 && w is Vector4)
|
||||||
|
return Matrix44.fromColumnVectors(x, y, z, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun rgba(r: Any, g: Any, b: Any, a: Any): ColorRGBa {
|
||||||
|
require(r is Double && g is Double && b is Double && a is Double)
|
||||||
|
return ColorRGBa(r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun dispatchFunction4(name: String, functions: Map<String, TypedFunction4>): ((Array<Any>) -> Any)? {
|
||||||
|
return when (name) {
|
||||||
|
"vec4" -> { x -> vec4(x[0], x[1], x[2], x[3]) }
|
||||||
|
"mat4" -> { x -> mat4(x[0], x[1], x[2], x[3]) }
|
||||||
|
"rgba" -> { x -> rgba(x[0], x[1], x[2], x[3]) }
|
||||||
|
else -> functions[name]?.let { { x: Array<Any> -> it.invoke(x[0], x[1], x[2], x[3]) } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
fun String.memberFunctions(n: String): ((Array<Any>) -> Any)? {
|
||||||
|
return when (n) {
|
||||||
|
"take" -> { n -> this.take((n[0] as Number).toInt()) }
|
||||||
|
"drop" -> { n -> this.drop((n[0] as Number).toInt()) }
|
||||||
|
"takeLast" -> { n -> this.takeLast((n[0] as Number).toInt()) }
|
||||||
|
"dropLast" -> { n -> this.takeLast((n[0] as Number).toInt()) }
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.math.Matrix44
|
||||||
|
import org.openrndr.math.Vector2
|
||||||
|
import org.openrndr.math.Vector3
|
||||||
|
import org.openrndr.math.Vector4
|
||||||
|
|
||||||
|
internal fun String.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"length" -> this.length.toDouble()
|
||||||
|
"uppercase" -> this.uppercase()
|
||||||
|
"lowercase" -> this.lowercase()
|
||||||
|
"reversed" -> this.reversed()
|
||||||
|
else -> error("unknown property '$property'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector2.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"xx" -> xx
|
||||||
|
"yx" -> yx
|
||||||
|
"yy" -> yy
|
||||||
|
"xy" -> this
|
||||||
|
"xxx" -> Vector3(x, x, x)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector3.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"z" -> z
|
||||||
|
"xx" -> Vector2(x, x)
|
||||||
|
"yx" -> Vector2(y, x)
|
||||||
|
"yy" -> Vector2(y, y)
|
||||||
|
"xy" -> Vector2(x, y)
|
||||||
|
"zx" -> Vector2(z, x)
|
||||||
|
"xz" -> Vector2(x, z)
|
||||||
|
"xxx" -> Vector3(x, x, x)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Vector4.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"x" -> x
|
||||||
|
"y" -> y
|
||||||
|
"z" -> z
|
||||||
|
"xx" -> Vector2(x, x)
|
||||||
|
"yx" -> Vector2(y, x)
|
||||||
|
"yy" -> Vector2(y, y)
|
||||||
|
"xy" -> Vector2(x, y)
|
||||||
|
"zx" -> Vector2(z, x)
|
||||||
|
"xz" -> Vector2(x, z)
|
||||||
|
"xyz" -> Vector3(x, y, z)
|
||||||
|
"xxy" -> Vector3(x, x, y)
|
||||||
|
"length" -> length
|
||||||
|
"normalized" -> normalized
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ColorRGBa.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"r" -> r
|
||||||
|
"g" -> g
|
||||||
|
"b" -> b
|
||||||
|
"a" -> alpha
|
||||||
|
"linear" -> toLinear()
|
||||||
|
"srgb" -> toSRGB()
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Matrix44.property(property: String): Any {
|
||||||
|
return when (property) {
|
||||||
|
"inversed" -> inversed
|
||||||
|
"transposed" -> transposed
|
||||||
|
else -> error("unknown property '$property")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,686 @@
|
|||||||
|
package org.openrndr.extra.expressions.typed
|
||||||
|
|
||||||
|
import org.antlr.v4.kotlinruntime.*
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker
|
||||||
|
import org.antlr.v4.kotlinruntime.tree.TerminalNode
|
||||||
|
import org.openrndr.collections.pop
|
||||||
|
import org.openrndr.collections.push
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangLexer
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangParser
|
||||||
|
import org.openrndr.extra.expressions.parser.KeyLangParserBaseListener
|
||||||
|
|
||||||
|
import org.openrndr.extra.noise.uniform
|
||||||
|
import org.openrndr.math.*
|
||||||
|
import kotlin.math.*
|
||||||
|
|
||||||
|
typealias TypedFunction0 = () -> Any
|
||||||
|
typealias TypedFunction1 = (Any) -> Any
|
||||||
|
typealias TypedFunction2 = (Any, Any) -> Any
|
||||||
|
typealias TypedFunction3 = (Any, Any, Any) -> Any
|
||||||
|
typealias TypedFunction4 = (Any, Any, Any, Any) -> Any
|
||||||
|
typealias TypedFunction5 = (Any, Any, Any, Any, Any) -> Any
|
||||||
|
|
||||||
|
|
||||||
|
private fun ArrayDeque<Any>.pushChecked(item: Any) {
|
||||||
|
// require(item is Double || item is Vector2 || item is Vector3 || item is Vector4 || item is Map<*, *> || item is Matrix44) {
|
||||||
|
//
|
||||||
|
// "$item ${item::class}"
|
||||||
|
// }
|
||||||
|
push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypedFunctionExtensions(
|
||||||
|
val functions0: Map<String, TypedFunction0> = emptyMap(),
|
||||||
|
val functions1: Map<String, TypedFunction1> = emptyMap(),
|
||||||
|
val functions2: Map<String, TypedFunction2> = emptyMap(),
|
||||||
|
val functions3: Map<String, TypedFunction3> = emptyMap(),
|
||||||
|
val functions4: Map<String, TypedFunction4> = emptyMap(),
|
||||||
|
val functions5: Map<String, TypedFunction5> = emptyMap()
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = TypedFunctionExtensions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class IDType {
|
||||||
|
VARIABLE,
|
||||||
|
PROPERTY,
|
||||||
|
MEMBER_FUNCTION0,
|
||||||
|
MEMBER_FUNCTION1,
|
||||||
|
MEMBER_FUNCTION2,
|
||||||
|
MEMBER_FUNCTION3,
|
||||||
|
FUNCTION0,
|
||||||
|
FUNCTION1,
|
||||||
|
FUNCTION2,
|
||||||
|
FUNCTION3,
|
||||||
|
FUNCTION4,
|
||||||
|
FUNCTION5
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TypedExpressionListener(
|
||||||
|
val functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY,
|
||||||
|
val constants: (String) -> Any? = { null }
|
||||||
|
) :
|
||||||
|
KeyLangParserBaseListener() {
|
||||||
|
val valueStack = ArrayDeque<Any>()
|
||||||
|
val functionStack = ArrayDeque<(Array<Any>) -> Any>()
|
||||||
|
val propertyStack = ArrayDeque<String>()
|
||||||
|
|
||||||
|
val idTypeStack = ArrayDeque<IDType>()
|
||||||
|
var lastExpressionResult: Any? = null
|
||||||
|
|
||||||
|
val exceptionStack = ArrayDeque<ExpressionException>()
|
||||||
|
|
||||||
|
override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) {
|
||||||
|
ifError {
|
||||||
|
throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}")
|
||||||
|
}
|
||||||
|
val result = valueStack.pop()
|
||||||
|
lastExpressionResult = result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) {
|
||||||
|
val op = valueStack.pop()
|
||||||
|
valueStack.pushChecked(
|
||||||
|
when (op) {
|
||||||
|
is Double -> -op
|
||||||
|
is Vector3 -> -op
|
||||||
|
is Vector2 -> -op
|
||||||
|
is Vector4 -> -op
|
||||||
|
is Matrix44 -> op * -1.0
|
||||||
|
else -> error("unsupported type")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.ASTERISK -> when {
|
||||||
|
left is Double && right is Double -> left * right
|
||||||
|
left is Vector2 && right is Vector2 -> left * right
|
||||||
|
left is Vector2 && right is Double -> left * right
|
||||||
|
left is Vector3 && right is Vector3 -> left * right
|
||||||
|
left is Vector3 && right is Double -> left * right
|
||||||
|
left is Vector4 && right is Vector4 -> left * right
|
||||||
|
left is Vector4 && right is Double -> left * right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left * right
|
||||||
|
left is Matrix44 && right is Vector4 -> left * right
|
||||||
|
left is Matrix44 && right is Double -> left * right
|
||||||
|
left is ColorRGBa && right is Double -> left * right
|
||||||
|
left is String && right is Double -> left.repeat(right.roundToInt())
|
||||||
|
else -> error("unsupported operands for * operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.DIVISION -> when {
|
||||||
|
left is Double && right is Double -> left / right
|
||||||
|
left is Vector2 && right is Vector2 -> left / right
|
||||||
|
left is Vector2 && right is Double -> left / right
|
||||||
|
left is Vector3 && right is Vector3 -> left / right
|
||||||
|
left is Vector3 && right is Double -> left / right
|
||||||
|
left is Vector4 && right is Vector4 -> left / right
|
||||||
|
left is Vector4 && right is Double -> left / right
|
||||||
|
left is ColorRGBa && right is Double -> left / right
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.PERCENTAGE -> when {
|
||||||
|
left is Double && right is Double -> left.mod(right)
|
||||||
|
left is Vector2 && right is Vector2 -> left.mod(right)
|
||||||
|
left is Vector3 && right is Vector3 -> left.mod(right)
|
||||||
|
left is Vector4 && right is Vector4 -> left.mod(right)
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("IMPLICIT_CAST_TO_ANY")
|
||||||
|
override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.PLUS -> when {
|
||||||
|
left is Double && right is Double -> left + right
|
||||||
|
left is Vector2 && right is Vector2 -> left + right
|
||||||
|
left is Vector3 && right is Vector3 -> left + right
|
||||||
|
left is Vector4 && right is Vector4 -> left + right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left + right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left + right
|
||||||
|
left is String && right is String -> left + right
|
||||||
|
else -> error("unsupported operands for + operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyLangLexer.Tokens.MINUS -> when {
|
||||||
|
left is Double && right is Double -> left - right
|
||||||
|
left is Vector2 && right is Vector2 -> left - right
|
||||||
|
left is Vector3 && right is Vector3 -> left - right
|
||||||
|
left is Vector4 && right is Vector4 -> left - right
|
||||||
|
left is Matrix44 && right is Matrix44 -> left - right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left - right
|
||||||
|
else -> error("unsupported operands for - operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitJoinOperation(ctx: KeyLangParser.JoinOperationContext) {
|
||||||
|
val right = (valueStack.pop() as Double).roundToInt()
|
||||||
|
val left = (valueStack.pop() as Double).roundToInt()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.AND -> right != 0 && left != 0
|
||||||
|
KeyLangLexer.Tokens.OR -> right != 0 || left != 0
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(if (result) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitComparisonOperation(ctx: KeyLangParser.ComparisonOperationContext) {
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (val operator = ctx.operator?.type) {
|
||||||
|
KeyLangLexer.Tokens.EQ -> when {
|
||||||
|
left is Double && right is Double -> left == right
|
||||||
|
left is Vector2 && right is Vector2 -> left == right
|
||||||
|
left is Vector3 && right is Vector3 -> left == right
|
||||||
|
left is Vector4 && right is Vector4 -> left == right
|
||||||
|
left is ColorRGBa && right is ColorRGBa -> left == right
|
||||||
|
left is String && right is String -> left == right
|
||||||
|
else -> error("unsupported operands for == operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.LTEQ -> when {
|
||||||
|
left is Double && right is Double -> left <= right
|
||||||
|
else -> error("unsupported operands for <= operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.LT -> when {
|
||||||
|
left is Double && right is Double -> left < right
|
||||||
|
else -> error("unsupported operands for < operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.GTEQ -> when {
|
||||||
|
left is Double && right is Double -> left >= right
|
||||||
|
else -> error("unsupported operands for >= operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
KeyLangLexer.Tokens.GT -> when {
|
||||||
|
left is Double && right is Double -> left > right
|
||||||
|
else -> error("unsupported operands for > operator left:${left::class} right:${right::class}")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("operator '$operator' not implemented")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(if (result) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitNegateExpression(ctx: KeyLangParser.NegateExpressionContext) {
|
||||||
|
val operand = (valueStack.pop() as Double).roundToInt()
|
||||||
|
valueStack.pushChecked(if (operand == 0) 1.0 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitTernaryExpression(ctx: KeyLangParser.TernaryExpressionContext) {
|
||||||
|
val right = valueStack.pop()
|
||||||
|
val left = valueStack.pop()
|
||||||
|
val comp = valueStack.pop()
|
||||||
|
|
||||||
|
val result = when (comp) {
|
||||||
|
is Double -> if (comp.roundToInt() != 0) left else right
|
||||||
|
else -> error("can't compare")
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) {
|
||||||
|
idTypeStack.push(IDType.VARIABLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(emptyArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(valueStack.pop())))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun enterMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) {
|
||||||
|
idTypeStack.push(IDType.MEMBER_FUNCTION3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) {
|
||||||
|
ifError {
|
||||||
|
pushError(it.message ?: "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1, argument2)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(arrayOf())
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3))
|
||||||
|
valueStack.pushChecked(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 = valueStack.pop()
|
||||||
|
val argument3 = valueStack.pop()
|
||||||
|
val argument2 = valueStack.pop()
|
||||||
|
val argument1 = valueStack.pop()
|
||||||
|
val argument0 = valueStack.pop()
|
||||||
|
|
||||||
|
val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3, argument4))
|
||||||
|
valueStack.pushChecked(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 enterPropReference(ctx: KeyLangParser.PropReferenceContext) {
|
||||||
|
idTypeStack.push(IDType.PROPERTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exitPropReference(ctx: KeyLangParser.PropReferenceContext) {
|
||||||
|
val root = valueStack.pop()
|
||||||
|
var current = root
|
||||||
|
val property = propertyStack.pop()
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
current = when (current) {
|
||||||
|
is Map<*, *> -> current[property] ?: error("property '$property' not found")
|
||||||
|
is Function<*> -> (current as ((String) -> Any?)).invoke(property)
|
||||||
|
?: error("property '$property' not found")
|
||||||
|
|
||||||
|
is Vector2 -> current.property(property)
|
||||||
|
is Vector3 -> current.property(property)
|
||||||
|
is Vector4 -> current.property(property)
|
||||||
|
is ColorRGBa -> current.property(property)
|
||||||
|
is Matrix44 -> current.property(property)
|
||||||
|
else -> error("can't look up: ${current::class} '$current', root:'$root' ${ctx.text} ")
|
||||||
|
}
|
||||||
|
valueStack.push(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun visitTerminal(node: TerminalNode) {
|
||||||
|
|
||||||
|
val type = node.symbol.type
|
||||||
|
if (type == KeyLangParser.Tokens.INTLIT) {
|
||||||
|
valueStack.pushChecked(node.text.toDouble())
|
||||||
|
} else if (type == KeyLangParser.Tokens.DECLIT) {
|
||||||
|
valueStack.pushChecked(node.text.toDouble())
|
||||||
|
} else if (type == KeyLangParser.Tokens.STRING_CONTENT) {
|
||||||
|
valueStack.pushChecked(node.text)
|
||||||
|
} else if (type == KeyLangParser.Tokens.ID) {
|
||||||
|
val name = node.text.replace("`", "")
|
||||||
|
@Suppress("DIVISION_BY_ZERO")
|
||||||
|
when (val idType = idTypeStack.pop()) {
|
||||||
|
IDType.VARIABLE -> valueStack.pushChecked(
|
||||||
|
when (name) {
|
||||||
|
"PI" -> PI
|
||||||
|
else -> constants(name) ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
IDType.PROPERTY -> propertyStack.push(name)
|
||||||
|
|
||||||
|
IDType.FUNCTION0 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
when (name) {
|
||||||
|
"random" -> { _ -> Double.uniform(0.0, 1.0) }
|
||||||
|
else -> functions.functions0[name]?.let { { _: Array<Any> -> it.invoke() } }
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}()'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
}
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION0,
|
||||||
|
IDType.MEMBER_FUNCTION1,
|
||||||
|
IDType.MEMBER_FUNCTION2,
|
||||||
|
IDType.MEMBER_FUNCTION3 -> {
|
||||||
|
val receiver = valueStack.pop()
|
||||||
|
when (receiver) {
|
||||||
|
is String -> {
|
||||||
|
functionStack.push(
|
||||||
|
receiver.memberFunctions(name)
|
||||||
|
?: error("no member function '$receiver.$name()'")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ColorRGBa -> {
|
||||||
|
when (idType) {
|
||||||
|
IDType.MEMBER_FUNCTION1 -> {
|
||||||
|
functionStack.push(when (name) {
|
||||||
|
"shade" -> { x -> receiver.shade(x[0] as Double) }
|
||||||
|
"opacify" -> { x -> receiver.opacify(x[0] as Double) }
|
||||||
|
else -> error("no member function '$receiver.$name()'")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("no member function $idType '$receiver.$name()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
is Function<*> -> {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
receiver as (String) -> Any
|
||||||
|
@Suppress("UNCHECKED_CAST") val function =
|
||||||
|
receiver.invoke(name) ?: error("no such function $name")
|
||||||
|
|
||||||
|
when (idType) {
|
||||||
|
IDType.MEMBER_FUNCTION0 -> {
|
||||||
|
function as () -> Any
|
||||||
|
functionStack.push({ function() })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION1 -> {
|
||||||
|
function as (Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION2 -> {
|
||||||
|
function as (Any, Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0], x[1]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.MEMBER_FUNCTION3 -> {
|
||||||
|
function as (Any, Any, Any) -> Any
|
||||||
|
functionStack.push({ x -> function(x[0], x[1], x[2]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("receiver '${receiver}' not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION1 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction1(name, functions.functions1)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION2 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction2(name, functions.functions2)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0, x1)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION3 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction3(name, functions.functions3)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
IDType.FUNCTION4 -> {
|
||||||
|
val function: (Array<Any>) -> Any =
|
||||||
|
dispatchFunction4(name, functions.functions4)
|
||||||
|
?: errorValue(
|
||||||
|
"unresolved function: '${name}(x0)'"
|
||||||
|
) { _ -> error("this is the error function") }
|
||||||
|
functionStack.push(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> error("unsupported id-type $idType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpressionException(message: String) : RuntimeException(message)
|
||||||
|
|
||||||
|
fun evaluateTypedExpression(
|
||||||
|
expression: String,
|
||||||
|
constants: (String) -> Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): Any? {
|
||||||
|
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 = TypedExpressionListener(functions, constants)
|
||||||
|
try {
|
||||||
|
ParseTreeWalker.DEFAULT.walk(listener, root)
|
||||||
|
} catch (e: ExpressionException) {
|
||||||
|
throw ExpressionException(e.message ?: "")
|
||||||
|
}
|
||||||
|
return listener.lastExpressionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compileTypedExpression(
|
||||||
|
expression: String,
|
||||||
|
constants: (String) -> Any? = { null },
|
||||||
|
functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY
|
||||||
|
): () -> Any {
|
||||||
|
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 = TypedExpressionListener(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()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,25 +13,17 @@ class TestExpressionErrors {
|
|||||||
val expression = ")("
|
val expression = ")("
|
||||||
invoking {
|
invoking {
|
||||||
evaluateExpression(expression)
|
evaluateExpression(expression)
|
||||||
} `should throw` ExpressionException::class `with message` "parser error in expression: ')('; [line: 1, character: 0 , near: [@0,0:0=')',<21>,1:0] ]"
|
} `should throw` ExpressionException::class
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
fun `an expression trying to reassign a number`() {
|
fun `an expression trying to reassign a number`() {
|
||||||
val expression = "3 = 5"
|
val expression = "3 = 5"
|
||||||
invoking {
|
invoking {
|
||||||
evaluateExpression(expression)
|
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] ]"
|
} `should throw` ExpressionException::class
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ include(
|
|||||||
"orx-easing",
|
"orx-easing",
|
||||||
"orx-envelopes",
|
"orx-envelopes",
|
||||||
"orx-expression-evaluator",
|
"orx-expression-evaluator",
|
||||||
|
"orx-expression-evaluator-typed",
|
||||||
"orx-fcurve",
|
"orx-fcurve",
|
||||||
"orx-fft",
|
"orx-fft",
|
||||||
"orx-jvm:orx-file-watcher",
|
"orx-jvm:orx-file-watcher",
|
||||||
|
|||||||
Reference in New Issue
Block a user