Skip to content

Commit

Permalink
expressions improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhirkevich Alexander Y authored and Zhirkevich Alexander Y committed Jul 24, 2024
1 parent 4f949bc commit def299f
Show file tree
Hide file tree
Showing 26 changed files with 838 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ internal abstract class ExpressionProperty<T : Any> : AnimatedProperty<T> {

override fun interpolated(state: AnimationState): T {
val evaluator = expressionEvaluator
return mapEvaluated(evaluator.run { evaluate(state) })
val evaluated = evaluator.run { evaluate(state) }

return if (evaluated is AnimatedProperty<*>){
evaluated.interpolated(state) as T
} else {
mapEvaluated(evaluated)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,132 @@
package io.github.alexzhirkevich.compottie.internal.animation.expressions

internal interface EvaluationContext {
import io.github.alexzhirkevich.compottie.internal.animation.expressions.operations.condition.OpFunction


internal enum class VariableScope {
Global, Block
}

val variables : MutableMap<String, Any>
internal interface EvaluationContext {

val randomSource : RandomSource

fun registerFunction(function: OpFunction)

fun getFunction(name: String) : OpFunction?

fun getVariable(name : String) : Any?

fun setVariable(name: String, value : Any, scope: VariableScope)

fun withScope(extraVariables : Map<String ,Any>, block : (EvaluationContext) -> Any) : Any

}

internal class DefaultEvaluatorContext(
override val variables: MutableMap<String, Any> = mutableMapOf(),
override val randomSource: RandomSource = RandomSource(),
) : EvaluationContext {

val result: Any? get() = variables["\$bm_rt"]
private val globalVariables: MutableMap<String, Any> = mutableMapOf()

private val blockVariables: MutableMap<String, Any> = mutableMapOf()

private val functions: MutableMap<String, OpFunction> = mutableMapOf()

private val child by lazy {
BlockEvaluatorContext(this)
}

val result: Any? get() = getVariable("\$bm_rt")

fun reset() {
globalVariables.clear()
blockVariables.clear()
}

override fun setVariable(name: String, value: Any, scope: VariableScope) {
val map = when (scope) {
VariableScope.Global -> globalVariables
VariableScope.Block -> blockVariables
}
map[name] = value
}

override fun registerFunction(function: OpFunction) {
functions[function.name] = function
}

override fun getFunction(name: String): OpFunction? {
return functions[name]
}

override fun getVariable(name: String): Any? {
return when {
blockVariables.containsKey(name) -> blockVariables[name]
globalVariables.containsKey(name) -> globalVariables[name]
else -> null
}
}

override fun withScope(
extraVariables: Map<String, Any>,
block: (EvaluationContext) -> Any
) : Any {
child.reset()
extraVariables.forEach { (n, v) ->
child.setVariable(n, v, VariableScope.Block)
}
return block(child)
}
}

private class BlockEvaluatorContext(
private val parent : EvaluationContext
) : EvaluationContext {

private val child by lazy {
BlockEvaluatorContext(this)
}

private val scopeVariables: MutableMap<String, Any> = mutableMapOf()
private val scopeFunctions: MutableMap<String, OpFunction> = mutableMapOf()

override val randomSource: RandomSource
get() = parent.randomSource

override fun registerFunction(function: OpFunction) {
scopeFunctions[function.name] = function
}

override fun getFunction(name: String): OpFunction? {
return scopeFunctions[name] ?: parent.getFunction(name)
}

override fun getVariable(name: String): Any? {
return if (scopeVariables.containsKey(name)) {
scopeVariables[name]
} else {
parent.getVariable(name)
}
}

override fun setVariable(name: String, value: Any, scope: VariableScope) {
when (scope) {
VariableScope.Global -> parent.setVariable(name, value, scope)
VariableScope.Block -> scopeVariables[name] = value
}
}

override fun withScope(extraVariables: Map<String, Any>, block: (EvaluationContext) -> Any) : Any {
child.reset()
extraVariables.forEach { (n, v) ->
child.setVariable(n, v, VariableScope.Block)
}
return block(child)
}

fun reset() {
variables.clear()
scopeFunctions.clear()
scopeVariables.clear()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package io.github.alexzhirkevich.compottie.internal.animation.expressions
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty

internal val EXPR_DEBUG_PRINT_ENABLED = false
internal val EXPR_DEBUG_PRINT_ENABLED = true

internal fun interface Expression {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.github.alexzhirkevich.compottie.internal.animation.expressions.operati

internal interface ExpressionContext<T> : Expression {

fun interpret(op: String?, args: List<Expression>): Expression?
fun interpret(op: String?, args: List<Expression>?): Expression?

fun withContext(
block: T.(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private class ExpressionEvaluatorImpl(expr : String) : ExpressionEvaluator {

private val context = DefaultEvaluatorContext()

private val expression: Expression = MainExpressionInterpreter(expr).interpret()
private val expression: Expression = MainExpressionInterpreter(expr, context).interpret()

override fun RawProperty<*>.evaluate(state: AnimationState): Any {
return if (state.enableExpressions) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package io.github.alexzhirkevich.compottie.internal.animation.expressions
import androidx.compose.ui.util.fastForEach
import io.github.alexzhirkevich.compottie.Compottie

internal class MainExpressionInterpreter(expr : String) : ExpressionInterpreter {
internal class MainExpressionInterpreter(
expr : String,
private val context: EvaluationContext
) : ExpressionInterpreter {

private val expressions = try {
val lines = expr
.replace("\t", " ")
.replace("\r", "")
.replace("\n{", "{")
.split(";", "\n")
.filter(String::isNotBlank)

Expand All @@ -19,21 +23,31 @@ internal class MainExpressionInterpreter(expr : String) : ExpressionInterpreter

var line = lines[i]

while (line.endsWithExpressionChar() || line.countOpenedBlocks()> 0) {
while (
line.endsWithExpressionChar() ||
line.countOpenedBlocks()> 0
) {

check(i < lines.lastIndex) {
"Unexpected end of line: $line"
}
line += ";" + lines[i + 1]
line += if (line.endsWith("{")) {
lines[i + 1]
} else {
";" + lines[i + 1]
}
i++
}

add(line)
i++
}
}.map {
if (EXPR_DEBUG_PRINT_ENABLED) {
println("Expressions: $expr")
}
try {
SingleExpressionInterpreter(it).interpret()
SingleExpressionInterpreter(it, context).interpret()
} catch (t: Throwable) {
Compottie.logger?.warn(
"Unsupported or invalid Lottie expression: $it. You can ignore it if the animation runs fine or expressions are disabled (${t.message})"
Expand Down
Loading

0 comments on commit def299f

Please sign in to comment.