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 f074a8f0..9950f61b 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/CompiledFunctions.kt @@ -11,9 +11,9 @@ fun compileFunction1( constants: (String) -> Any? = { null }, functions: TypedFunctionExtensions = TypedFunctionExtensions.EMPTY ): ((T0) -> R) { - require(constants(parameter0) == null) { - "${parameter0} is in constants with value '${constants(parameter0)}" - } +// require(constants(parameter0) == null) { +// "${parameter0} is in constants with value '${constants(parameter0)}" +// } val root = expressionRoot(expression) var varP0: T0? = null diff --git a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/MemberFunctions.kt b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/MemberFunctions.kt index e4dd44ee..a84c9eed 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/MemberFunctions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/MemberFunctions.kt @@ -1,6 +1,8 @@ package org.openrndr.extra.expressions.typed -fun String.memberFunctions(n: String): ((Array) -> Any)? { +import kotlin.math.roundToInt + +internal 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()) } @@ -8,4 +10,16 @@ fun String.memberFunctions(n: String): ((Array) -> Any)? { "dropLast" -> { n -> this.takeLast((n[0] as Number).toInt()) } else -> null } +} + +internal fun List<*>.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()) } + "map" -> { n -> val lambda = (n[0] as (Any)->Any); this.map { lambda(it!!) } } + "filter" -> { n -> val lambda = (n[0] as (Any)->Any); this.filter { (lambda(it!!) as Double).roundToInt() != 0 } } + else -> 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 2745a00e..0f992d9e 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt @@ -56,7 +56,8 @@ enum class IDType { FUNCTION2, FUNCTION3, FUNCTION4, - FUNCTION5 + FUNCTION5, + FUNCTION_ARGUMENT } abstract class TypedExpressionListenerBase( @@ -74,22 +75,71 @@ abstract class TypedExpressionListenerBase( var lastExpressionResult: Any? = null val exceptionStack = ArrayDeque() + + var inFunctionLiteral = 0 + + fun reset() { + valueStack.clear() + functionStack.clear() + propertyStack.clear() + idTypeStack.clear() + lastExpressionResult = null + exceptionStack.clear() + inFunctionLiteral = 0 + } } abstract val state: State + override fun enterLine(ctx: KeyLangParser.LineContext) { + val s = state + s.reset() + } + + override fun exitListLiteral(ctx: KeyLangParser.ListLiteralContext) { + val s = state + val list = (0 until ctx.getExpression().size).map { s.valueStack.pop() } + s.valueStack.push(list.reversed()) + } + + override fun enterFunctionLiteral(ctx: KeyLangParser.FunctionLiteralContext) { + val s = state + s.inFunctionLiteral++ + } + + override fun exitFunctionLiteral(ctx: KeyLangParser.FunctionLiteralContext) { + val s = state + s.inFunctionLiteral-- + val functionExpr = ctx.getExpression().text + + val ids = ctx.ID() + val f = when (ids.size) { + 0 -> compileFunction1(functionExpr, "it", constants, functions) + 1 -> compileFunction1(functionExpr, ids[0].text, constants, functions) + 2 -> compileFunction2(functionExpr, ids[0].text, ids[1].text, constants, functions) + else -> error("functions with ${ids.size} parameters are not supported") + + } + s.valueStack.push(f) + } override fun exitExpressionStatement(ctx: KeyLangParser.ExpressionStatementContext) { + val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { throw ExpressionException("error in evaluation of '${ctx.text}': ${it.message ?: ""}") } val result = state.valueStack.pop() - state.lastExpressionResult = result + s.lastExpressionResult = result } override fun exitMinusExpression(ctx: KeyLangParser.MinusExpressionContext) { val s = state - - + if (s.inFunctionLiteral > 0) { + return + } val op = s.valueStack.pop() s.valueStack.pushChecked( when (op) { @@ -105,6 +155,9 @@ abstract class TypedExpressionListenerBase( override fun exitBinaryOperation1(ctx: KeyLangParser.BinaryOperation1Context) { val s = state + if (s.inFunctionLiteral > 0) { + return + } ifError { pushError(it.message ?: "") @@ -128,6 +181,7 @@ abstract class TypedExpressionListenerBase( 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()) + left is List<*> && right is Double -> (0 until right.roundToInt()).flatMap { left } else -> error("unsupported operands for * operator left:${left::class} right:${right::class}") } @@ -159,6 +213,11 @@ abstract class TypedExpressionListenerBase( @Suppress("IMPLICIT_CAST_TO_ANY") override fun exitBinaryOperation2(ctx: KeyLangParser.BinaryOperation2Context) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + + ifError { pushError(it.message ?: "") return @@ -176,6 +235,7 @@ abstract class TypedExpressionListenerBase( left is Matrix44 && right is Matrix44 -> left + right left is ColorRGBa && right is ColorRGBa -> left + right left is String && right is String -> left + right + left is List<*> && right is List<*> -> left + right else -> error("unsupported operands for + operator left:${left::class} right:${right::class}") } @@ -196,6 +256,10 @@ abstract class TypedExpressionListenerBase( override fun exitJoinOperation(ctx: KeyLangParser.JoinOperationContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + val right = (s.valueStack.pop() as Double).roundToInt() val left = (s.valueStack.pop() as Double).roundToInt() @@ -209,6 +273,9 @@ abstract class TypedExpressionListenerBase( override fun exitComparisonOperation(ctx: KeyLangParser.ComparisonOperationContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } val right = s.valueStack.pop() val left = s.valueStack.pop() @@ -251,12 +318,20 @@ abstract class TypedExpressionListenerBase( override fun exitNegateExpression(ctx: KeyLangParser.NegateExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + 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 s = state + if (s.inFunctionLiteral > 0) { + return + } + val right = s.valueStack.pop() val left = s.valueStack.pop() val comp = s.valueStack.pop() @@ -270,16 +345,28 @@ abstract class TypedExpressionListenerBase( override fun enterValueReference(ctx: KeyLangParser.ValueReferenceContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.VARIABLE) } override fun enterMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.MEMBER_FUNCTION1) } override fun exitMemberFunctionCall0Expression(ctx: KeyLangParser.MemberFunctionCall0ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -289,11 +376,19 @@ abstract class TypedExpressionListenerBase( override fun enterMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.MEMBER_FUNCTION1) } override fun exitMemberFunctionCall1Expression(ctx: KeyLangParser.MemberFunctionCall1ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -303,11 +398,19 @@ abstract class TypedExpressionListenerBase( override fun enterMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.MEMBER_FUNCTION2) } override fun exitMemberFunctionCall2Expression(ctx: KeyLangParser.MemberFunctionCall2ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -320,11 +423,19 @@ abstract class TypedExpressionListenerBase( override fun enterMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.MEMBER_FUNCTION3) } override fun exitMemberFunctionCall3Expression(ctx: KeyLangParser.MemberFunctionCall3ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -336,14 +447,44 @@ abstract class TypedExpressionListenerBase( s.valueStack.pushChecked(s.functionStack.pop().invoke(arrayOf(argument0, argument1, argument2))) } + override fun enterMemberFunctionCall0LambdaExpression(ctx: KeyLangParser.MemberFunctionCall0LambdaExpressionContext) { + val s = state + if (s.inFunctionLiteral > 0) { + return + } + + s.idTypeStack.push(IDType.MEMBER_FUNCTION1) + } + + override fun exitMemberFunctionCall0LambdaExpression(ctx: KeyLangParser.MemberFunctionCall0LambdaExpressionContext) { + val s = state + if (s.inFunctionLiteral > 0) { + return + } + + ifError { + pushError(it.message ?: "") + return + } + s.valueStack.pushChecked(s.functionStack.pop().invoke(arrayOf(s.valueStack.pop()))) + } + override fun enterFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION0) } override fun exitFunctionCall0Expression(ctx: KeyLangParser.FunctionCall0ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -356,11 +497,19 @@ abstract class TypedExpressionListenerBase( override fun enterFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION1) } override fun exitFunctionCall1Expression(ctx: KeyLangParser.FunctionCall1ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -375,11 +524,19 @@ abstract class TypedExpressionListenerBase( override fun enterFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION2) } override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -395,11 +552,19 @@ abstract class TypedExpressionListenerBase( override fun enterFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION3) } override fun exitFunctionCall3Expression(ctx: KeyLangParser.FunctionCall3ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -416,11 +581,19 @@ abstract class TypedExpressionListenerBase( override fun enterFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION4) } override fun exitFunctionCall4Expression(ctx: KeyLangParser.FunctionCall4ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -439,11 +612,19 @@ abstract class TypedExpressionListenerBase( override fun enterFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.FUNCTION5) } override fun exitFunctionCall5Expression(ctx: KeyLangParser.FunctionCall5ExpressionContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + ifError { pushError(it.message ?: "") return @@ -467,6 +648,7 @@ abstract class TypedExpressionListenerBase( private fun pushError(message: String) { val s = state + s.exceptionStack.push(ExpressionException(message)) } @@ -480,11 +662,19 @@ abstract class TypedExpressionListenerBase( override fun enterPropReference(ctx: KeyLangParser.PropReferenceContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + s.idTypeStack.push(IDType.PROPERTY) } override fun exitPropReference(ctx: KeyLangParser.PropReferenceContext) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + val root = s.valueStack.pop() var current = root val property = s.propertyStack.pop() @@ -507,6 +697,10 @@ abstract class TypedExpressionListenerBase( override fun visitTerminal(node: TerminalNode) { val s = state + if (s.inFunctionLiteral > 0) { + return + } + val type = node.symbol.type if (type == KeyLangParser.Tokens.INTLIT) { s.valueStack.pushChecked(node.text.toDouble()) @@ -552,6 +746,13 @@ abstract class TypedExpressionListenerBase( ) } + is List<*> -> { + s.functionStack.push( + receiver.memberFunctions(name) + ?: error("no member function '$receiver.$name()'") + ) + } + is ColorRGBa -> { when (idType) { IDType.MEMBER_FUNCTION1 -> { @@ -598,6 +799,7 @@ abstract class TypedExpressionListenerBase( } } + else -> error("receiver '${receiver}' not supported") } } @@ -639,6 +841,10 @@ abstract class TypedExpressionListenerBase( s.functionStack.push(function) } + IDType.FUNCTION_ARGUMENT -> { + + } + else -> error("unsupported id-type $idType") } } diff --git a/orx-expression-evaluator-typed/src/jsMain/kotlin/typed/TypedExpressions.js.kt b/orx-expression-evaluator-typed/src/jsMain/kotlin/typed/TypedExpressions.js.kt new file mode 100644 index 00000000..8c0ccefe --- /dev/null +++ b/orx-expression-evaluator-typed/src/jsMain/kotlin/typed/TypedExpressions.js.kt @@ -0,0 +1,8 @@ +package org.openrndr.extra.expressions.typed + +actual class TypedExpressionListener actual constructor( + functions: TypedFunctionExtensions, + constants: (String) -> Any? +) : TypedExpressionListenerBase(functions, constants) { + actual override val state: State = State() +} \ No newline at end of file diff --git a/orx-expression-evaluator-typed/src/jvmMain/kotlin/typed/TypedExpressions.jvm.kt b/orx-expression-evaluator-typed/src/jvmMain/kotlin/typed/TypedExpressions.jvm.kt new file mode 100644 index 00000000..8516183f --- /dev/null +++ b/orx-expression-evaluator-typed/src/jvmMain/kotlin/typed/TypedExpressions.jvm.kt @@ -0,0 +1,15 @@ +package org.openrndr.extra.expressions.typed + +import kotlin.concurrent.getOrSet + +/* +Thread safe TypeExpressionListener + */ +actual class TypedExpressionListener actual constructor( + functions: TypedFunctionExtensions, + constants: (String) -> Any? +) : TypedExpressionListenerBase(functions, constants) { + private val threadLocalState = ThreadLocal() + actual override val state: State + get() = threadLocalState.getOrSet { State() } +} \ No newline at end of file diff --git a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedExpression.kt b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedExpression.kt index 608744ed..8592791f 100644 --- a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedExpression.kt +++ b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestTypedExpression.kt @@ -7,6 +7,53 @@ import kotlin.test.Test class TestTypedExpression { + @Test + fun funTestFunction() { + run { + val r = evaluateTypedExpression("{ x -> 2.0 + x }") + val f = r as (Double) -> Double + println(f(3.0)) + } + run { + val r = evaluateTypedExpression("{ { 2.0 + it } }") + val f0 = r as (Any) -> ((Any) -> Any) + val f1 = f0(0.0) + println(f1(3.0)) + } + } + + @Test + fun funTestLambdaArg() { + run { + val r = evaluateTypedExpression("[0.0, 1.0].map { x -> 2.0 + x }") + assertEquals(listOf(2.0, 3.0), r) + } + + run { + val r = evaluateTypedExpression("[0.0, 1.0].map { x -> vec2(2.0 + x, 2.0 + x) }") + assertEquals(listOf(Vector2(2.0, 2.0), Vector2(3.0, 3.0)), r) + } + + run { + val r = evaluateTypedExpression("[0.0, 1.0, 2.0].filter { x -> x >= 1.0 }") + assertEquals(listOf(1.0, 2.0), r) + } + } + + + + @Test + fun testList() { + println("result is: ${evaluateTypedExpression("[]")}") + println("result is: ${evaluateTypedExpression("[1.0, 2.0]")}") + println("result is: ${evaluateTypedExpression("[1.0, 2.0].take(1)")}") + println("result is: ${evaluateTypedExpression("[1.0 + 2.0, 2.0 * 3.0].take(1 + 1)")}") + + println("result is: ${evaluateTypedExpression("[] + []")}") + println("result is: ${evaluateTypedExpression("([1] * 2 + [2] * 1)*5")}" ) + } + + @Test fun testTernary() { println("result is: ${evaluateTypedExpression("2.0 > 0.5 ? 1.3 : 0.7")}") diff --git a/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 b/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 index 4a8283a6..dace190f 100644 --- a/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 +++ b/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 @@ -25,18 +25,24 @@ DIVISION : '/' ; ASSIGN : '=' ; LPAREN : '(' ; RPAREN : ')' ; +LBRACKET : '[' ; +RBRACKET : ']' ; +LCURLY : '{' ; +RCURLY : '}' ; QUESTION_MARK : '?' ; COLON : ':' ; +ARROW : '->' ; + COMMA : ',' ; DOT : '.' ; EQ : '==' ; LT : '<' ; LTEQ : '<=' ; -GT : '>=' ; -GTEQ : '>' ; +GT : '>' ; +GTEQ : '>=' ; AND : '&&' ; OR : '||' ; diff --git a/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 b/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 index 491544ae..421744f4 100644 --- a/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 +++ b/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 @@ -10,8 +10,13 @@ line : statement (NEWLINE | EOF) ; statement : expression # expressionStatement ; +lambda: LCURLY (ID ( COMMA ID )* ARROW )? expression RCURLY # functionLiteral; + expression : INTLIT # intLiteral | DECLIT # decimalLiteral + | LBRACKET (expression ( COMMA expression )*)? RBRACKET # listLiteral + | expression DOT ID lambda # memberFunctionCall0LambdaExpression + | lambda # lambdaExpression | expression DOT ID LPAREN RPAREN # memberFunctionCall0Expression | expression DOT ID LPAREN expression RPAREN # memberFunctionCall1Expression | expression DOT ID LPAREN expression COMMA expression RPAREN # memberFunctionCall2Expression