[orx-expression-evaluator-typed] Add range operators

This commit is contained in:
Edwin Jakobs
2024-08-08 10:01:07 +02:00
parent eaa71a61bd
commit 8529825d34
5 changed files with 167 additions and 7 deletions

View File

@@ -7,6 +7,10 @@ import org.openrndr.collections.pop
import org.openrndr.collections.push import org.openrndr.collections.push
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.extra.expressions.parser.KeyLangLexer 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.KeyLangParser
import org.openrndr.extra.expressions.parser.KeyLangParserBaseListener import org.openrndr.extra.expressions.parser.KeyLangParserBaseListener
import org.openrndr.extra.noise.uniform import org.openrndr.extra.noise.uniform
@@ -94,12 +98,53 @@ abstract class TypedExpressionListenerBase(
s.reset() 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..<upper).toList().map { it.toDouble() }
RANGE_EXCLUSIVE_UNTIL -> (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..<upper step stepSize).toList().map { it.toDouble() }
RANGE_EXCLUSIVE_UNTIL -> (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) { override fun exitListLiteral(ctx: KeyLangParser.ListLiteralContext) {
val s = state val s = state
if (s.inFunctionLiteral > 0) { if (s.inFunctionLiteral > 0) {
return 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()) s.valueStack.push(list.reversed())
} }
@@ -113,7 +158,7 @@ abstract class TypedExpressionListenerBase(
val value = when (listValue) { val value = when (listValue) {
is List<*> -> listValue[index] ?: error("got null") 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'") else -> error("can't index on '$listValue'")
} }
s.valueStack.push(value) s.valueStack.push(value)
@@ -545,10 +590,10 @@ abstract class TypedExpressionListenerBase(
if (s.inFunctionLiteral > 0) { if (s.inFunctionLiteral > 0) {
return return
} }
s.idTypeStack.push(IDType.FUNCTION2) s.idTypeStack.push(IDType.FUNCTION2)
} }
override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) { override fun exitFunctionCall2Expression(ctx: KeyLangParser.FunctionCall2ExpressionContext) {
val s = state val s = state
if (s.inFunctionLiteral > 0) { 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"
)
} }
} }

View File

@@ -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 )
}
}

View File

@@ -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)
}
}

View File

@@ -7,6 +7,14 @@ NEWLINE : '\r\n' | '\r' | '\n' ;
WS : [\t ]+ -> channel(WHITESPACE) ; WS : [\t ]+ -> channel(WHITESPACE) ;
RANGE_INCLUSIVE : '..' ;
RANGE_EXCLUSIVE_UNTIL : 'until' ;
RANGE_EXCLUSIVE : '..<' ;
RANGE_DOWNTO : 'downTo' ;
STEP : 'step' ;
// 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_-]*'`';
FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ; FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ;

View File

@@ -8,13 +8,19 @@ keyLangFile : lines=line+ ;
line : statement (NEWLINE | EOF) ; line : statement (NEWLINE | EOF) ;
statement : 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; lambda: LCURLY (ID ( COMMA ID )* ARROW )? expression RCURLY # functionLiteral;
expression : INTLIT # intLiteral expression : INTLIT # intLiteral
| DECLIT # decimalLiteral | DECLIT # decimalLiteral
| LBRACKET (expression ( COMMA expression )*)? RBRACKET # listLiteral | LBRACKET (expressionRoot ( COMMA expressionRoot )*)? RBRACKET # listLiteral
| expression DOT ID lambda # memberFunctionCall0LambdaExpression | expression DOT ID lambda # memberFunctionCall0LambdaExpression
| lambda # lambdaExpression | lambda # lambdaExpression
| expression DOT ID LPAREN RPAREN # memberFunctionCall0Expression | expression DOT ID LPAREN RPAREN # memberFunctionCall0Expression
@@ -32,7 +38,7 @@ expression : INTLIT # int
| ID # valueReference | ID # valueReference
| STRING_OPEN (parts+=stringLiteralContent)* STRING_CLOSE # stringLiteral | STRING_OPEN (parts+=stringLiteralContent)* STRING_CLOSE # stringLiteral
| expression DOT ID # propReference | expression DOT ID # propReference
| LPAREN expression RPAREN # parenExpression | LPAREN expressionRoot RPAREN # parenExpression
| MINUS expression # minusExpression | MINUS expression # minusExpression
| NOT expression # negateExpression | NOT expression # negateExpression
| expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1 | expression operator=(DIVISION|ASTERISK|PERCENTAGE) expression # binaryOperation1