Skip to content

Commit 5fa4e72

Browse files
committed
[orx-keyframer] Add support for backticked identifier names, replace spek tests
1 parent 225c285 commit 5fa4e72

File tree

9 files changed

+273
-254
lines changed

9 files changed

+273
-254
lines changed

orx-jvm/orx-keyframer/build.gradle

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ dependencies {
3838
implementation libs.gson
3939
implementation(libs.kotlin.reflect)
4040

41+
testImplementation(libs.kotlin.test)
42+
4143
demoImplementation(project(":orx-camera"))
4244
demoImplementation(project(":orx-jvm:orx-panel"))
4345

@@ -50,4 +52,8 @@ dependencies {
5052
}
5153

5254
tasks.getByName("compileKotlin").dependsOn("generateGrammarSource")
53-
tasks.getByName("compileTestKotlin").dependsOn("generateTestGrammarSource")
55+
tasks.getByName("compileTestKotlin").dependsOn("generateTestGrammarSource")
56+
57+
test {
58+
useJUnitPlatform()
59+
}

orx-jvm/orx-keyframer/src/main/antlr/KeyLangLexer.g4

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ DECIMAL : 'Decimal';
2020
STRING : 'String';
2121

2222
// Identifiers
23-
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* ;
23+
ID : [$_]*[a-zA-Z][A-Za-z0-9_]* | '`'[$_]*[A-Za-z0-9_-]*'`';
2424
FUNCTION_ID : [$_]*[a-z][A-Za-z0-9_]* ;
2525

2626
// Literals
@@ -38,6 +38,7 @@ ASSIGN : '=' ;
3838
LPAREN : '(' ;
3939
RPAREN : ')' ;
4040

41+
4142
COMMA : ',' ;
4243

4344
STRING_OPEN : '"' -> pushMode(MODE_IN_STRING);
@@ -51,40 +52,5 @@ ESCAPE_SLASH : '\\\\' ;
5152
ESCAPE_NEWLINE : '\\n' ;
5253
ESCAPE_SHARP : '\\#' ;
5354
STRING_CLOSE : '"' -> popMode ;
54-
INTERPOLATION_OPEN : '#{' -> pushMode(MODE_IN_INTERPOLATION) ;
5555
STRING_CONTENT : ~["\n\r\t\\#]+ ;
5656
57-
STR_UNMATCHED : . -> type(UNMATCHED) ;
58-
59-
mode MODE_IN_INTERPOLATION;
60-
61-
INTERPOLATION_CLOSE : '}' -> popMode ;
62-
63-
INTERP_WS : [\t ]+ -> channel(WHITESPACE), type(WS) ;
64-
65-
// Keywords
66-
INTERP_AS : 'as'-> type(AS) ;
67-
INTERP_INT : 'Int'-> type(INT) ;
68-
INTERP_DECIMAL : 'Decimal'-> type(DECIMAL) ;
69-
INTERP_STRING : 'String'-> type(STRING) ;
70-
71-
// Literals
72-
INTERP_INTLIT : ('0'|[1-9][0-9]*) -> type(INTLIT) ;
73-
INTERP_DECLIT : ('0'|[1-9][0-9]*) '.' [0-9]+ -> type(DECLIT) ;
74-
75-
// Operators
76-
INTERP_PLUS : '+' -> type(PLUS) ;
77-
INTERP_MINUS : '-' -> type(MINUS) ;
78-
INTERP_ASTERISK : '*' -> type(ASTERISK) ;
79-
INTERP_DIVISION : '/' -> type(DIVISION) ;
80-
INTERP_PERCENTAGE : '%' -> type(PERCENTAGE) ;
81-
INTERP_ASSIGN : '=' -> type(ASSIGN) ;
82-
INTERP_LPAREN : '(' -> type(LPAREN) ;
83-
INTERP_RPAREN : ')' -> type(RPAREN) ;
84-
85-
// Identifiers
86-
INTERP_ID : [_]*[a-z][A-Za-z0-9_]* -> type(ID);
87-
88-
INTERP_STRING_OPEN : '"' -> type(STRING_OPEN), pushMode(MODE_IN_STRING);
89-
90-
INTERP_UNMATCHED : . -> type(UNMATCHED) ;

orx-jvm/orx-keyframer/src/main/antlr/KeyLangParser.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package org.openrndr.extra.keyframer.antlr;
77

88
options { tokenVocab=KeyLangLexer; }
99

10-
miniCalcFile : lines=line+ ;
10+
keyLangFile : lines=line+ ;
1111

1212
line : statement (NEWLINE | EOF) ;
1313

orx-jvm/orx-keyframer/src/main/kotlin/Expressions.kt

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -248,31 +248,31 @@ internal class ExpressionListener(val functions: FunctionExtensions = FunctionEx
248248
doubleStack.push(node.text.toDouble())
249249
}
250250
if (type == KeyLangParser.ID) {
251-
251+
val name = node.text.replace("`","")
252252
@Suppress("DIVISION_BY_ZERO")
253253
when (val idType = idTypeStack.pop()) {
254254
IDType.VARIABLE -> doubleStack.push(
255-
when (val name = node.text) {
255+
when (name) {
256256
"PI" -> PI
257257
else -> variables[name] ?: errorValue("unresolved variable: '${name}'", 0.0 / 0.0)
258258
}
259259
)
260260

261261
IDType.FUNCTION0 -> {
262262
val function: (DoubleArray) -> Double =
263-
when (val candidate = node.text) {
263+
when (name) {
264264
"random" -> { _ -> Double.uniform(0.0, 1.0) }
265-
else -> functions.functions0[candidate]?.let { { _: DoubleArray -> it.invoke() } }
265+
else -> functions.functions0[name]?.let { { _: DoubleArray -> it.invoke() } }
266266
?: errorValue(
267-
"unresolved function: '${candidate}()'"
267+
"unresolved function: '${name}()'"
268268
) { _ -> error("this is the error function") }
269269
}
270270
functionStack.push(function)
271271
}
272272

273273
IDType.FUNCTION1 -> {
274274
val function: (DoubleArray) -> Double =
275-
when (val candidate = node.text) {
275+
when (name) {
276276
"sqrt" -> { x -> sqrt(x[0]) }
277277
"radians" -> { x -> Math.toRadians(x[0]) }
278278
"degrees" -> { x -> Math.toDegrees(x[0]) }
@@ -287,60 +287,69 @@ internal class ExpressionListener(val functions: FunctionExtensions = FunctionEx
287287
"floor" -> { x -> floor(x[0]) }
288288
"ceil" -> { x -> ceil(x[0]) }
289289
"saturate" -> { x -> x[0].coerceIn(0.0, 1.0) }
290-
else -> functions.functions1[candidate]?.let { { x: DoubleArray -> it.invoke(x[0]) } }
290+
else -> functions.functions1[name]?.let { { x: DoubleArray -> it.invoke(x[0]) } }
291291
?: errorValue(
292-
"unresolved function: '${candidate}(x0)'"
292+
"unresolved function: '${name}(x0)'"
293293
) { _ -> error("this is the error function") }
294294
}
295295
functionStack.push(function)
296296
}
297297
IDType.FUNCTION2 -> {
298298
val function: (DoubleArray) -> Double =
299-
when (val candidate = node.text) {
299+
when (name) {
300300
"max" -> { x -> max(x[0], x[1]) }
301301
"min" -> { x -> min(x[0], x[1]) }
302302
"pow" -> { x -> x[0].pow(x[1]) }
303303
"atan2" -> { x -> atan2(x[0], x[1]) }
304304
"random" -> { x -> Double.uniform(x[0], x[1]) }
305305
"length" -> { x -> Vector2(x[0], x[1]).length }
306-
else -> functions.functions2[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1]) } }
306+
else -> functions.functions2[name]?.let { { x: DoubleArray -> it.invoke(x[0], x[1]) } }
307307
?: errorValue(
308-
"unresolved function: '${candidate}(x0, x1)'"
308+
"unresolved function: '${name}(x0, x1)'"
309309
) { _ -> error("this is the error function") }
310310
}
311311
functionStack.push(function)
312312
}
313313
IDType.FUNCTION3 -> {
314314
val function: (DoubleArray) -> Double =
315-
when (val candidate = node.text) {
315+
when (name) {
316316
"mix" -> { x -> mix(x[0], x[1], x[2]) }
317+
"min" -> { x -> x.minOrNull()!! }
318+
"max" -> { x -> x.maxOrNull()!! }
319+
"sum" -> { x -> x.sum() }
317320
"smoothstep" -> { x -> smoothstep(x[0], x[1], x[2]) }
318321
"length" -> { x -> Vector3(x[0], x[1], x[2]).length }
319-
else -> functions.functions3[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2]) } }
322+
else -> functions.functions3[name]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2]) } }
320323
?: errorValue(
321-
"unresolved function: '${candidate}(x0, x1, x2)'"
324+
"unresolved function: '${name}(x0, x1, x2)'"
322325
) { _ -> error("this is the error function") }
323326
}
324327
functionStack.push(function)
325328
}
326329
IDType.FUNCTION4 -> {
327330
val function: (DoubleArray) -> Double =
328-
when (val candidate = node.text) {
329-
else -> functions.functions4[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3]) } }
331+
when (name) {
332+
"min" -> { x -> x.minOrNull()!! }
333+
"max" -> { x -> x.maxOrNull()!! }
334+
"sum" -> { x -> x.sum() }
335+
else -> functions.functions4[name]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3]) } }
330336
?: errorValue(
331-
"unresolved function: '${candidate}(x0, x1, x2, x3)'"
337+
"unresolved function: '${name}(x0, x1, x2, x3)'"
332338
) { _ -> error("this is the error function") }
333339
}
334340
functionStack.push(function)
335341
}
336342

