mirror of
https://github.com/Ratstail91/Mementos.git
synced 2025-11-29 10:34:27 +11:00
Committed everything
This commit is contained in:
@@ -0,0 +1,730 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user