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 7ac7e383..a66de688 100644 --- a/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt +++ b/orx-expression-evaluator-typed/src/commonMain/kotlin/typed/TypedExpressions.kt @@ -7,6 +7,10 @@ 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.KeyLangLexer.Tokens.RANGE_DOWNTO +import org.openrndr.extra.expressions.parser.KeyLangLexer.Tokens.RANGE_EXCLUSIVE +import org.openrndr.extra.expressions.parser.KeyLangLexer.Tokens.RANGE_EXCLUSIVE_UNTIL +import org.openrndr.extra.expressions.parser.KeyLangLexer.Tokens.RANGE_INCLUSIVE import org.openrndr.extra.expressions.parser.KeyLangParser import org.openrndr.extra.expressions.parser.KeyLangParserBaseListener import org.openrndr.extra.noise.uniform @@ -94,12 +98,53 @@ abstract class TypedExpressionListenerBase( s.reset() } + override fun exitRangeExpression(ctx: KeyLangParser.RangeExpressionContext) { + val s = state + if (s.inFunctionLiteral > 0) { + return + } + + val step: Any? + if (ctx.step != null) { + step = s.valueStack.pop() + } else { + step = null + } + + val right = s.valueStack.pop() + val left = s.valueStack.pop() + + + val lower = (left as Double).toInt() + val upper = (right as Double).toInt() + val list = if (step == null) { + when (ctx.operator?.type) { + RANGE_INCLUSIVE -> (lower..upper).toList().map { it.toDouble() } + RANGE_EXCLUSIVE -> (lower.. (lower until upper).toList().map { it.toDouble() } + RANGE_DOWNTO -> (lower downTo upper).toList().map { it.toDouble() } + else -> error("unsupported operator: '${ctx.operator?.text}'") + } + } else { + val stepSize = (step as Double).toInt() + when (ctx.operator?.type) { + RANGE_INCLUSIVE -> (lower..upper step stepSize).toList().map { it.toDouble() } + RANGE_EXCLUSIVE -> (lower.. (lower until upper step stepSize).toList().map { it.toDouble() } + RANGE_DOWNTO -> (lower downTo upper step stepSize).toList().map { it.toDouble() } + else -> error("unsupported operator: '${ctx.operator?.text}'") + } + } + s.valueStack.push(list) + + } + override fun exitListLiteral(ctx: KeyLangParser.ListLiteralContext) { val s = state if (s.inFunctionLiteral > 0) { return } - val list = (0 until ctx.expression().size).map { s.valueStack.pop() } + val list = (0 until ctx.expressionRoot().size).map { s.valueStack.pop() } s.valueStack.push(list.reversed()) } @@ -113,7 +158,7 @@ abstract class TypedExpressionListenerBase( val value = when (listValue) { is List<*> -> listValue[index] ?: error("got null") - is Function<*> -> (listValue as (Int)->Any)(index) + is Function<*> -> (listValue as (Int) -> Any)(index) else -> error("can't index on '$listValue'") } s.valueStack.push(value) @@ -545,10 +590,10 @@ abstract class TypedExpressionListenerBase( if (s.inFunctionLiteral > 0) { return } - s.idTypeStack.push(IDType.FUNCTION2) } + override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { val s = state if (s.inFunctionLiteral > 0) { @@ -819,7 +864,11 @@ abstract class TypedExpressionListenerBase( } - else -> error("receiver for '$name' '${receiver.toString().take(30)}' ${receiver::class} not supported") + else -> error( + "receiver for '$name' '${ + receiver.toString().take(30) + }' ${receiver::class} not supported" + ) } } diff --git a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestListLiteralExpression.kt b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestListLiteralExpression.kt new file mode 100644 index 00000000..fb4a50f8 --- /dev/null +++ b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestListLiteralExpression.kt @@ -0,0 +1,25 @@ +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 +import kotlin.test.assertTrue + +class TestListLiteralExpression { + + @Test + fun testSimpleList() { + val r = evaluateTypedExpression("[0, 1, 2]") + assertTrue(r is List<*>) + assertEquals(3, r.size ) + } + + @Test + fun testRangesList() { + val r = evaluateTypedExpression("[0..1, 1..2, 2..3]") + assertTrue(r is List<*>) + assertEquals(3, r.size ) + } + +} \ No newline at end of file diff --git a/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestRangeExpression.kt b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestRangeExpression.kt new file mode 100644 index 00000000..6bf7669c --- /dev/null +++ b/orx-expression-evaluator-typed/src/jvmTest/kotlin/typed/TestRangeExpression.kt @@ -0,0 +1,72 @@ +package typed + +import org.junit.jupiter.api.Assertions.assertEquals +import org.openrndr.extra.expressions.typed.evaluateTypedExpression +import kotlin.test.Test +import kotlin.test.assertTrue + +class TestRangeExpression { + @Test + fun testRangeInclusive() { + val r = evaluateTypedExpression("(0..10)") + assertTrue(r is List<*>) + assertEquals(11, r.size) + } + + @Test + fun testRangeDownTo() { + val r = evaluateTypedExpression("(10 downTo 0)") + assertTrue(r is List<*>) + assertEquals(11, r.size) + assertEquals(0.0, r.last()) + } + + @Test + fun testRangeExclusive() { + val r = evaluateTypedExpression("(0..<10)") + assertTrue(r is List<*>) + assertEquals(10, r.size) + } + + @Test + fun testRangeInclusivePrecedenceRight() { + val r = evaluateTypedExpression("(0..10+2)") + assertTrue(r is List<*>) + assertEquals(13, r.size) + } + + @Test + fun testRangeInclusivePrecedenceLeft() { + val r = evaluateTypedExpression("1+2..10") + assertTrue(r is List<*>) + assertEquals(8, r.size) + } + + @Test + fun testRangeInclusivePrecedenceLeftRight() { + val r = evaluateTypedExpression("1+2..10+2") + assertTrue(r is List<*>) + assertEquals(10, r.size) + } + + @Test + fun testRangeUntilPrecedenceLeftRight() { + val r = evaluateTypedExpression("1+2 until 10+2") + assertTrue(r is List<*>) + assertEquals(9, r.size) + } + + @Test + fun testRangeUntilPrecedenceLeftRightMap() { + val r = evaluateTypedExpression("(1+2 until 10+2).map { x -> x * 2 }") + assertTrue(r is List<*>) + assertEquals(9, r.size) + } + + @Test + fun testRangeExclusiveStep() { + val r = evaluateTypedExpression("(0..10 step 2)") + assertTrue(r is List<*>) + assertEquals(6, r.size) + } +} \ No newline at end of file diff --git a/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 b/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 index dace190f..563e2c08 100644 --- a/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 +++ b/orx-expression-evaluator/src/commonMain/antlr/KeyLangLexer.g4 @@ -7,6 +7,14 @@ NEWLINE : '\r\n' | '\r' | '\n' ; WS : [\t ]+ -> channel(WHITESPACE) ; +RANGE_INCLUSIVE : '..' ; +RANGE_EXCLUSIVE_UNTIL : 'until' ; +RANGE_EXCLUSIVE : '..<' ; +RANGE_DOWNTO : 'downTo' ; + +STEP : 'step' ; + + // Identifiers ID : [$_]*[a-zA-Z][A-Za-z0-9_]* | '`'[$_]*[A-Za-z0-9_-]*'`'; FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ; diff --git a/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 b/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 index 747713be..18e370c1 100644 --- a/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 +++ b/orx-expression-evaluator/src/commonMain/antlr/KeyLangParser.g4 @@ -8,13 +8,19 @@ keyLangFile : lines=line+ ; line : statement (NEWLINE | EOF) ; statement : - expression # expressionStatement ; + expressionRoot # expressionStatement ; + +rangeExpression: expression operator=(RANGE_INCLUSIVE|RANGE_EXCLUSIVE|RANGE_EXCLUSIVE_UNTIL|RANGE_DOWNTO) expression (step=STEP expression)?; + +expressionRoot: rangeExpression + | expression + ; lambda: LCURLY (ID ( COMMA ID )* ARROW )? expression RCURLY # functionLiteral; expression : INTLIT # intLiteral | DECLIT # decimalLiteral - | LBRACKET (expression ( COMMA expression )*)? RBRACKET # listLiteral + | LBRACKET (expressionRoot ( COMMA expressionRoot )*)? RBRACKET # listLiteral | expression DOT ID lambda # memberFunctionCall0LambdaExpression | lambda # lambdaExpression | expression DOT ID LPAREN RPAREN # memberFunctionCall0Expression @@ -32,7 +38,7 @@ expression : INTLIT # int | ID # valueReference | STRING_OPEN (parts+=stringLiteralContent)* STRING_CLOSE # stringLiteral | expression DOT ID # propReference - | LPAREN expression RPAREN # parenExpression + | LPAREN expressionRoot RPAREN # parenExpression | MINUS expression # minusExpression | NOT expression # negateExpression | expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1