diff --git a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt index b29493c3..f074a8f0 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt @@ -1,37 +1,171 @@ package org.openrndr.extra.expressions.typed import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker -import org.openrndr.extra.expressions.ExpressionException + +/** + * Compile a function + */ +fun compileFunction1( + 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)}" + } + val root = expressionRoot(expression) + + var varP0: T0? = null + val constantValues = fun(p: String): Any? { + return if (p == parameter0) { + varP0 + } else { + constants(p) + } + } + val listener = TypedExpressionListener(functions, constantValues) + + return { p0 -> + varP0 = p0 + ParseTreeWalker.DEFAULT.walk(listener, root) + @Suppress("UNCHECKED_CAST") + listener.state.lastExpressionResult as? R ?: error("no result") + } +} fun compileFunction1OrNull( expression: String, parameter0: String, - constants: (String)->Any? = { null }, - functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY + constants: (String) -> Any? = { null }, + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY, + onError: (Throwable) -> Unit = { } ): ((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 compileFunction1(expression, parameter0, constants, functions) + } catch (e: Throwable) { + onError(e) return null } } + +// + +fun compileFunction2( + expression: String, + parameter0: String, + parameter1: String, + constants: (String) -> Any? = { null }, + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY +): ((T0, T1) -> R) { + require(constants(parameter0) == null) { + "${parameter0} is in constants with value '${constants(parameter0)}" + } + require(constants(parameter1) == null) { + "${parameter1} is in constants with value '${constants(parameter1)}" + } + + val root = expressionRoot(expression) + + var varP0: T0? = null + var varP1: T1? = null + val constantValues = fun(p: String): Any? { + return if (p == parameter0) { + varP0 + } else if (p == parameter1) { + varP1 + } else { + constants(p) + } + } + val listener = TypedExpressionListener(functions, constantValues) + + return { p0, p1 -> + varP0 = p0 + varP1 = p1 + ParseTreeWalker.DEFAULT.walk(listener, root) + @Suppress("UNCHECKED_CAST") + listener.state.lastExpressionResult as? R ?: error("no result") + } +} + +fun compileFunction2OrNull( + expression: String, + parameter0: String, + parameter1: String, + constants: (String) -> Any? = { null }, + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY, + onError: (Throwable) -> Unit = { } +): ((T0, T1) -> R)? { + try { + return compileFunction2(expression, parameter0, parameter1, constants, functions) + } catch (e: Throwable) { + onError(e) + return null + } +} + +// + +fun compileFunction3( + expression: String, + parameter0: String, + parameter1: String, + parameter2: String, + constants: (String) -> Any? = { null }, + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY +): ((T0, T1, T2) -> R) { + require(constants(parameter0) == null) { + "${parameter0} is in constants with value '${constants(parameter0)}" + } + require(constants(parameter1) == null) { + "${parameter1} is in constants with value '${constants(parameter1)}" + } + require(constants(parameter2) == null) { + "${parameter2} is in constants with value '${constants(parameter2)}" + } + + val root = expressionRoot(expression) + + var varP0: T0? = null + var varP1: T1? = null + var varP2: T2? = null + val constantValues = fun(p: String): Any? { + return if (p == parameter0) { + varP0 + } else if (p == parameter1) { + varP1 + } else if (p == parameter2) { + varP2 + } else { + constants(p) + } + } + val listener = TypedExpressionListener(functions, constantValues) + + return { p0, p1, p2 -> + varP0 = p0 + varP1 = p1 + varP2 = p2 + ParseTreeWalker.DEFAULT.walk(listener, root) + @Suppress("UNCHECKED_CAST") + listener.state.lastExpressionResult as? R ?: error("no result") + } +} + +fun compileFunction3OrNull( + expression: String, + parameter0: String, + parameter1: String, + parameter2: String, + constants: (String) -> Any? = { null }, + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY, + onError: (Throwable) -> Unit = { } +): ((T0, T1, T2) -> R)? { + try { + return compileFunction3(expression, parameter0, parameter1, parameter2, constants, functions) + } catch (e: Throwable) { + onError(e) + return null + } +} \ No newline at end of file diff --git a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt index bf2bd718..2745a00e 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt @@ -9,6 +9,7 @@ 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.expressions.parser.KeyLangParserVisitor import org.openrndr.extra.noise.uniform import org.openrndr.math.* @@ -43,7 +44,7 @@ class TypedFunctionExtensions( } } -internal enum class IDType { +enum class IDType { VARIABLE, PROPERTY, MEMBER_FUNCTION0, @@ -58,31 +59,39 @@ internal enum class IDType { FUNCTION5 } -internal class TypedExpressionListener( +abstract class TypedExpressionListenerBase( val functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY, val constants: (String) -> Any? = { null } ) : KeyLangParserBaseListener() { - val valueStack = ArrayDeque() - val functionStack = ArrayDeque<(Array) -> Any>() - val propertyStack = ArrayDeque() - val idTypeStack = ArrayDeque() - var lastExpressionResult: Any? = null + class State { + val valueStack = ArrayDeque() + val functionStack = ArrayDeque<(Array) -> Any>() + val propertyStack = ArrayDeque() - val exceptionStack = ArrayDeque() + val idTypeStack = ArrayDeque() + var lastExpressionResult: Any? = null + + val exceptionStack = ArrayDeque() + } + + abstract val state: State override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) { ifError { throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}") } - val result = valueStack.pop() - lastExpressionResult = result + val result = state.valueStack.pop() + state.lastExpressionResult = result } override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) { - val op = valueStack.pop() - valueStack.pushChecked( + val s = state + + + val op = s.valueStack.pop() + s.valueStack.pushChecked( when (op) { is Double -> -op is Vector3 -> -op @@ -95,13 +104,15 @@ internal class TypedExpressionListener( } override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) { + val s = state + ifError { pushError(it.message ?: "") return } - val right = valueStack.pop() - val left = valueStack.pop() + val right = s.valueStack.pop() + val left = s.valueStack.pop() val result = when (val operator = ctx.operator?.type) { KeyLangLexer.Tokens.ASTERISK -> when { @@ -142,18 +153,19 @@ internal class TypedExpressionListener( else -> error("operator '$operator' not implemented") } - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } @Suppress("IMPLICIT_CAST_TO_ANY") override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) { + val s = state ifError { pushError(it.message ?: "") return } - val right = valueStack.pop() - val left = valueStack.pop() + val right = s.valueStack.pop() + val left = s.valueStack.pop() val result = when (val operator = ctx.operator?.type) { KeyLangLexer.Tokens.PLUS -> when { @@ -179,24 +191,27 @@ internal class TypedExpressionListener( else -> error("operator '$operator' not implemented") } - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun exitJoinOperation(ctx: KeyLangParser.JoinOperationContext) { - val right = (valueStack.pop() as Double).roundToInt() - val left = (valueStack.pop() as Double).roundToInt() + val s = state + val right = (s.valueStack.pop() as Double).roundToInt() + val left = (s.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) + s.valueStack.pushChecked(if (result) 1.0 else 0.0) } override fun exitComparisonOperation(ctx: KeyLangParser.ComparisonOperationContext) { - val right = valueStack.pop() - val left = valueStack.pop() + val s = state + + val right = s.valueStack.pop() + val left = s.valueStack.pop() val result = when (val operator = ctx.operator?.type) { KeyLangLexer.Tokens.EQ -> when { @@ -208,18 +223,22 @@ internal class TypedExpressionListener( 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}") @@ -227,195 +246,218 @@ internal class TypedExpressionListener( else -> error("operator '$operator' not implemented") } - valueStack.pushChecked(if (result) 1.0 else 0.0) + s.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) + val s = state + val operand = (s.valueStack.pop() as Double).roundToInt() + s.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 s = state + val right = s.valueStack.pop() + val left = s.valueStack.pop() + val comp = s.valueStack.pop() val result = when (comp) { is Double -> if (comp.roundToInt() != 0) left else right else -> error("can't compare") } - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) { - idTypeStack.push(IDType.VARIABLE) + val s = state + s.idTypeStack.push(IDType.VARIABLE) } override fun enterMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) { - idTypeStack.push(IDType.MEMBER_FUNCTION1) + val s = state + s.idTypeStack.push(IDType.MEMBER_FUNCTION1) } override fun exitMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - valueStack.pushChecked(functionStack.pop().invoke(emptyArray())) + s.valueStack.pushChecked(s.functionStack.pop().invoke(emptyArray())) } override fun enterMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) { - idTypeStack.push(IDType.MEMBER_FUNCTION1) + val s = state + s.idTypeStack.push(IDType.MEMBER_FUNCTION1) } override fun exitMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - valueStack.pushChecked(functionStack.pop().invoke(arrayOf(valueStack.pop()))) + s.valueStack.pushChecked(s.functionStack.pop().invoke(arrayOf(s.valueStack.pop()))) } override fun enterMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) { - idTypeStack.push(IDType.MEMBER_FUNCTION2) + val s = state + s.idTypeStack.push(IDType.MEMBER_FUNCTION2) } override fun exitMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val argument1 = valueStack.pop() - val argument0 = valueStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() - valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1))) + s.valueStack.pushChecked(s.functionStack.pop().invoke(arrayOf(argument0, argument1))) } override fun enterMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) { - idTypeStack.push(IDType.MEMBER_FUNCTION3) + val s = state + s.idTypeStack.push(IDType.MEMBER_FUNCTION3) } override fun exitMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val argument2 = valueStack.pop() - val argument1 = valueStack.pop() - val argument0 = valueStack.pop() + val argument2 = s.valueStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() - valueStack.pushChecked(functionStack.pop().invoke(arrayOf(argument0, argument1, argument2))) + s.valueStack.pushChecked(s.functionStack.pop().invoke(arrayOf(argument0, argument1, argument2))) } override fun enterFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) { - idTypeStack.push(IDType.FUNCTION0) + val s = state + s.idTypeStack.push(IDType.FUNCTION0) } override fun exitFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val function = functionStack.pop() + val function = s.functionStack.pop() val result = function.invoke(arrayOf()) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) { - idTypeStack.push(IDType.FUNCTION1) + val s = state + s.idTypeStack.push(IDType.FUNCTION1) } override fun exitFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val function = functionStack.pop() - val argument = valueStack.pop() + val function = s.functionStack.pop() + val argument = s.valueStack.pop() val result = function.invoke(arrayOf(argument)) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { - idTypeStack.push(IDType.FUNCTION2) + val s = state + s.idTypeStack.push(IDType.FUNCTION2) } override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val function = functionStack.pop() - val argument1 = valueStack.pop() - val argument0 = valueStack.pop() + val function = s.functionStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() val result = function.invoke(arrayOf(argument0, argument1)) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) { - idTypeStack.push(IDType.FUNCTION3) + val s = state + s.idTypeStack.push(IDType.FUNCTION3) } override fun exitFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) { + val s = state ifError { pushError(it.message ?: "") return } - val function = functionStack.pop() - val argument2 = valueStack.pop() - val argument1 = valueStack.pop() - val argument0 = valueStack.pop() + val function = s.functionStack.pop() + val argument2 = s.valueStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() val result = function.invoke(arrayOf(argument0, argument1, argument2)) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) { - idTypeStack.push(IDType.FUNCTION4) + val s = state + s.idTypeStack.push(IDType.FUNCTION4) } override fun exitFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) { + val s = state 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 function = s.functionStack.pop() + val argument3 = s.valueStack.pop() + val argument2 = s.valueStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3)) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } override fun enterFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) { - idTypeStack.push(IDType.FUNCTION5) + val s = state + s.idTypeStack.push(IDType.FUNCTION5) } override fun exitFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) { + val s = state 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 function = s.functionStack.pop() + val argument4 = s.valueStack.pop() + val argument3 = s.valueStack.pop() + val argument2 = s.valueStack.pop() + val argument1 = s.valueStack.pop() + val argument0 = s.valueStack.pop() val result = function.invoke(arrayOf(argument0, argument1, argument2, argument3, argument4)) - valueStack.pushChecked(result) + s.valueStack.pushChecked(result) } private fun errorValue(message: String, value: T): T { @@ -424,24 +466,28 @@ internal class TypedExpressionListener( } private fun pushError(message: String) { - exceptionStack.push(ExpressionException(message)) + val s = state + s.exceptionStack.push(ExpressionException(message)) } private inline fun ifError(f: (e: Throwable) -> Unit) { - if (exceptionStack.isNotEmpty()) { - val e = exceptionStack.pop() + val s = state + if (s.exceptionStack.isNotEmpty()) { + val e = s.exceptionStack.pop() f(e) } } override fun enterPropReference(ctx: KeyLangParser.PropReferenceContext) { - idTypeStack.push(IDType.PROPERTY) + val s = state + s.idTypeStack.push(IDType.PROPERTY) } override fun exitPropReference(ctx: KeyLangParser.PropReferenceContext) { - val root = valueStack.pop() + val s = state + val root = s.valueStack.pop() var current = root - val property = propertyStack.pop() + val property = s.propertyStack.pop() @Suppress("UNCHECKED_CAST") current = when (current) { is Map<*, *> -> current[property] ?: error("property '$property' not found") @@ -455,31 +501,31 @@ internal class TypedExpressionListener( is Matrix44 -> current.property(property) else -> error("can't look up: ${current::class} '$current', root:'$root' ${ctx.text} ") } - valueStack.push(current) + s.valueStack.push(current) } override fun visitTerminal(node: TerminalNode) { - + val s = state val type = node.symbol.type if (type == KeyLangParser.Tokens.INTLIT) { - valueStack.pushChecked(node.text.toDouble()) + s.valueStack.pushChecked(node.text.toDouble()) } else if (type == KeyLangParser.Tokens.DECLIT) { - valueStack.pushChecked(node.text.toDouble()) + s.valueStack.pushChecked(node.text.toDouble()) } else if (type == KeyLangParser.Tokens.STRING_CONTENT) { - valueStack.pushChecked(node.text) + s.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 (val idType = s.idTypeStack.pop()) { + IDType.VARIABLE -> s.valueStack.pushChecked( when (name) { "PI" -> PI else -> constants(name) ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0) } ) - IDType.PROPERTY -> propertyStack.push(name) + IDType.PROPERTY -> s.propertyStack.push(name) IDType.FUNCTION0 -> { val function: (Array) -> Any = @@ -490,17 +536,17 @@ internal class TypedExpressionListener( "unresolved function: '${name}()'" ) { _ -> error("this is the error function") } } - functionStack.push(function) + s.functionStack.push(function) } IDType.MEMBER_FUNCTION0, IDType.MEMBER_FUNCTION1, IDType.MEMBER_FUNCTION2, IDType.MEMBER_FUNCTION3 -> { - val receiver = valueStack.pop() + val receiver = s.valueStack.pop() when (receiver) { is String -> { - functionStack.push( + s.functionStack.push( receiver.memberFunctions(name) ?: error("no member function '$receiver.$name()'") ) @@ -509,7 +555,7 @@ internal class TypedExpressionListener( is ColorRGBa -> { when (idType) { IDType.MEMBER_FUNCTION1 -> { - functionStack.push(when (name) { + s.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()'") @@ -530,22 +576,22 @@ internal class TypedExpressionListener( when (idType) { IDType.MEMBER_FUNCTION0 -> { function as () -> Any - functionStack.push({ function() }) + s.functionStack.push({ function() }) } IDType.MEMBER_FUNCTION1 -> { function as (Any) -> Any - functionStack.push({ x -> function(x[0]) }) + s.functionStack.push({ x -> function(x[0]) }) } IDType.MEMBER_FUNCTION2 -> { function as (Any, Any) -> Any - functionStack.push({ x -> function(x[0], x[1]) }) + s.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]) }) + s.functionStack.push({ x -> function(x[0], x[1], x[2]) }) } else -> error("unreachable") @@ -557,12 +603,13 @@ internal class TypedExpressionListener( } IDType.FUNCTION1 -> { + val s = state val function: (Array) -> Any = dispatchFunction1(name, functions.functions1) ?: errorValue( "unresolved function: '${name}(x0)'" ) { _ -> error("this is the error function") } - functionStack.push(function) + s.functionStack.push(function) } IDType.FUNCTION2 -> { @@ -571,7 +618,7 @@ internal class TypedExpressionListener( ?: errorValue( "unresolved function: '${name}(x0, x1)'" ) { _ -> error("this is the error function") } - functionStack.push(function) + s.functionStack.push(function) } IDType.FUNCTION3 -> { @@ -580,7 +627,7 @@ internal class TypedExpressionListener( ?: errorValue( "unresolved function: '${name}(x0)'" ) { _ -> error("this is the error function") } - functionStack.push(function) + s.functionStack.push(function) } IDType.FUNCTION4 -> { @@ -589,7 +636,7 @@ internal class TypedExpressionListener( ?: errorValue( "unresolved function: '${name}(x0)'" ) { _ -> error("this is the error function") } - functionStack.push(function) + s.functionStack.push(function) } else -> error("unsupported id-type $idType") @@ -598,6 +645,14 @@ internal class TypedExpressionListener( } } +expect class TypedExpressionListener( + functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY, + constants: (String) -> Any? = { null } +) : TypedExpressionListenerBase { + + override val state: State +} + class ExpressionException(message: String) : RuntimeException(message) fun evaluateTypedExpression( @@ -628,7 +683,8 @@ fun evaluateTypedExpression( } catch (e: ExpressionException) { throw ExpressionException(e.message ?: "") } - return listener.lastExpressionResult + + return listener.state.lastExpressionResult } fun compileTypedExpression( @@ -661,7 +717,7 @@ fun compileTypedExpression( } catch (e: ExpressionException) { throw ExpressionException(e.message ?: "") } - listener.lastExpressionResult ?: error("no result") + listener.state.lastExpressionResult ?: error("no result") } } diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/CompiledFunctions.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/CompiledFunctions.kt deleted file mode 100644 index b29493c3..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/CompiledFunctions.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.openrndr.extra.expressions.typed - -import org.antlr.v4.kotlinruntime.tree.ParseTreeWalker -import org.openrndr.extra.expressions.ExpressionException - -fun 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 - } -} diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function1.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/Function1.kt deleted file mode 100644 index 0ef205cd..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function1.kt +++ /dev/null @@ -1,140 +0,0 @@ -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): ((Array) -> 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 -> it.invoke(x[0]) } } - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function2.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/Function2.kt deleted file mode 100644 index 5f53a42a..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function2.kt +++ /dev/null @@ -1,73 +0,0 @@ -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): ((Array) -> 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 -> it.invoke(x[0], x[1]) } } - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function3.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/Function3.kt deleted file mode 100644 index d2345144..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function3.kt +++ /dev/null @@ -1,43 +0,0 @@ -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): ((Array) -> 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 -> - it.invoke(x[0], x[1], x[2]) - } - } - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function4.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/Function4.kt deleted file mode 100644 index 122dbb69..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/Function4.kt +++ /dev/null @@ -1,30 +0,0 @@ -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): ((Array) -> 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 -> it.invoke(x[0], x[1], x[2], x[3]) } } - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/MemberFunctions.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/MemberFunctions.kt deleted file mode 100644 index e4dd44ee..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/MemberFunctions.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.openrndr.extra.expressions.typed - -fun String.memberFunctions(n: String): ((Array) -> 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 - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/Properties.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/Properties.kt deleted file mode 100644 index 4782094f..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/Properties.kt +++ /dev/null @@ -1,92 +0,0 @@ -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") - } -} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/kotlin/typed/TypedExpressions.kt b/orx-expression-evaluator/src/commonMain/kotlin/typed/TypedExpressions.kt deleted file mode 100644 index d9fb67c7..00000000 --- a/orx-expression-evaluator/src/commonMain/kotlin/typed/TypedExpressions.kt +++ /dev/null @@ -1,686 +0,0 @@ -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.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 = emptyMap(), - val functions1: Map = emptyMap(), - val functions2: Map = emptyMap(), - val functions3: Map = emptyMap(), - val functions4: Map = emptyMap(), - val functions5: Map = 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() - val functionStack = ArrayDeque<(Array) -> Any>() - val propertyStack = ArrayDeque() - - val idTypeStack = ArrayDeque() - var lastExpressionResult: Any? = null - - val exceptionStack = ArrayDeque() - - 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 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 = - when (name) { - "random" -> { _ -> Double.uniform(0.0, 1.0) } - else -> functions.functions0[name]?.let { { _: Array -> 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 = - dispatchFunction1(name, functions.functions1) - ?: errorValue( - "unresolved function: '${name}(x0)'" - ) { _ -> error("this is the error function") } - functionStack.push(function) - } - - IDType.FUNCTION2 -> { - val function: (Array) -> 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 = - dispatchFunction3(name, functions.functions3) - ?: errorValue( - "unresolved function: '${name}(x0)'" - ) { _ -> error("this is the error function") } - functionStack.push(function) - } - - IDType.FUNCTION4 -> { - val function: (Array) -> 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() -} -