module Fable class RuntimeObject # RuntimeObjects can be included in the main story as a hierarchy # usually parents are container objectsd attr_accessor :parent, :own_debug_metadata, :path, :original_object def initialize @path = nil end def debug_metadata if @own_debug_metadata.nil? if !parent.nil? return parent.debug_metadata || DebugMetadata.new end end return @own_debug_metadata end def indentation_string(indentation = 0) " " * indentation end def debug_line_number_of_path(path) return nil if path.nil? # Try to get a line number from debug metadata root = self.root_content_container if !root.nil? target_content = root.content_at_path(path).object if !target_content.nil? target_debug_metadata = target_content.debug_metadata if !target_debug_metadata.nil? target_debug_metadata.start_line_number end end end end def path if !defined?(@path) || @path.nil? if parent.nil? @path = Path.new("") else # Maintain a stack so that the order of the components is reversed # when they're added to the Path. We're iterating up from the # leaves/children to the root components = [] child = self container = child.parent while !container.nil? if child.is_a?(Container) && child.valid_name? components << Path::Component.new(name: child.name) else components << Path::Component.new(index: container.content.index(child)) end child = container container = container.parent end @path = Path.new(components.reverse) end end return @path end def resolve_path(path) if path.relative? nearest_container = self if !nearest_container.is_a?(Container) nearest_container = self.parent path = path.tail end return nearest_container.content_at_path(path) else return self.root_content_container.content_at_path(path) end end def convert_path_to_relative(global_path) # 1. Find last shared ancestor # 2. Drill up using '..' style (actually represented as "^") # 3. Re-build downward chain from common ancestor own_path = self.path min_path_length = [global_path.length, own_path.length].min last_shared_path_comp_index = -1 (0..min_path_length).each do |i| own_component = own_path.components[i] other_component = global_path.components[i] if own_component == other_component last_shared_path_comp_index = i else break end end # No shared path components, so just use global path if last_shared_path_comp_index == -1 return global_path end number_of_upwards_moves = (own_path.length - 1) - last_shared_path_comp_index - 1 new_path_components = [] (0..number_of_upwards_moves).each do |i| new_path_components << Path::Component.parent_component end (last_shared_path_comp_index + 1..global_path.length).each do |i| new_path_components << global_path.components[i] end return Path.new(new_path_components, true) end # Find the most compact representation for a path, # whether relative or global def compact_path_string(other_path) if other_path.relative? relative_path_string = other_path.components_string global_path_string = self.path.path_by_appending_path(other_path).components_string else relative_path = convert_path_to_relative(other_path) relative_path_string = relative_path.components_string global_path_string = other_path.components_string end if relative_path_string.length < global_path_string.length return relative_path_string else return global_path_string end end def root_content_container ancestor = self while !ancestor.parent.nil? ancestor = ancestor.parent end return ancestor end def copy raise NotImplementedError, "#{self.class} doesn't support copying" end end end