Basically, a dirty flag that is checked every time you try to read a value. More specifically, each object has a self.value attribute that serves as a cache of the object's content. When it is notified that something has changed, it deletes the value. When you access it, it recomputes itself from scratch, taking advantage of caches of subobjects where possible.
You can see most of the logic in fern.ast.node.Node. self.refresh_impl is supposed to set self.value in a way appropriate to the subclass, and the Node functions handle the rest.
This is actually a pain to debug, especially with lots of nested scopes, but it seems to me like the evaluation should be lazy, rather than changing one object potentially re-computing the entire tree when you only cared about one part. You can call .refresh() on it manually if you need to get the computation out of the way.
You can see most of the logic in fern.ast.node.Node. self.refresh_impl is supposed to set self.value in a way appropriate to the subclass, and the Node functions handle the rest.
This is actually a pain to debug, especially with lots of nested scopes, but it seems to me like the evaluation should be lazy, rather than changing one object potentially re-computing the entire tree when you only cared about one part. You can call .refresh() on it manually if you need to get the computation out of the way.