# Parsing context that holds anchors and what they refer to. # # When implementing `new(ctx : YAML::ParseContext, node : YAML::Nodes::Node)` # to deserialize an object from a node, `Reference` types must invoke # both `read_alias` and `record_anchor` in order to support parsing # recursive data structures. # # - `read_alias` must be invoked before an instance is created # - `record_anchor` must be invoked after an instance is created and # before its members are deserialized. class YAML::ParseContext def initialize # Recorded anchors: anchor => {boxed object, crystal_type_id} @anchors = {} of String => {Void*, Int32} end # Associates an object with an anchor. def record_anchor(node, object : T) : Nil forall T if anchor = node.anchor @anchors[anchor] = {Box(T).box(object), object.crystal_type_id} end end # Tries to read an alias from `node` of type `T`. Invokes # the block if successful, and invokers must return this object # instead of deserializing their members. def read_alias(node, type : T.class, &) forall T if ptr = read_alias_impl(node, T.crystal_instance_type_id, raise_on_alias: true) yield Box(T).unbox(ptr) end end # Similar to `read_alias` but doesn't raise if an alias exists # but an instance of type T isn't associated with the current anchor. def read_alias?(node, type : T.class, &) forall T if ptr = read_alias_impl(node, T.crystal_instance_type_id, raise_on_alias: false) yield Box(T).unbox(ptr) end end private def read_alias_impl(node, expected_crystal_type_id, raise_on_alias) if node.is_a?(YAML::Nodes::Alias) value = @anchors[node.anchor]? if value box, crystal_type_id = value if crystal_type_id == expected_crystal_type_id return box end end raise("Error deserializing alias") if raise_on_alias end nil end end