- using System;
- using System.Collections.Generic;
- using System.Linq;
- namespace Ink.Parsed
- {
- public class Path
- {
- public FlowLevel baseTargetLevel {
- get {
- if (baseLevelIsAmbiguous)
- return FlowLevel.Story;
- else
- return (FlowLevel) _baseTargetLevel;
- }
- }
- public bool baseLevelIsAmbiguous {
- get {
- return _baseTargetLevel == null;
- }
- }
- public string firstComponent {
- get {
- if (components == null || components.Count == 0)
- return null;
- return components [0].name;
- }
- }
- public int numberOfComponents {
- get {
- return components.Count;
- }
- }
- public string dotSeparatedComponents {
- get {
- if( _dotSeparatedComponents == null ) {
- _dotSeparatedComponents = string.Join(".", components.Select(c => c?.name));
- }
- return _dotSeparatedComponents;
- }
- }
- string _dotSeparatedComponents;
- public List<Identifier> components { get; }
- public Path(FlowLevel baseFlowLevel, List<Identifier> components)
- {
- _baseTargetLevel = baseFlowLevel;
- this.components = components;
- }
- public Path(List<Identifier> components)
- {
- _baseTargetLevel = null;
- this.components = components;
- }
- public Path(Identifier ambiguousName)
- {
- _baseTargetLevel = null;
- components = new List<Identifier> ();
- components.Add (ambiguousName);
- }
- public override string ToString ()
- {
- if (components == null || components.Count == 0) {
- if (baseTargetLevel == FlowLevel.WeavePoint)
- return "-> <next gather point>";
- else
- return "<invalid Path>";
- }
- return "-> " + dotSeparatedComponents;
- }
- public Parsed.Object ResolveFromContext(Parsed.Object context)
- {
- if (components == null || components.Count == 0) {
- return null;
- }
- // Find base target of path from current context. e.g.
- // ==> BASE.sub.sub
- var baseTargetObject = ResolveBaseTarget (context);
- if (baseTargetObject == null) {
- return null;
- }
- // Given base of path, resolve final target by working deeper into hierarchy
- // e.g. ==> base.mid.FINAL
- if (components.Count > 1) {
- return ResolveTailComponents (baseTargetObject);
- }
- return baseTargetObject;
- }
- // Find the root object from the base, i.e. root from:
- // root.sub1.sub2
- Parsed.Object ResolveBaseTarget(Parsed.Object originalContext)
- {
- var firstComp = firstComponent;
- // Work up the ancestry to find the node that has the named object
- Parsed.Object ancestorContext = originalContext;
- while (ancestorContext != null) {
- // Only allow deep search when searching deeper from original context.
- // Don't allow search upward *then* downward, since that's searching *everywhere*!
- // Allowed examples:
- // - From an inner gather of a stitch, you should search up to find a knot called 'x'
- // at the root of a story, but not a stitch called 'x' in that knot.
- // - However, from within a knot, you should be able to find a gather/choice
- // anywhere called 'x'
- // (that latter example is quite loose, but we allow it)
- bool deepSearch = ancestorContext == originalContext;
- var foundBase = TryGetChildFromContext (ancestorContext, firstComp, null, deepSearch);
- if (foundBase != null)
- return foundBase;
- ancestorContext = ancestorContext.parent;
- }
- return null;
- }
- // Find the final child from path given root, i.e.:
- // root.sub.finalChild
- Parsed.Object ResolveTailComponents(Parsed.Object rootTarget)
- {
- Parsed.Object foundComponent = rootTarget;
- for (int i = 1; i < components.Count; ++i) {
- var compName = components [i].name;
- FlowLevel minimumExpectedLevel;
- var foundFlow = foundComponent as FlowBase;
- if (foundFlow != null)
- minimumExpectedLevel = (FlowLevel)(foundFlow.flowLevel + 1);
- else
- minimumExpectedLevel = FlowLevel.WeavePoint;
- foundComponent = TryGetChildFromContext (foundComponent, compName, minimumExpectedLevel);
- if (foundComponent == null)
- break;
- }
- return foundComponent;
- }
- // See whether "context" contains a child with a given name at a given flow level
- // Can either be a named knot/stitch (a FlowBase) or a weave point within a Weave (Choice or Gather)
- // This function also ignores any other object types that are neither FlowBase nor Weave.
- // Called from both ResolveBase (force deep) and ResolveTail for the individual components.
- Parsed.Object TryGetChildFromContext(Parsed.Object context, string childName, FlowLevel? minimumLevel, bool forceDeepSearch = false)
- {
- // null childLevel means that we don't know where to find it
- bool ambiguousChildLevel = minimumLevel == null;
- // Search for WeavePoint within Weave
- var weaveContext = context as Weave;
- if ( weaveContext != null && (ambiguousChildLevel || minimumLevel == FlowLevel.WeavePoint)) {
- return (Parsed.Object) weaveContext.WeavePointNamed (childName);
- }
- // Search for content within Flow (either a sub-Flow or a WeavePoint)
- var flowContext = context as FlowBase;
- if (flowContext != null) {
- // When searching within a Knot, allow a deep searches so that
- // named weave points (choices and gathers) can be found within any stitch
- // Otherwise, we just search within the immediate object.
- var shouldDeepSearch = forceDeepSearch || flowContext.flowLevel == FlowLevel.Knot;
- return flowContext.ContentWithNameAtLevel (childName, minimumLevel, shouldDeepSearch);
- }
- return null;
- }
- FlowLevel? _baseTargetLevel;
- }
- }