Support local functions

commit_hash:7751f6b71a5611165e3a0c9b01614fd0618e44bc
This commit is contained in:
grechka62
2024-11-20 15:51:16 +03:00
parent b8bd860ae0
commit 20c8f33cce
24 changed files with 292 additions and 74 deletions
+7 -1
View File
@@ -734,6 +734,7 @@
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/Function.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/Function.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/FunctionArgument.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/FunctionArgument.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/FunctionProvider.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/FunctionProvider.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/LocalFunctionProvider.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/LocalFunctionProvider.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/StoredValueProvider.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/StoredValueProvider.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/VariableProvider.kt",
"client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/WarningSender.kt":"divkit/public/client/android/div-evaluable/src/main/java/com/yandex/div/evaluable/WarningSender.kt",
@@ -1078,23 +1079,27 @@
"client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/FunctionProviderDecorator.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/FunctionProviderDecorator.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/ChildPathUnitCache.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/ChildPathUnitCache.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/LocalFunction.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/LocalFunction.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeTree.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeTree.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValueDeclarationException.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValueDeclarationException.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValuesActionHandler.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValuesActionHandler.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValuesController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/storedvalues/StoredValuesController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/triggers/ConditionPart.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/triggers/ConditionPart.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/triggers/TriggersController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/triggers/TriggersController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/ConstantsProvider.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/ConstantsProvider.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/DivVariableController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/DivVariableController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/DivVariablesParser.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/DivVariablesParser.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/GlobalVariableController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/GlobalVariableController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/LocalVariableController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/LocalVariableController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/MultiVariableSource.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/MultiVariableSource.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/Observers.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/Observers.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/SingleVariableSource.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/SingleVariableSource.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableAndConstantController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableController.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableControllerImpl.kt",
"client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableDeclarationNotifier.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/expression/variables/VariableDeclarationNotifier.kt",
@@ -1147,6 +1152,7 @@
"client/android/div/src/main/java/com/yandex/div/core/util/DivTreeWalk.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/DivTreeWalk.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/DivUtil.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/DivUtil.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/ExpressionSubscribers.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/ExpressionSubscribers.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/FunctionMapper.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/FunctionMapper.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/ImageRepresentation.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/ImageRepresentation.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/ImageUtils.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/ImageUtils.kt",
"client/android/div/src/main/java/com/yandex/div/core/util/Releasables.kt":"divkit/public/client/android/div/src/main/java/com/yandex/div/core/util/Releasables.kt",
@@ -36,6 +36,11 @@ class IntegerOverflow(
cause: Exception? = null
) : EvaluableException("Failed to evaluate [$expression]. $REASON_INTEGER_OVERFLOW", cause)
class MissingLocalFunctionException(
name: String,
args: List<EvaluableType>,
): EvaluableException("Function '$name(${args.toMessageFormat()})' is missing.")
internal fun throwExceptionOnEvaluationFailed(
expression: String,
reason: String,
@@ -20,7 +20,9 @@ abstract class Function {
): Any {
val result = evaluate(evaluationContext, expressionContext, args)
if (EvaluableType.of(result) != resultType) {
throw EvaluableException("Function returned ${EvaluableType.of(result)}, but $resultType was expected")
throw EvaluableException(
"Function $this returned ${EvaluableType.of(result)}, but $resultType was expected."
)
}
return result
}
@@ -0,0 +1,25 @@
package com.yandex.div.evaluable
class LocalFunctionProvider(private val functions: List<Function>) : FunctionProvider {
override fun get(name: String, args: List<EvaluableType>): Function {
findFunction(name) { matchesArguments(args) }?.let { return it }
findFunction(name) { matchesArgumentsWithCast(args) }?.let { return it }
throw MissingLocalFunctionException(name, args)
}
override fun getMethod(name: String, args: List<EvaluableType>): Function {
findFunction(name) { matchesArguments(args) }?.let { return it }
findFunction(name) { matchesArgumentsWithCast(args) }?.let { return it }
throw MissingLocalFunctionException(name, args)
}
private fun findFunction(name: String, matcher: Function.() -> Function.MatchResult): Function? {
val localFunctions = functions.filter { it.name == name && it.matcher() == Function.MatchResult.Ok }
return when (localFunctions.size) {
0 -> null
1 -> return localFunctions[0]
else -> throw EvaluableException("Function ${localFunctions[0]} declared multiple times.")
}
}
}
@@ -2,9 +2,9 @@ package com.yandex.div.core.expression
import com.yandex.div.core.Disposable
import com.yandex.div.core.ObserverList
import com.yandex.div.core.expression.variables.LocalVariableController
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.core.expression.variables.VariableAndConstantController
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.core.expression.variables.VariableSource
import com.yandex.div.core.view2.errors.ErrorCollector
import com.yandex.div.evaluable.Evaluable
import com.yandex.div.evaluable.EvaluableException
@@ -40,7 +40,11 @@ internal class ExpressionResolverImpl(
var suppressMissingVariableException: Boolean = false
init {
onCreateCallback.onCreate(this, variableController)
onCreateCallback.onCreate(
this,
variableController,
evaluator.evaluationContext.functionProvider as FunctionProviderDecorator
)
}
override fun <R, T : Any> get(
@@ -226,13 +230,13 @@ internal class ExpressionResolverImpl(
}
}
operator fun plus(variableSource: VariableSource): ExpressionResolverImpl {
val localVariableController = LocalVariableController(variableController, variableSource)
operator fun plus(constants: ConstantsProvider): ExpressionResolverImpl {
val variableAndConstantController = VariableAndConstantController(variableController, constants)
return ExpressionResolverImpl(
variableController = localVariableController,
variableController = variableAndConstantController,
evaluator = Evaluator(
evaluationContext = EvaluationContext(
variableProvider = localVariableController,
variableProvider = variableAndConstantController,
storedValueProvider = evaluator.evaluationContext.storedValueProvider,
functionProvider = evaluator.evaluationContext.functionProvider,
warningSender = evaluator.evaluationContext.warningSender
@@ -256,6 +260,10 @@ internal class ExpressionResolverImpl(
* as a new ExpressionRuntime we are using OnCreateCallback.
*/
internal fun interface OnCreateCallback {
fun onCreate(resolver: ExpressionResolverImpl, variableController: VariableController)
fun onCreate(
resolver: ExpressionResolverImpl,
variableController: VariableController,
functionProvider: FunctionProviderDecorator
)
}
}
@@ -11,6 +11,7 @@ internal class ExpressionsRuntime(
val expressionResolver: ExpressionResolver,
val variableController: VariableController,
val triggersController: TriggersController? = null,
val functionProvider: FunctionProviderDecorator,
val runtimeStore: RuntimeStore,
) {
private val expressionResolverImpl get() = expressionResolver as? ExpressionResolverImpl
@@ -125,12 +125,13 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
addSource(globalVariableController.variableSource)
}
val functionProvider = FunctionProviderDecorator(GeneratedBuiltinFunctionProvider)
val evaluationContext = EvaluationContext(
variableProvider = variableController,
storedValueProvider = { storedValueName ->
storedValuesController.getStoredValue(storedValueName, errorCollector)?.getValue()
},
functionProvider = GeneratedBuiltinFunctionProvider,
functionProvider = functionProvider,
warningSender = { expressionContext, message ->
val rawExpr = expressionContext.evaluable.rawExpr
val warning = "Warning occurred while evaluating '$rawExpr': $message"
@@ -142,9 +143,9 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
val evaluator = Evaluator(evaluationContext)
val runtimeStore = RuntimeStore(evaluator, errorCollector, logger, divActionBinder)
val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController ->
val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
runtimeStore.putRuntime(
runtime = ExpressionsRuntime(resolver, variableController, null, runtimeStore)
runtime = ExpressionsRuntime(resolver, variableController, null, functionProvider, runtimeStore)
)
}
@@ -168,6 +169,7 @@ internal class ExpressionsRuntimeProvider @Inject constructor(
expressionResolver,
variableController,
triggersController,
functionProvider,
runtimeStore,
).also { runtimeStore.rootRuntime = it }
}
@@ -0,0 +1,38 @@
package com.yandex.div.core.expression
import com.yandex.div.evaluable.EvaluableType
import com.yandex.div.evaluable.Function
import com.yandex.div.evaluable.FunctionProvider
import com.yandex.div.evaluable.LocalFunctionProvider
import com.yandex.div.evaluable.MissingLocalFunctionException
internal class FunctionProviderDecorator(private val provider: FunctionProvider) : FunctionProvider {
override fun get(name: String, args: List<EvaluableType>) = provider.get(name, args)
override fun getMethod(name: String, args: List<EvaluableType>) = provider.getMethod(name, args)
operator fun plus(functions: List<Function>): FunctionProviderDecorator {
val localProvider = LocalFunctionProvider(functions)
return FunctionProviderDecorator(
object : FunctionProvider {
override fun get(name: String, args: List<EvaluableType>): Function {
return try {
localProvider.get(name, args)
} catch (e: MissingLocalFunctionException) {
provider.get(name, args)
}
}
override fun getMethod(name: String, args: List<EvaluableType>): Function {
return try {
localProvider.getMethod(name, args)
} catch (e: MissingLocalFunctionException) {
provider.getMethod(name, args)
}
}
}
)
}
}
@@ -123,19 +123,20 @@ internal class DivRuntimeVisitor @Inject constructor(
path: MutableList<String>,
parentRuntime: ExpressionsRuntime?
): ExpressionsRuntime? {
return if (div.value().variables.isNullOrEmpty() && div.value().variableTriggers.isNullOrEmpty()) {
parentRuntime
} else {
return if (div.needLocalRuntime) {
val stringPath = path.joinToString("/")
divView.runtimeStore?.getOrCreateRuntime(
path = stringPath,
variables = div.value().variables?.toVariables(),
triggers = div.value().variableTriggers,
functions = div.value().functions,
parentRuntime = parentRuntime,
)?.also { runtime -> runtime.onAttachedToWindow(divView) } ?: run {
Assert.fail("ExpressionRuntimeVisitor cannot create runtime for path = $stringPath")
null
}
} else {
parentRuntime
}
}
@@ -0,0 +1,47 @@
package com.yandex.div.core.expression.local
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.core.expression.variables.VariableAndConstantController
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.evaluable.Evaluable
import com.yandex.div.evaluable.EvaluableType
import com.yandex.div.evaluable.EvaluationContext
import com.yandex.div.evaluable.Evaluator
import com.yandex.div.evaluable.ExpressionContext
import com.yandex.div.evaluable.Function
import com.yandex.div.evaluable.FunctionArgument
class LocalFunction(
override val name: String,
override val declaredArgs: List<FunctionArgument>,
override val resultType: EvaluableType,
private val argNames: List<String>,
body: String,
) : Function() {
override val isPure = false
private val evaluable = Evaluable.lazy(body)
override fun evaluate(
evaluationContext: EvaluationContext,
expressionContext: ExpressionContext,
args: List<Any>
): Any {
val argConstants = mutableMapOf<String, Any>()
argNames.forEachIndexed { index, name ->
argConstants[name] = args[index]
}
val variableProvider = VariableAndConstantController(
evaluationContext.variableProvider as VariableController,
ConstantsProvider(argConstants),
)
val newContext = EvaluationContext(
variableProvider,
evaluationContext.storedValueProvider,
evaluationContext.functionProvider,
evaluationContext.warningSender
)
return Evaluator(newContext).eval(evaluable)
}
}
@@ -5,6 +5,7 @@ import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.triggers.TriggersController
import com.yandex.div.core.expression.variables.VariableControllerImpl
import com.yandex.div.core.util.toLocalFunctions
import com.yandex.div.core.view2.divs.DivActionBinder
import com.yandex.div.core.view2.errors.ErrorCollector
import com.yandex.div.data.Variable
@@ -13,6 +14,7 @@ import com.yandex.div.evaluable.Evaluator
import com.yandex.div.internal.Assert
import com.yandex.div.json.expressions.ExpressionResolver
import com.yandex.div2.DivBase
import com.yandex.div2.DivFunction
import com.yandex.div2.DivTrigger
private const val ERROR_UNKNOWN_RESOLVER =
@@ -41,8 +43,8 @@ internal class RuntimeStore(
}
private val onCreateCallback by lazy {
ExpressionResolverImpl.OnCreateCallback { resolver, variableController ->
ExpressionsRuntime(resolver, variableController, null, this).also {
ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
ExpressionsRuntime(resolver, variableController, null, functionProvider, this).also {
/**
* we cannot provide path here, otherwise descendants of ExpressionResolver will
* receive the same callback and override runtime for provided path.
@@ -70,10 +72,11 @@ internal class RuntimeStore(
path: String,
variables: List<Variable>? = null,
triggers: List<DivTrigger>? = null,
functions: List<DivFunction>? = null,
parentResolver: ExpressionResolver? = null,
parentRuntime: ExpressionsRuntime? = null,
) = tree.getNode(path)?.runtime ?: getRuntimeOrCreateChild(
path, variables, triggers,null, parentResolver, parentRuntime
path, variables, triggers, functions, null, parentResolver, parentRuntime
)
internal fun getRuntimeWithOrNull(resolver: ExpressionResolver) = resolverToRuntime[resolver]
@@ -98,6 +101,7 @@ internal class RuntimeStore(
path: String,
variables: List<Variable>?,
triggers: List<DivTrigger>?,
functions: List<DivFunction>?,
resolver: ExpressionResolver,
parentResolver: ExpressionResolver?,
): ExpressionsRuntime? {
@@ -110,7 +114,7 @@ internal class RuntimeStore(
}
if (runtimeForPath != null) tree.removeRuntimeAndCleanup(runtimeForPath, path)
return getRuntimeOrCreateChild(path, variables, triggers, existingRuntime, parentResolver)
return getRuntimeOrCreateChild(path, variables, triggers, functions, existingRuntime, parentResolver)
}
internal fun cleanup() {
@@ -135,14 +139,18 @@ internal class RuntimeStore(
path: String,
variables: List<Variable>?,
variablesTriggers: List<DivTrigger>?,
functions: List<DivFunction>?,
): ExpressionsRuntime {
val localVariableController = VariableControllerImpl(baseRuntime.variableController)
variables?.forEach { localVariableController.declare(it) }
var functionProvider = baseRuntime.functionProvider
functions?.let { functionProvider += it.toLocalFunctions() }
val evaluationContext = EvaluationContext(
variableProvider = localVariableController,
storedValueProvider = evaluator.evaluationContext.storedValueProvider,
functionProvider = evaluator.evaluationContext.functionProvider,
functionProvider = functionProvider,
warningSender = evaluator.evaluationContext.warningSender
)
@@ -165,7 +173,7 @@ internal class RuntimeStore(
ensureTriggersSynced(variablesTriggers)
}
return ExpressionsRuntime(resolver, localVariableController, triggerController, this).also {
return ExpressionsRuntime(resolver, localVariableController, triggerController, functionProvider, this).also {
putRuntime(it, path, parentRuntime)
}
}
@@ -174,6 +182,7 @@ internal class RuntimeStore(
path: String,
variables: List<Variable>?,
variablesTriggers: List<DivTrigger>?,
functions: List<DivFunction>?,
existingRuntime: ExpressionsRuntime? = null,
parentResolver: ExpressionResolver? = null,
parentRuntime: ExpressionsRuntime? = null,
@@ -189,13 +198,13 @@ internal class RuntimeStore(
val parentRuntime = parentRuntime ?: parentResolver?.let { getRuntimeWithOrNull(it) }
return if (variables.isNullOrEmpty() && variablesTriggers.isNullOrEmpty()) {
return if (needLocalRuntime(variables, variablesTriggers, functions)) {
createChildRuntime(runtime, parentRuntime, path, variables, variablesTriggers, functions)
} else {
runtime.also {
tree.storeRuntime(runtime, parentRuntime, path)
runtime.updateSubscriptions()
}
} else {
createChildRuntime(runtime, parentRuntime, path, variables, variablesTriggers)
}
}
}
@@ -1,7 +1,7 @@
package com.yandex.div.core.expression.local
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.variables.LocalVariableController
import com.yandex.div.core.expression.variables.VariableAndConstantController
internal class RuntimeTree {
private val runtimesToNodes = mutableMapOf<ExpressionsRuntime, RuntimeNode>()
@@ -49,7 +49,7 @@ internal class RuntimeTree {
pathToNodes.remove(it.path)
// we should not cleanup LocalVariableController because it will just cleanup it's delegate
if (it.runtime.variableController !is LocalVariableController) it.runtime.cleanup()
if (it.runtime.variableController !is VariableAndConstantController) it.runtime.cleanup()
}
}
@@ -0,0 +1,15 @@
package com.yandex.div.core.expression.local
import com.yandex.div.data.Variable
import com.yandex.div2.Div
import com.yandex.div2.DivFunction
import com.yandex.div2.DivTrigger
internal val Div.needLocalRuntime get() =
!value().run { variables.isNullOrEmpty() && variableTriggers.isNullOrEmpty() && functions.isNullOrEmpty() }
internal fun needLocalRuntime(
variables: List<Variable>?,
variableTriggers: List<DivTrigger>?,
functions: List<DivFunction>?
) = !(variables.isNullOrEmpty() && variableTriggers.isNullOrEmpty() && functions.isNullOrEmpty())
@@ -0,0 +1,7 @@
package com.yandex.div.core.expression.variables
import com.yandex.div.evaluable.VariableProvider
internal class ConstantsProvider(private val constants: Map<String, Any>) : VariableProvider {
override fun get(name: String) = constants[name]
}
@@ -4,11 +4,13 @@ import com.yandex.div.core.Disposable
import com.yandex.div.core.view2.errors.ErrorCollector
import com.yandex.div.data.Variable
internal class LocalVariableController(
internal class VariableAndConstantController(
private val delegate: VariableController,
private val localVariables: VariableSource
private val constants: ConstantsProvider,
) : VariableController {
override fun get(name: String) = constants.get(name) ?: super.get(name)
override fun subscribeToVariablesChange(
names: List<String>,
invokeOnSubscription: Boolean,
@@ -31,8 +33,7 @@ internal class LocalVariableController(
override fun addSource(source: VariableSource) = delegate.addSource(source)
override fun getMutableVariable(name: String) =
localVariables.getMutableVariable(name) ?: delegate.getMutableVariable(name)
override fun getMutableVariable(name: String) = delegate.getMutableVariable(name)
override fun declare(variable: Variable) = delegate.declare(variable)
@@ -0,0 +1,37 @@
package com.yandex.div.core.util
import com.yandex.div.evaluable.EvaluableType
import com.yandex.div.evaluable.FunctionArgument
import com.yandex.div.core.expression.local.LocalFunction
import com.yandex.div2.DivEvaluableType
import com.yandex.div2.DivFunction
internal fun List<DivFunction>.toLocalFunctions(): List<LocalFunction> {
return map { divFunction ->
val argNames = mutableListOf<String>()
val argTypes = mutableListOf<FunctionArgument>()
divFunction.arguments.forEach {
argNames.add(it.name)
argTypes.add(FunctionArgument(it.type.toEvaluableType()))
}
LocalFunction(
divFunction.name,
argTypes,
divFunction.returnType.toEvaluableType(),
argNames,
divFunction.body,
)
}
}
private fun DivEvaluableType.toEvaluableType() = when (this) {
DivEvaluableType.STRING -> EvaluableType.STRING
DivEvaluableType.INTEGER -> EvaluableType.INTEGER
DivEvaluableType.NUMBER -> EvaluableType.NUMBER
DivEvaluableType.BOOLEAN -> EvaluableType.BOOLEAN
DivEvaluableType.DATETIME -> EvaluableType.DATETIME
DivEvaluableType.COLOR -> EvaluableType.COLOR
DivEvaluableType.URL -> EvaluableType.URL
DivEvaluableType.DICT -> EvaluableType.DICT
DivEvaluableType.ARRAY -> EvaluableType.ARRAY
}
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.annotation.MainThread
import com.yandex.div.core.annotations.Mockable
import com.yandex.div.core.dagger.DivScope
import com.yandex.div.core.expression.local.needLocalRuntime
import com.yandex.div.core.expression.suppressExpressionErrors
import com.yandex.div.core.extension.DivExtensionController
import com.yandex.div.core.state.DivStatePath
@@ -250,17 +251,18 @@ internal class DivBinder @Inject constructor(
private fun getBindingContext(
parentContext: BindingContext, div: Div, path: DivStatePath
): BindingContext {
return if (div.value().variableTriggers.isNullOrEmpty() && div.value().variables.isNullOrEmpty()) {
parentContext
} else {
return if (div.needLocalRuntime) {
parentContext.getFor(
parentContext.runtimeStore?.getOrCreateRuntime(
path = path.fullPath,
parentResolver = parentContext.expressionResolver,
variables = div.value().variables?.toVariables(),
triggers = div.value().variableTriggers,
functions = div.value().functions,
)?.expressionResolver ?: parentContext.expressionResolver
)
} else {
parentContext
}
}
}
@@ -630,12 +630,13 @@ internal fun resolveRuntime(
resolver: ExpressionResolver,
parentResolver: ExpressionResolver,
) = runtimeStore?.resolveRuntimeWith(
path = path,
variables = div.variables?.toVariables(),
triggers = div.variableTriggers,
resolver = resolver,
parentResolver = parentResolver,
)
path = path,
variables = div.variables?.toVariables(),
triggers = div.variableTriggers,
functions = div.functions,
resolver = resolver,
parentResolver = parentResolver,
)
internal fun DivBase.getChildPathUnit(index: Int) = id ?: ChildPathUnitCache.getValue(index)
@@ -2,8 +2,7 @@ package com.yandex.div.internal.core
import com.yandex.div.core.annotations.InternalApi
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.variables.VariableSource
import com.yandex.div.data.Variable
import com.yandex.div.core.expression.variables.ConstantsProvider
import com.yandex.div.internal.util.forEach
import com.yandex.div.internal.util.mapIndexedNotNull
import com.yandex.div.json.expressions.ExpressionResolver
@@ -61,15 +60,11 @@ private fun DivCollectionItemBuilder.getItemResolver(
): ExpressionResolver? {
val resolverImpl = resolver as? ExpressionResolverImpl ?: return resolver
val validElement = resolverImpl.validateItemBuilderDataElement(dataElement, index) ?: return null
val localVariableSource = VariableSource(
mapOf(
dataElementName to Variable.DictVariable(dataElementName, validElement),
INDEX_VARIABLE_NAME to Variable.IntegerVariable(INDEX_VARIABLE_NAME, index.toLong())
),
{},
mutableListOf()
)
return resolverImpl + localVariableSource
val localDataProvider = ConstantsProvider(mapOf(
dataElementName to validElement,
INDEX_VARIABLE_NAME to index.toLong()
))
return resolverImpl + localDataProvider
}
private fun Div.copy(id: String? = value().id): Div {
@@ -11,18 +11,24 @@ import com.yandex.div.evaluable.Function
import com.yandex.div.evaluable.FunctionProvider
import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider
import com.yandex.div.evaluable.types.DateTime
import com.yandex.div.internal.parser.*
import com.yandex.div.internal.parser.Converter
import com.yandex.div.internal.parser.NUMBER_TO_DOUBLE
import com.yandex.div.internal.parser.TYPE_HELPER_BOOLEAN
import com.yandex.div.internal.parser.TYPE_HELPER_DOUBLE
import com.yandex.div.internal.parser.TYPE_HELPER_INT
import com.yandex.div.internal.parser.TYPE_HELPER_STRING
import com.yandex.div.internal.parser.TypeHelper
import com.yandex.div.json.ParsingErrorLogger
import com.yandex.div.json.ParsingException
import com.yandex.div.json.expressions.Expression
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner
import java.util.*
import org.junit.Before
import java.util.TimeZone
/**
* Tests for [ExpressionResolverImpl].
@@ -70,7 +76,7 @@ class ExpressionResolverImplTest {
private val evaluationContext = EvaluationContext(
variableProvider = { externalVariables.getMutableVariable(it)?.getValue() },
storedValueProvider = mock(),
functionProvider = GeneratedBuiltinFunctionProvider,
functionProvider = FunctionProviderDecorator(GeneratedBuiltinFunctionProvider),
warningSender = { _, _ -> }
)
@@ -78,7 +84,7 @@ class ExpressionResolverImplTest {
externalVariables,
Evaluator(evaluationContext),
mock()
) { _, _ -> }
) { _, _, _ -> }
private val withFuncGetCallback = { callback: () -> Unit ->
ExpressionResolverImpl(
@@ -87,7 +93,7 @@ class ExpressionResolverImplTest {
EvaluationContext(
variableProvider = evaluationContext.variableProvider,
storedValueProvider = evaluationContext.storedValueProvider,
functionProvider = object : FunctionProvider {
functionProvider = FunctionProviderDecorator(object : FunctionProvider {
override fun get(name: String, args: List<EvaluableType>): Function {
callback()
return evaluationContext.functionProvider.get(name, args)
@@ -97,12 +103,12 @@ class ExpressionResolverImplTest {
callback()
return evaluationContext.functionProvider.getMethod(name, args)
}
},
}),
warningSender = evaluationContext.warningSender
)
),
mock()
) { _, _ -> }
) { _, _, _ -> }
}
@Before
@@ -3,6 +3,7 @@ package com.yandex.div.core.expression.local
import com.yandex.div.core.Div2Logger
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.FunctionProviderDecorator
import com.yandex.div.core.expression.variables.VariableController
import com.yandex.div.core.state.DivStatePath
import com.yandex.div.core.view2.divs.DivActionBinder
@@ -26,7 +27,7 @@ private const val PARENT_VARIABLE = "parent_variable"
@RunWith(RobolectricTestRunner::class)
class RuntimeStoreTest {
private val evaluationContext = EvaluationContext(mock(), mock(), mock(), mock())
private val evaluationContext = EvaluationContext(mock(), mock(), mock<FunctionProviderDecorator>(), mock())
private val evaluator = mock<Evaluator> {
on { evaluationContext } doReturn evaluationContext
}
@@ -38,18 +39,19 @@ class RuntimeStoreTest {
private val underTest = RuntimeStore(evaluator, errorCollector, div2Logger, divActionBinder)
private var runtimeFromCallback: ExpressionsRuntime? = null
private val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController ->
private val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider ->
runtimeFromCallback = ExpressionsRuntime(
resolver, variableController, null, underTest
resolver, variableController, null, functionProvider, underTest
)
underTest.putRuntime(runtimeFromCallback!!)
}
private val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
private val rootVariableController: VariableController = mock<VariableController>()
private val functionProvider = mock<FunctionProviderDecorator>()
private val rootResolver = ExpressionResolverImpl(rootVariableController, evaluator, errorCollector, callback)
private val rootRuntime: ExpressionsRuntime =
ExpressionsRuntime(rootResolver, rootVariableController, null, underTest)
ExpressionsRuntime(rootResolver, rootVariableController, null, functionProvider, underTest)
@Before
fun putRootResolver() {
@@ -63,8 +65,8 @@ class RuntimeStoreTest {
@Test
fun `setPathToRuntimeWith links path to created runtime`() {
val newResolver = ExpressionResolverImpl(mock(), mock(), errorCollector, callback)
underTest.resolveRuntimeWith(path.fullPath, null, null, newResolver, newResolver)
val newResolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
underTest.resolveRuntimeWith(path.fullPath, null, null, null, newResolver, newResolver)
Assert.assertNotNull(underTest.getRuntimeWithOrNull(newResolver))
Assert.assertEquals(
@@ -76,7 +78,7 @@ class RuntimeStoreTest {
@Test
fun `setPathToRuntimeWith creates runtime with new variables if variables provided`() {
val variables = listOf(Variable.IntegerVariable(CHILD_VARIABLE, 123))
underTest.resolveRuntimeWith(path.fullPath, variables, null, resolver, resolver)
underTest.resolveRuntimeWith(path.fullPath, variables, null, null, resolver, resolver)
val runtime = underTest.getOrCreateRuntime(path.fullPath, null)
Assert.assertNotNull(underTest.getRuntimeWithOrNull(resolver))
@@ -89,7 +91,7 @@ class RuntimeStoreTest {
@Test
fun `getOrCreateRuntime returns runtime for path if exist`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val runtime = ExpressionsRuntime(resolver, mock(), null, underTest)
val runtime = ExpressionsRuntime(resolver, mock(), null, functionProvider, underTest)
underTest.putRuntime(runtime, PATH, rootRuntime)
Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, null, null))
@@ -101,7 +103,7 @@ class RuntimeStoreTest {
fun `getOrCreateRuntime returns parent runtime for path if no variables provided`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
val parentVariableController = mock<VariableController>()
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, underTest)
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest)
underTest.putRuntime(runtime, PARENT_PATH, rootRuntime)
Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, parentResolver = resolver))
@@ -115,7 +117,7 @@ class RuntimeStoreTest {
val parentVariableController = mock<VariableController> {
on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123")
}
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, underTest)
val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest)
underTest.putRuntime(runtime, PARENT_PATH, rootRuntime)
@@ -143,7 +145,7 @@ class RuntimeStoreTest {
@Test
fun `setPathToRuntimeWith links path to runtime`() {
val resolver = ExpressionResolverImpl(mock(), evaluator, errorCollector, callback)
underTest.resolveRuntimeWith(path.fullPath, null, null, resolver, resolver)
underTest.resolveRuntimeWith(path.fullPath, null, null, null, resolver, resolver)
Assert.assertNotNull(runtimeFromCallback)
Assert.assertEquals(
@@ -166,7 +168,7 @@ class RuntimeStoreTest {
parentVariableController, evaluator, errorCollector, callback
)
underTest.resolveRuntimeWith(path.fullPath, variables, null, resolver, resolver)
underTest.resolveRuntimeWith(path.fullPath, variables, null, null, resolver, resolver)
val newRuntime = underTest.getOrCreateRuntime(path.fullPath, null, null)
Assert.assertNotNull(newRuntime)
@@ -3,6 +3,7 @@ package com.yandex.div.core.expression.variables
import com.yandex.div.core.expression.ExpressionResolverImpl
import com.yandex.div.core.expression.ExpressionsRuntime
import com.yandex.div.core.expression.ExpressionsRuntimeProvider
import com.yandex.div.core.expression.FunctionProviderDecorator
import com.yandex.div.core.expression.local.RuntimeStore
import com.yandex.div.core.state.DivStatePath
import com.yandex.div.core.view2.BindingContext
@@ -10,6 +11,8 @@ import com.yandex.div.core.view2.Div2View
import com.yandex.div.core.view2.errors.ErrorCollector
import com.yandex.div.core.view2.errors.ErrorCollectors
import com.yandex.div.data.Variable
import com.yandex.div.evaluable.EvaluationContext
import com.yandex.div.evaluable.Evaluator
import com.yandex.div2.DivData
import org.junit.Assert.assertEquals
import org.junit.Test
@@ -41,8 +44,12 @@ class TwoWayVariableBinderTest {
}
private val path = DivStatePath(0)
private val store = RuntimeStore(mock(), mock(), mock(), mock())
private val expressionResolver = ExpressionResolverImpl(mock(), mock(), mock(), mock())
private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), store)
private val evaluationContext = EvaluationContext(mock(), mock(), mock<FunctionProviderDecorator>(), mock())
private val evaluator = mock<Evaluator> {
on { evaluationContext } doReturn evaluationContext
}
private val expressionResolver = ExpressionResolverImpl(mock(), evaluator, mock(), mock())
private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), mock(), store)
private val expressionsRuntimeProvider = mock<ExpressionsRuntimeProvider> {
on { getOrCreate(any(), any(), any()) } doReturn expressionsRuntime
}
@@ -124,7 +124,7 @@ class LocalVariablesTest {
private fun setVariable(name: String, value: String, path: String) {
val variableController = div2View.expressionsRuntime
?.runtimeStore?.getOrCreateRuntime(path, null, null, null)?.variableController
?.runtimeStore?.getOrCreateRuntime(path, null, null, null, null)?.variableController
val variable = variableController?.getMutableVariable(name) ?: return
variable.set(value)
}
+1
View File
@@ -222,6 +222,7 @@
},
"$description": "translations.json#/div_base_functions",
"platforms": [
"android",
"ios"
]
},