Files
Mementos/Unity/Alternate Genre Jam/Assets/Ink/InkLibs/InkCompiler/ParsedHierarchy/Path.cs
2021-06-30 21:39:19 +10:00

193 lines
6.7 KiB
C#

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;
}
}