337343
IDType.FUNCTION5 -> {
338344
val function: (DoubleArray) -> Double =
339-
when (val candidate = node.text) {
345+
when (name) {
346+
"min" -> { x -> x.minOrNull()!! }
347+
"max" -> { x -> x.maxOrNull()!! }
348+
"sum" -> { x -> x.sum() }
340349
"map" -> { x -> map(x[0], x[1], x[2], x[3], x[4]) }
341-
else -> functions.functions5[candidate]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3], x[4]) } }
350+
else -> functions.functions5[name]?.let { { x: DoubleArray -> it.invoke(x[0], x[1], x[2], x[3], x[4]) } }
342351
?: errorValue(
343-
"unresolved function: '${candidate}(x0, x1, x2, x3, x4)'"
352+
"unresolved function: '${name}(x0, x1, x2, x3, x4)'"
344353
) { _ -> error("this is the error function") }
345354
}
346355
functionStack.push(function)
@@ -374,7 +383,7 @@ fun evaluateExpression(
374383
}
375384
})
376385

377-
val root = parser.miniCalcFile()
386+
val root = parser.keyLangFile()
378387
val listener = ExpressionListener(functions)
379388
listener.variables.putAll(variables)
380389
try {

orx-jvm/orx-keyframer/src/test/kotlin/TestExpressionErrors.kt

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,50 @@ import org.amshove.kluent.`with message`
33
import org.amshove.kluent.invoking
44
import org.openrndr.extra.keyframer.ExpressionException
55
import org.openrndr.extra.keyframer.evaluateExpression
6-
import org.spekframework.spek2.Spek
7-
import org.spekframework.spek2.style.specification.describe
8-
import java.lang.IllegalStateException
6+
import kotlin.test.Test
97

10-
object TestExpressionErrors : Spek({
8+
class TestExpressionErrors {
119

12-
describe("an expression with non-sensible writing") {
10+
@Test
11+
fun `an expression with non-sensible writing`() {
1312
val expression = ")("
14-
it("should cause an exception to be thrown when evaluated") {
15-
invoking {
16-
evaluateExpression(expression)
17-
} `should throw` ExpressionException::class `with message` "parser error in expression: ')('; [line: 1, character: 0 , near: [@0,0:0=')',<21>,1:0] ]"
18-
}
13+
invoking {
14+
evaluateExpression(expression)
15+
} `should throw` ExpressionException::class `with message` "parser error in expression: ')('; [line: 1, character: 0 , near: [@0,0:0=')',<21>,1:0] ]"
16+
1917
}
2018

21-
describe("an expression with equality instead of assign") {
19+
@Test
20+
fun `an expression with equality instead of assign`() {
2221
val expression = "a == 5"
23-
it("should cause an exception to be thrown when evaluated") {
24-
invoking {
25-
evaluateExpression(expression)
26-
} `should throw` ExpressionException::class `with message` "parser error in expression: 'a == 5'; [line: 1, character: 3 , near: [@3,3:3='=',<19>,1:3] ]"
27-
}
22+
invoking {
23+
evaluateExpression(expression)
24+
} `should throw` ExpressionException::class `with message` "parser error in expression: 'a == 5'; [line: 1, character: 3 , near: [@3,3:3='=',<19>,1:3] ]"
25+
2826
}
2927

30-
describe("an expression trying to reassign a number") {
28+
@Test
29+
fun `an expression trying to reassign a number`() {
3130
val expression = "3 = 5"
32-
it("should cause an exception to be thrown when evaluated") {
33-
invoking {
34-
evaluateExpression(expression)
35-
} `should throw` ExpressionException::class `with message` "parser error in expression: '3 = 5'; [line: 1, character: 2 , near: [@2,2:2='=',<19>,1:2] ]"
36-
}
31+
invoking {
32+
evaluateExpression(expression)
33+
} `should throw` ExpressionException::class `with message` "parser error in expression: '3 = 5'; [line: 1, character: 2 , near: [@2,2:2='=',<19>,1:2] ]"
3734
}
3835

39-
describe("an expression that uses non-existing functions") {
36+
@Test
37+
fun `an expression that uses non-existing functions`() {
4038
val expression = "notExisting(5)"
41-
it("should cause an exception to be thrown when evaluated") {
42-
invoking {
43-
evaluateExpression(expression)
44-
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting(5)': unresolved function: 'notExisting(x0)'"
45-
}
39+
invoking {
40+
evaluateExpression(expression)
41+
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting(5)': unresolved function: 'notExisting(x0)'"
42+
4643
}
4744

48-
describe("an expression that uses non-existing variables") {
45+
@Test
46+
fun `an expression that uses non-existing variables`() {
4947
val expression = "notExisting + 4"
50-
it("should cause an exception to be thrown when evaluated") {
51-
invoking {
52-
evaluateExpression(expression)
53-
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting+4': unresolved variable: 'notExisting'"
54-
}
48+
invoking {
49+
evaluateExpression(expression)
50+
} `should throw` ExpressionException::class `with message` "error in evaluation of 'notExisting+4': unresolved variable: 'notExisting'"
5551
}
56-
57-
})
52+
}

0 commit comments

Comments
 (0)