The co-inductive cache used for analysis
The cache contains two maps from (Config, Tree)
to Res
:
- input cache (
this.last
) - output cache (
this.current
)
The two caches are required because we want to make sure in a new iteration, an expression is evaluated exactly once. The monotonicity of the analysis ensures that the cache state goes up the lattice of the abstract domain, consequently the algorithm terminates.
The general skeleton for usage of the cache is as follows
def analysis(entryExp: Expr) = {
def iterate(entryExp: Expr)(using Cache) =
eval(entryExp, initConfig)
if cache.hasChanged && noErrors then
cache.last = cache.current
cache.current = Empty
cache.changed = false
iterate(entryExp)
else
reportErrors
def eval(expr: Expr, config: Config)(using Cache) =
cache.cachedEval(config, expr) {
// Actual recursive evaluation of expression.
//
// Only executed if the entry `(exp, config)` is not in the output cache.
}
iterate(entryExp)(using new Cache)
}
See the documentation for the method Cache.cachedEval
for more information.
What goes to the configuration (Config
) and what goes to the result (Res
) need to be decided by the specific analysis and justified by reasoning about soundness.
Type parameters
- Config
-
The analysis state that matters for evaluating an expression.
- Res
-
The result from the evaluation the given expression.
Attributes
- Companion
- object
- Graph
-
- Supertypes
- Known subtypes