mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 02:24:28 +11:00
731 lines
28 KiB
C#
731 lines
28 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace Ink.Parsed
|
|
{
|
|
// Used by the FlowBase when constructing the weave flow from
|
|
// a flat list of content objects.
|
|
public class Weave : Parsed.Object
|
|
{
|
|
// Containers can be chained as multiple gather points
|
|
// get created as the same indentation level.
|
|
// rootContainer is always the first in the chain, while
|
|
// currentContainer is the latest.
|
|
public Runtime.Container rootContainer {
|
|
get {
|
|
if (_rootContainer == null) {
|
|
GenerateRuntimeObject ();
|
|
}
|
|
|
|
return _rootContainer;
|
|
}
|
|
}
|
|
Runtime.Container currentContainer { get; set; }
|
|
|
|
public int baseIndentIndex { get; private set; }
|
|
|
|
// Loose ends are:
|
|
// - Choices or Gathers that need to be joined up
|
|
// - Explicit Divert to gather points (i.e. "->" without a target)
|
|
public List<IWeavePoint> looseEnds;
|
|
|
|
public List<GatherPointToResolve> gatherPointsToResolve;
|
|
public class GatherPointToResolve
|
|
{
|
|
public Runtime.Divert divert;
|
|
public Runtime.Object targetRuntimeObj;
|
|
}
|
|
|
|
public Parsed.Object lastParsedSignificantObject
|
|
{
|
|
get {
|
|
if (content.Count == 0) return null;
|
|
|
|
// Don't count extraneous newlines or VAR/CONST declarations,
|
|
// since they're "empty" statements outside of the main flow.
|
|
Parsed.Object lastObject = null;
|
|
for (int i = content.Count - 1; i >= 0; --i) {
|
|
lastObject = content [i];
|
|
|
|
var lastText = lastObject as Parsed.Text;
|
|
if (lastText && lastText.text == "\n") {
|
|
continue;
|
|
}
|
|
|
|
if (IsGlobalDeclaration (lastObject))
|
|
continue;
|
|
|
|
break;
|
|
}
|
|
|
|
var lastWeave = lastObject as Weave;
|
|
if (lastWeave)
|
|
lastObject = lastWeave.lastParsedSignificantObject;
|
|
|
|
return lastObject;
|
|
}
|
|
}
|
|
|
|
public Weave(List<Parsed.Object> cont, int indentIndex=-1)
|
|
{
|
|
if (indentIndex == -1) {
|
|
baseIndentIndex = DetermineBaseIndentationFromContent (cont);
|
|
} else {
|
|
baseIndentIndex = indentIndex;
|
|
}
|
|
|
|
AddContent (cont);
|
|
|
|
ConstructWeaveHierarchyFromIndentation ();
|
|
}
|
|
|
|
public void ResolveWeavePointNaming ()
|
|
{
|
|
var namedWeavePoints = FindAll<IWeavePoint> (w => !string.IsNullOrEmpty (w.name));
|
|
|
|
_namedWeavePoints = new Dictionary<string, IWeavePoint> ();
|
|
|
|
foreach (var weavePoint in namedWeavePoints) {
|
|
|
|
// Check for weave point naming collisions
|
|
IWeavePoint existingWeavePoint;
|
|
if (_namedWeavePoints.TryGetValue (weavePoint.name, out existingWeavePoint)) {
|
|
var typeName = existingWeavePoint is Gather ? "gather" : "choice";
|
|
var existingObj = (Parsed.Object)existingWeavePoint;
|
|
|
|
Error ("A " + typeName + " with the same label name '" + weavePoint.name + "' already exists in this context on line " + existingObj.debugMetadata.startLineNumber, (Parsed.Object)weavePoint);
|
|
}
|
|
|
|
_namedWeavePoints [weavePoint.name] = weavePoint;
|
|
}
|
|
}
|
|
|
|
void ConstructWeaveHierarchyFromIndentation()
|
|
{
|
|
// Find nested indentation and convert to a proper object hierarchy
|
|
// (i.e. indented content is replaced with a Weave object that contains
|
|
// that nested content)
|
|
int contentIdx = 0;
|
|
while (contentIdx < content.Count) {
|
|
|
|
Parsed.Object obj = content [contentIdx];
|
|
|
|
// Choice or Gather
|
|
if (obj is IWeavePoint) {
|
|
var weavePoint = (IWeavePoint)obj;
|
|
var weaveIndentIdx = weavePoint.indentationDepth - 1;
|
|
|
|
// Inner level indentation - recurse
|
|
if (weaveIndentIdx > baseIndentIndex) {
|
|
|
|
// Step through content until indent jumps out again
|
|
int innerWeaveStartIdx = contentIdx;
|
|
while (contentIdx < content.Count) {
|
|
var innerWeaveObj = content [contentIdx] as IWeavePoint;
|
|
if (innerWeaveObj != null) {
|
|
var innerIndentIdx = innerWeaveObj.indentationDepth - 1;
|
|
if (innerIndentIdx <= baseIndentIndex) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
contentIdx++;
|
|
}
|
|
|
|
int weaveContentCount = contentIdx - innerWeaveStartIdx;
|
|
|
|
var weaveContent = content.GetRange (innerWeaveStartIdx, weaveContentCount);
|
|
content.RemoveRange (innerWeaveStartIdx, weaveContentCount);
|
|
|
|
var weave = new Weave (weaveContent, weaveIndentIdx);
|
|
InsertContent (innerWeaveStartIdx, weave);
|
|
|
|
// Continue iteration from this point
|
|
contentIdx = innerWeaveStartIdx;
|
|
}
|
|
|
|
}
|
|
|
|
contentIdx++;
|
|
}
|
|
}
|
|
|
|
// When the indentation wasn't told to us at construction time using
|
|
// a choice point with a known indentation level, we may be told to
|
|
// determine the indentation level by incrementing from our closest ancestor.
|
|
public int DetermineBaseIndentationFromContent(List<Parsed.Object> contentList)
|
|
{
|
|
foreach (var obj in contentList) {
|
|
if (obj is IWeavePoint) {
|
|
return ((IWeavePoint)obj).indentationDepth - 1;
|
|
}
|
|
}
|
|
|
|
// No weave points, so it doesn't matter
|
|
return 0;
|
|
}
|
|
|
|
public override Runtime.Object GenerateRuntimeObject ()
|
|
{
|
|
_rootContainer = currentContainer = new Runtime.Container();
|
|
looseEnds = new List<IWeavePoint> ();
|
|
|
|
gatherPointsToResolve = new List<GatherPointToResolve> ();
|
|
|
|
// Iterate through content for the block at this level of indentation
|
|
// - Normal content is nested under Choices and Gathers
|
|
// - Blocks that are further indented cause recursion
|
|
// - Keep track of loose ends so that they can be diverted to Gathers
|
|
foreach(var obj in content) {
|
|
|
|
// Choice or Gather
|
|
if (obj is IWeavePoint) {
|
|
AddRuntimeForWeavePoint ((IWeavePoint)obj);
|
|
}
|
|
|
|
// Non-weave point
|
|
else {
|
|
|
|
// Nested weave
|
|
if (obj is Weave) {
|
|
var weave = (Weave)obj;
|
|
AddRuntimeForNestedWeave (weave);
|
|
gatherPointsToResolve.AddRange (weave.gatherPointsToResolve);
|
|
}
|
|
|
|
// Other object
|
|
// May be complex object that contains statements - e.g. a multi-line conditional
|
|
else {
|
|
AddGeneralRuntimeContent (obj.runtimeObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass any loose ends up the hierarhcy
|
|
PassLooseEndsToAncestors();
|
|
|
|
return _rootContainer;
|
|
}
|
|
|
|
// Found gather point:
|
|
// - gather any loose ends
|
|
// - set the gather as the main container to dump new content in
|
|
void AddRuntimeForGather(Gather gather)
|
|
{
|
|
// Determine whether this Gather should be auto-entered:
|
|
// - It is auto-entered if there were no choices in the last section
|
|
// - A section is "since the previous gather" - so reset now
|
|
bool autoEnter = !hasSeenChoiceInSection;
|
|
hasSeenChoiceInSection = false;
|
|
|
|
var gatherContainer = gather.runtimeContainer;
|
|
|
|
if (gather.name == null) {
|
|
// Use disallowed character so it's impossible to have a name collision
|
|
gatherContainer.name = "g-" + _unnamedGatherCount;
|
|
_unnamedGatherCount++;
|
|
}
|
|
|
|
// Auto-enter: include in main content
|
|
if (autoEnter) {
|
|
currentContainer.AddContent (gatherContainer);
|
|
}
|
|
|
|
// Don't auto-enter:
|
|
// Add this gather to the main content, but only accessible
|
|
// by name so that it isn't stepped into automatically, but only via
|
|
// a divert from a loose end.
|
|
else {
|
|
_rootContainer.AddToNamedContentOnly (gatherContainer);
|
|
}
|
|
|
|
// Consume loose ends: divert them to this gather
|
|
foreach (IWeavePoint looseEndWeavePoint in looseEnds) {
|
|
|
|
var looseEnd = (Parsed.Object)looseEndWeavePoint;
|
|
|
|
// Skip gather loose ends that are at the same level
|
|
// since they'll be handled by the auto-enter code below
|
|
// that only jumps into the gather if (current runtime choices == 0)
|
|
if (looseEnd is Gather) {
|
|
var prevGather = (Gather)looseEnd;
|
|
if (prevGather.indentationDepth == gather.indentationDepth) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Runtime.Divert divert = null;
|
|
|
|
if (looseEnd is Parsed.Divert) {
|
|
divert = (Runtime.Divert) looseEnd.runtimeObject;
|
|
} else {
|
|
divert = new Runtime.Divert ();
|
|
var looseWeavePoint = looseEnd as IWeavePoint;
|
|
looseWeavePoint.runtimeContainer.AddContent (divert);
|
|
}
|
|
|
|
// Pass back knowledge of this loose end being diverted
|
|
// to the FlowBase so that it can maintain a list of them,
|
|
// and resolve the divert references later
|
|
gatherPointsToResolve.Add (new GatherPointToResolve{ divert = divert, targetRuntimeObj = gatherContainer });
|
|
}
|
|
looseEnds.Clear ();
|
|
|
|
// Replace the current container itself
|
|
currentContainer = gatherContainer;
|
|
}
|
|
|
|
void AddRuntimeForWeavePoint(IWeavePoint weavePoint)
|
|
{
|
|
// Current level Gather
|
|
if (weavePoint is Gather) {
|
|
AddRuntimeForGather ((Gather)weavePoint);
|
|
}
|
|
|
|
// Current level choice
|
|
else if (weavePoint is Choice) {
|
|
|
|
// Gathers that contain choices are no longer loose ends
|
|
// (same as when weave points get nested content)
|
|
if (previousWeavePoint is Gather) {
|
|
looseEnds.Remove (previousWeavePoint);
|
|
}
|
|
|
|
// Add choice point content
|
|
var choice = (Choice)weavePoint;
|
|
currentContainer.AddContent (choice.runtimeObject);
|
|
|
|
// Add choice's inner content to self
|
|
choice.innerContentContainer.name = "c-" + _choiceCount;
|
|
currentContainer.AddToNamedContentOnly (choice.innerContentContainer);
|
|
_choiceCount++;
|
|
|
|
hasSeenChoiceInSection = true;
|
|
}
|
|
|
|
// Keep track of loose ends
|
|
addContentToPreviousWeavePoint = false; // default
|
|
if (WeavePointHasLooseEnd (weavePoint)) {
|
|
looseEnds.Add (weavePoint);
|
|
|
|
|
|
var looseChoice = weavePoint as Choice;
|
|
if (looseChoice) {
|
|
addContentToPreviousWeavePoint = true;
|
|
}
|
|
}
|
|
previousWeavePoint = weavePoint;
|
|
}
|
|
|
|
// Add nested block at a greater indentation level
|
|
public void AddRuntimeForNestedWeave(Weave nestedResult)
|
|
{
|
|
// Add this inner block to current container
|
|
// (i.e. within the main container, or within the last defined Choice/Gather)
|
|
AddGeneralRuntimeContent (nestedResult.rootContainer);
|
|
|
|
// Now there's a deeper indentation level, the previous weave point doesn't
|
|
// count as a loose end (since it will have content to go to)
|
|
if (previousWeavePoint != null) {
|
|
looseEnds.Remove (previousWeavePoint);
|
|
addContentToPreviousWeavePoint = false;
|
|
}
|
|
}
|
|
|
|
// Normal content gets added into the latest Choice or Gather by default,
|
|
// unless there hasn't been one yet.
|
|
void AddGeneralRuntimeContent(Runtime.Object content)
|
|
{
|
|
// Content is allowed to evaluate runtimeObject to null
|
|
// (e.g. AuthorWarning, which doesn't make it into the runtime)
|
|
if (content == null)
|
|
return;
|
|
|
|
if (addContentToPreviousWeavePoint) {
|
|
previousWeavePoint.runtimeContainer.AddContent (content);
|
|
} else {
|
|
currentContainer.AddContent (content);
|
|
}
|
|
}
|
|
|
|
void PassLooseEndsToAncestors()
|
|
{
|
|
if (looseEnds.Count == 0) return;
|
|
|
|
// Search for Weave ancestor to pass loose ends to for gathering.
|
|
// There are two types depending on whether the current weave
|
|
// is separated by a conditional or sequence.
|
|
// - An "inner" weave is one that is directly connected to the current
|
|
// weave - i.e. you don't have to pass through a conditional or
|
|
// sequence to get to it. We're allowed to pass all loose ends to
|
|
// one of these.
|
|
// - An "outer" weave is one that is outside of a conditional/sequence
|
|
// that the current weave is nested within. We're only allowed to
|
|
// pass gathers (i.e. 'normal flow') loose ends up there, not normal
|
|
// choices. The rule is that choices have to be diverted explicitly
|
|
// by the author since it's ambiguous where flow should go otherwise.
|
|
//
|
|
// e.g.:
|
|
//
|
|
// - top <- e.g. outer weave
|
|
// {true:
|
|
// * choice <- e.g. inner weave
|
|
// * * choice 2
|
|
// more content <- e.g. current weave
|
|
// * choice 2
|
|
// }
|
|
// - more of outer weave
|
|
//
|
|
Weave closestInnerWeaveAncestor = null;
|
|
Weave closestOuterWeaveAncestor = null;
|
|
|
|
// Find inner and outer ancestor weaves as defined above.
|
|
bool nested = false;
|
|
for (var ancestor = this.parent; ancestor != null; ancestor = ancestor.parent)
|
|
{
|
|
|
|
// Found ancestor?
|
|
var weaveAncestor = ancestor as Weave;
|
|
if (weaveAncestor != null)
|
|
{
|
|
if (!nested && closestInnerWeaveAncestor == null)
|
|
closestInnerWeaveAncestor = weaveAncestor;
|
|
|
|
if (nested && closestOuterWeaveAncestor == null)
|
|
closestOuterWeaveAncestor = weaveAncestor;
|
|
}
|
|
|
|
|
|
// Weaves nested within Sequences or Conditionals are
|
|
// "sealed" - any loose ends require explicit diverts.
|
|
if (ancestor is Sequence || ancestor is Conditional)
|
|
nested = true;
|
|
}
|
|
|
|
// No weave to pass loose ends to at all?
|
|
if (closestInnerWeaveAncestor == null && closestOuterWeaveAncestor == null)
|
|
return;
|
|
|
|
// Follow loose end passing logic as defined above
|
|
for (int i = looseEnds.Count - 1; i >= 0; i--) {
|
|
var looseEnd = looseEnds[i];
|
|
|
|
bool received = false;
|
|
|
|
// This weave is nested within a conditional or sequence:
|
|
// - choices can only be passed up to direct ancestor ("inner") weaves
|
|
// - gathers can be passed up to either, but favour the closer (inner) weave
|
|
// if there is one
|
|
if(nested) {
|
|
if( looseEnd is Choice && closestInnerWeaveAncestor != null) {
|
|
closestInnerWeaveAncestor.ReceiveLooseEnd(looseEnd);
|
|
received = true;
|
|
}
|
|
|
|
else if( !(looseEnd is Choice) ) {
|
|
var receivingWeave = closestInnerWeaveAncestor ?? closestOuterWeaveAncestor;
|
|
if(receivingWeave != null) {
|
|
receivingWeave.ReceiveLooseEnd(looseEnd);
|
|
received = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No nesting, all loose ends can be safely passed up
|
|
else {
|
|
closestInnerWeaveAncestor.ReceiveLooseEnd(looseEnd);
|
|
received = true;
|
|
}
|
|
|
|
if(received) looseEnds.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
void ReceiveLooseEnd(IWeavePoint childWeaveLooseEnd)
|
|
{
|
|
looseEnds.Add(childWeaveLooseEnd);
|
|
}
|
|
|
|
public override void ResolveReferences(Story context)
|
|
{
|
|
base.ResolveReferences (context);
|
|
|
|
// Check that choices nested within conditionals and sequences are terminated
|
|
if( looseEnds != null && looseEnds.Count > 0 ) {
|
|
var isNestedWeave = false;
|
|
for (var ancestor = this.parent; ancestor != null; ancestor = ancestor.parent)
|
|
{
|
|
if (ancestor is Sequence || ancestor is Conditional)
|
|
{
|
|
isNestedWeave = true;
|
|
break;
|
|
}
|
|
}
|
|
if (isNestedWeave)
|
|
{
|
|
ValidateTermination(BadNestedTerminationHandler);
|
|
}
|
|
}
|
|
|
|
foreach(var gatherPoint in gatherPointsToResolve) {
|
|
gatherPoint.divert.targetPath = gatherPoint.targetRuntimeObj.path;
|
|
}
|
|
|
|
CheckForWeavePointNamingCollisions ();
|
|
}
|
|
|
|
public IWeavePoint WeavePointNamed(string name)
|
|
{
|
|
if (_namedWeavePoints == null)
|
|
return null;
|
|
|
|
IWeavePoint weavePointResult = null;
|
|
if (_namedWeavePoints.TryGetValue (name, out weavePointResult))
|
|
return weavePointResult;
|
|
|
|
return null;
|
|
}
|
|
|
|
// Global VARs and CONSTs are treated as "outside of the flow"
|
|
// when iterating over content that follows loose ends
|
|
bool IsGlobalDeclaration (Parsed.Object obj)
|
|
{
|
|
|
|
var varAss = obj as VariableAssignment;
|
|
if (varAss && varAss.isGlobalDeclaration && varAss.isDeclaration)
|
|
return true;
|
|
|
|
var constDecl = obj as ConstantDeclaration;
|
|
if (constDecl)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// While analysing final loose ends, we look to see whether there
|
|
// are any diverts etc which choices etc divert from
|
|
IEnumerable<Parsed.Object> ContentThatFollowsWeavePoint (IWeavePoint weavePoint)
|
|
{
|
|
var obj = (Parsed.Object)weavePoint;
|
|
|
|
// Inner content first (e.g. for a choice)
|
|
if (obj.content != null) {
|
|
foreach (var contentObj in obj.content) {
|
|
|
|
// Global VARs and CONSTs are treated as "outside of the flow"
|
|
if (IsGlobalDeclaration (contentObj)) continue;
|
|
|
|
yield return contentObj;
|
|
}
|
|
}
|
|
|
|
|
|
var parentWeave = obj.parent as Weave;
|
|
if (parentWeave == null) {
|
|
throw new System.Exception ("Expected weave point parent to be weave?");
|
|
}
|
|
|
|
var weavePointIdx = parentWeave.content.IndexOf (obj);
|
|
|
|
for (int i = weavePointIdx+1; i < parentWeave.content.Count; i++) {
|
|
var laterObj = parentWeave.content [i];
|
|
|
|
// Global VARs and CONSTs are treated as "outside of the flow"
|
|
if (IsGlobalDeclaration (laterObj)) continue;
|
|
|
|
// End of the current flow
|
|
if (laterObj is IWeavePoint)
|
|
break;
|
|
|
|
// Other weaves will be have their own loose ends
|
|
if (laterObj is Weave)
|
|
break;
|
|
|
|
yield return laterObj;
|
|
}
|
|
}
|
|
|
|
public delegate void BadTerminationHandler (Parsed.Object terminatingObj);
|
|
public void ValidateTermination (BadTerminationHandler badTerminationHandler)
|
|
{
|
|
// Don't worry if the last object in the flow is a "TODO",
|
|
// even if there are other loose ends in other places
|
|
if (lastParsedSignificantObject is AuthorWarning) {
|
|
return;
|
|
}
|
|
|
|
// By now, any sub-weaves will have passed loose ends up to the root weave (this).
|
|
// So there are 2 possible situations:
|
|
// - There are loose ends from somewhere in the flow.
|
|
// These aren't necessarily "real" loose ends - they're weave points
|
|
// that don't connect to any lower weave points, so we just
|
|
// have to check that they terminate properly.
|
|
// - This weave is just a list of content with no actual weave points,
|
|
// so we just need to check that the list of content terminates.
|
|
|
|
bool hasLooseEnds = looseEnds != null && looseEnds.Count > 0;
|
|
|
|
if (hasLooseEnds) {
|
|
foreach (var looseEnd in looseEnds) {
|
|
var looseEndFlow = ContentThatFollowsWeavePoint (looseEnd);
|
|
ValidateFlowOfObjectsTerminates (looseEndFlow, (Parsed.Object)looseEnd, badTerminationHandler);
|
|
}
|
|
}
|
|
|
|
// No loose ends... is there any inner weaving at all?
|
|
// If not, make sure the single content stream is terminated correctly
|
|
else {
|
|
|
|
// If there's any actual weaving, assume that content is
|
|
// terminated correctly since we would've had a loose end otherwise
|
|
foreach (var obj in content) {
|
|
if (obj is IWeavePoint) return;
|
|
}
|
|
|
|
// Straight linear flow? Check it terminates
|
|
ValidateFlowOfObjectsTerminates (content, this, badTerminationHandler);
|
|
}
|
|
}
|
|
|
|
void BadNestedTerminationHandler(Parsed.Object terminatingObj)
|
|
{
|
|
Conditional conditional = null;
|
|
for (var ancestor = terminatingObj.parent; ancestor != null; ancestor = ancestor.parent) {
|
|
if( ancestor is Sequence || ancestor is Conditional ) {
|
|
conditional = ancestor as Conditional;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var errorMsg = "Choices nested in conditionals or sequences need to explicitly divert afterwards.";
|
|
|
|
// Tutorialise proper choice syntax if this looks like a single choice within a condition, e.g.
|
|
// { condition:
|
|
// * choice
|
|
// }
|
|
if (conditional != null) {
|
|
var numChoices = conditional.FindAll<Choice>().Count;
|
|
if( numChoices == 1 ) {
|
|
errorMsg = "Choices with conditions should be written: '* {condition} choice'. Otherwise, "+ errorMsg.ToLower();
|
|
}
|
|
}
|
|
|
|
Error(errorMsg, terminatingObj);
|
|
}
|
|
|
|
void ValidateFlowOfObjectsTerminates (IEnumerable<Parsed.Object> objFlow, Parsed.Object defaultObj, BadTerminationHandler badTerminationHandler)
|
|
{
|
|
bool terminated = false;
|
|
Parsed.Object terminatingObj = defaultObj;
|
|
foreach (var flowObj in objFlow) {
|
|
|
|
var divert = flowObj.Find<Divert> (d => !d.isThread && !d.isTunnel && !d.isFunctionCall && !(d.parent is DivertTarget));
|
|
if (divert != null) {
|
|
terminated = true;
|
|
}
|
|
|
|
if (flowObj.Find<TunnelOnwards> () != null) {
|
|
terminated = true;
|
|
break;
|
|
}
|
|
|
|
terminatingObj = flowObj;
|
|
}
|
|
|
|
|
|
if (!terminated) {
|
|
|
|
// Author has left a note to self here - clearly we don't need
|
|
// to leave them with another warning since they know what they're doing.
|
|
if (terminatingObj is AuthorWarning) {
|
|
return;
|
|
}
|
|
|
|
badTerminationHandler (terminatingObj);
|
|
}
|
|
}
|
|
|
|
bool WeavePointHasLooseEnd(IWeavePoint weavePoint)
|
|
{
|
|
// No content, must be a loose end.
|
|
if (weavePoint.content == null) return true;
|
|
|
|
// If a weave point is diverted from, it doesn't have a loose end.
|
|
// Detect a divert object within a weavePoint's main content
|
|
// Work backwards since we're really interested in the end,
|
|
// although it doesn't actually make a difference!
|
|
// (content after a divert will simply be inaccessible)
|
|
for (int i = weavePoint.content.Count - 1; i >= 0; --i) {
|
|
var innerDivert = weavePoint.content [i] as Divert;
|
|
if (innerDivert) {
|
|
bool willReturn = innerDivert.isThread || innerDivert.isTunnel || innerDivert.isFunctionCall;
|
|
if (!willReturn) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Enforce rule that weave points must not have the same
|
|
// name as any stitches or knots upwards in the hierarchy
|
|
void CheckForWeavePointNamingCollisions()
|
|
{
|
|
if (_namedWeavePoints == null)
|
|
return;
|
|
|
|
var ancestorFlows = new List<FlowBase> ();
|
|
foreach (var obj in this.ancestry) {
|
|
var flow = obj as FlowBase;
|
|
if (flow)
|
|
ancestorFlows.Add (flow);
|
|
else
|
|
break;
|
|
}
|
|
|
|
|
|
foreach (var namedWeavePointPair in _namedWeavePoints) {
|
|
var weavePointName = namedWeavePointPair.Key;
|
|
var weavePoint = (Parsed.Object) namedWeavePointPair.Value;
|
|
|
|
foreach(var flow in ancestorFlows) {
|
|
|
|
// Shallow search
|
|
var otherContentWithName = flow.ContentWithNameAtLevel (weavePointName);
|
|
|
|
if (otherContentWithName && otherContentWithName != weavePoint) {
|
|
var errorMsg = string.Format ("{0} '{1}' has the same label name as a {2} (on {3})",
|
|
weavePoint.GetType().Name,
|
|
weavePointName,
|
|
otherContentWithName.GetType().Name,
|
|
otherContentWithName.debugMetadata);
|
|
|
|
Error(errorMsg, (Parsed.Object) weavePoint);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep track of previous weave point (Choice or Gather)
|
|
// at the current indentation level:
|
|
// - to add ordinary content to be nested under it
|
|
// - to add nested content under it when it's indented
|
|
// - to remove it from the list of loose ends when
|
|
// - it has indented content since it's no longer a loose end
|
|
// - it's a gather and it has a choice added to it
|
|
IWeavePoint previousWeavePoint = null;
|
|
bool addContentToPreviousWeavePoint = false;
|
|
|
|
// Used for determining whether the next Gather should auto-enter
|
|
bool hasSeenChoiceInSection = false;
|
|
|
|
int _unnamedGatherCount;
|
|
|
|
int _choiceCount;
|
|
|
|
|
|
Runtime.Container _rootContainer;
|
|
Dictionary<string, IWeavePoint> _namedWeavePoints;
|
|
}
|
|
}
|
|
|