+import ast
+
+from .common import NodeProperty
+from lib.utils import Schema, SourceLogger
+
+class LazyLookup:
+ def __init__(self):
+ self.__tab = {}
+
+ def put(self, lazy):
+ self.__tab[lazy.get_key()] = lazy
+
+ def get(self, key):
+ return self.__tab[key] if key in self.__tab else None
+
+ def __setitem__(self, key, val):
+ lz = self.__tab[key]
+ lz.resolve_set(val)
+
+ def __getitem__(self, key):
+ lz = self.__tab[key]
+ return lz.resolve_get()
+
+class Lazy:
+ NodeValue = 'val'
+
+ LazyTypes = Schema.Union(NodeValue)
+ Syntax = Schema(ast.Attribute, attr=LazyTypes, value=ast.Name)
+
+ def __init__(self, source, type, target, env):
+ self.target = target
+ self.type = type
+ self.env = env
+ self.source = source
+
+ def __resolve_type(self):
+ if self.type == Lazy.NodeValue:
+ return NodeProperty.Value
+ return None
+
+ def resolve_get(self):
+ node = self.env.get_node(self.target)
+
+ accessor = self.__resolve_type()
+ if not accessor:
+ return None
+
+ status = NodeProperty.Status[node]
+ if status == "Updating":
+ tok = NodeProperty.Token[self.source]
+ SourceLogger.warn(self.source, tok,
+ f"cyclic dependency detected: {self.source._name} <-> {self.target}." +
+ f"Reusing cached value, maybe staled.")
+ else:
+ node.update()
+
+ return accessor[node] if accessor else None
+
+ def resolve_set(self, val):
+ node = self.env.get_node(self.target)
+ accessor = self.__resolve_type()
+
+ if NodeProperty.Readonly[node]:
+ raise self.source.config_error(
+ f"{self.target} is readonly")
+
+ if accessor:
+ accessor[node] = val
+ else:
+ raise self.source.config_error(
+ f"invalid type {self.type} for {self.target}")
+
+ def get_key(self):
+ return Lazy.get_key_from(self.type, self.target)
+
+ @staticmethod
+ def get_key_from(type, target):
+ return f"{type}${target}"
+
+ @staticmethod
+ def from_astn(cfgnode, astn):
+ if Lazy.Syntax != astn:
+ return None
+
+ type_ = astn.attr
+ target = astn.value.id
+
+ return Lazy.from_type(cfgnode, type_, target)
+
+ @staticmethod
+ def from_type(cfgnode, type_, target):
+ key = Lazy.get_key_from(type_, target)
+ lz = cfgnode._lazy_table.get(key)
+
+ if lz:
+ return key
+
+ lz = Lazy(cfgnode, type_, target, cfgnode._env)
+ cfgnode._lazy_table.put(lz)
+
+ return key
+