- using System.Collections.Generic;
- namespace Ink.Parsed
- {
-
- public abstract class FlowBase : Parsed.Object, INamedContent
- {
- public class Argument
- {
- public Identifier identifier;
- public bool isByReference;
- public bool isDivertTarget;
- }
- public string name
- {
- get { return identifier?.name; }
- }
- public Identifier identifier { get; set; }
- public List<Argument> arguments { get; protected set; }
- public bool hasParameters { get { return arguments != null && arguments.Count > 0; } }
- public Dictionary<string, VariableAssignment> variableDeclarations;
- public abstract FlowLevel flowLevel { get; }
- public bool isFunction { get; protected set; }
- public FlowBase (Identifier name = null, List<Parsed.Object> topLevelObjects = null, List<Argument> arguments = null, bool isFunction = false, bool isIncludedStory = false)
- {
- this.identifier = name;
- if (topLevelObjects == null) {
- topLevelObjects = new List<Parsed.Object> ();
- }
-
- PreProcessTopLevelObjects (topLevelObjects);
- topLevelObjects = SplitWeaveAndSubFlowContent (topLevelObjects, isRootStory:this is Story && !isIncludedStory);
- AddContent(topLevelObjects);
- this.arguments = arguments;
- this.isFunction = isFunction;
- this.variableDeclarations = new Dictionary<string, VariableAssignment> ();
- }
- List<Parsed.Object> SplitWeaveAndSubFlowContent(List<Parsed.Object> contentObjs, bool isRootStory)
- {
- var weaveObjs = new List<Parsed.Object> ();
- var subFlowObjs = new List<Parsed.Object> ();
- _subFlowsByName = new Dictionary<string, FlowBase> ();
- foreach (var obj in contentObjs) {
- var subFlow = obj as FlowBase;
- if (subFlow) {
- if (_firstChildFlow == null)
- _firstChildFlow = subFlow;
- subFlowObjs.Add (obj);
- _subFlowsByName [subFlow.identifier?.name] = subFlow;
- } else {
- weaveObjs.Add (obj);
- }
- }
-
- if (isRootStory) {
- weaveObjs.Add (new Gather (null, 1));
- weaveObjs.Add (new Divert (new Path (Identifier.Done)));
- }
- var finalContent = new List<Parsed.Object> ();
- if (weaveObjs.Count > 0) {
- _rootWeave = new Weave (weaveObjs, 0);
- finalContent.Add (_rootWeave);
- }
- if (subFlowObjs.Count > 0) {
- finalContent.AddRange (subFlowObjs);
- }
- return finalContent;
- }
- protected virtual void PreProcessTopLevelObjects(List<Parsed.Object> topLevelObjects)
- {
-
- }
- public struct VariableResolveResult
- {
- public bool found;
- public bool isGlobal;
- public bool isArgument;
- public bool isTemporary;
- public FlowBase ownerFlow;
- }
- public VariableResolveResult ResolveVariableWithName(string varName, Parsed.Object fromNode)
- {
- var result = new VariableResolveResult ();
-
- var ownerFlow = fromNode == null ? this : fromNode.ClosestFlowBase ();
-
- if (ownerFlow.arguments != null ) {
- foreach (var arg in ownerFlow.arguments) {
- if (arg.identifier.name.Equals (varName)) {
- result.found = true;
- result.isArgument = true;
- result.ownerFlow = ownerFlow;
- return result;
- }
- }
- }
-
- var story = this.story;
- if (ownerFlow != story && ownerFlow.variableDeclarations.ContainsKey (varName)) {
- result.found = true;
- result.ownerFlow = ownerFlow;
- result.isTemporary = true;
- return result;
- }
-
- if (story.variableDeclarations.ContainsKey (varName)) {
- result.found = true;
- result.ownerFlow = story;
- result.isGlobal = true;
- return result;
- }
- result.found = false;
- return result;
- }
- public void TryAddNewVariableDeclaration(VariableAssignment varDecl)
- {
- var varName = varDecl.variableName;
- if (variableDeclarations.ContainsKey (varName)) {
- var prevDeclError = "";
- var debugMetadata = variableDeclarations [varName].debugMetadata;
- if (debugMetadata != null) {
- prevDeclError = " ("+variableDeclarations [varName].debugMetadata+")";
- }
- Error("found declaration variable '"+varName+"' that was already declared"+prevDeclError, varDecl, false);
- return;
- }
- variableDeclarations [varDecl.variableName] = varDecl;
- }
- public void ResolveWeavePointNaming ()
- {
-
-
- if( _rootWeave )
- _rootWeave.ResolveWeavePointNaming ();
- if (_subFlowsByName != null) {
- foreach (var namedSubFlow in _subFlowsByName) {
- namedSubFlow.Value.ResolveWeavePointNaming ();
- }
- }
- }
- public override Runtime.Object GenerateRuntimeObject ()
- {
- Return foundReturn = null;
- if (isFunction) {
- CheckForDisallowedFunctionFlowControl ();
- }
-
- else if( flowLevel == FlowLevel.Knot || flowLevel == FlowLevel.Stitch ) {
- foundReturn = Find<Return> ();
- if (foundReturn != null) {
- Error ("Return statements can only be used in knots that are declared as functions: == function " + this.identifier + " ==", foundReturn);
- }
- }
- var container = new Runtime.Container ();
- container.name = identifier?.name;
- if( this.story.countAllVisits ) {
- container.visitsShouldBeCounted = true;
- }
- GenerateArgumentVariableAssignments (container);
-
-
-
-
-
-
-
-
-
- int contentIdx = 0;
- while (content != null && contentIdx < content.Count) {
- Parsed.Object obj = content [contentIdx];
-
- if (obj is FlowBase) {
- var childFlow = (FlowBase)obj;
- var childFlowRuntime = childFlow.runtimeObject;
-
-
- if (contentIdx == 0 && !childFlow.hasParameters
- && this.flowLevel == FlowLevel.Knot) {
- _startingSubFlowDivert = new Runtime.Divert ();
- container.AddContent(_startingSubFlowDivert);
- _startingSubFlowRuntime = childFlowRuntime;
- }
-
- var namedChild = (Runtime.INamedContent)childFlowRuntime;
- Runtime.INamedContent existingChild = null;
- if (container.namedContent.TryGetValue(namedChild.name, out existingChild) ) {
- var errorMsg = string.Format ("{0} already contains flow named '{1}' (at {2})",
- this.GetType().Name,
- namedChild.name,
- (existingChild as Runtime.Object).debugMetadata);
- Error (errorMsg, childFlow);
- }
- container.AddToNamedContentOnly (namedChild);
- }
-
-
-
- else {
- container.AddContent (obj.runtimeObject);
- }
- contentIdx++;
- }
-
-
-
-
-
-
-
- if (flowLevel != FlowLevel.Story && !this.isFunction && _rootWeave != null && foundReturn == null) {
- _rootWeave.ValidateTermination (WarningInTermination);
- }
- return container;
- }
- void GenerateArgumentVariableAssignments(Runtime.Container container)
- {
- if (this.arguments == null || this.arguments.Count == 0) {
- return;
- }
-
-
-
- for (int i = arguments.Count - 1; i >= 0; --i) {
- var paramName = arguments [i].identifier?.name;
- var assign = new Runtime.VariableAssignment (paramName, isNewDeclaration:true);
- container.AddContent (assign);
- }
- }
- public Parsed.Object ContentWithNameAtLevel(string name, FlowLevel? level = null, bool deepSearch = false)
- {
-
- if (level == this.flowLevel || level == null) {
- if (name == this.identifier?.name) {
- return this;
- }
- }
- if ( level == FlowLevel.WeavePoint || level == null ) {
- Parsed.Object weavePointResult = null;
- if (_rootWeave) {
- weavePointResult = (Parsed.Object)_rootWeave.WeavePointNamed (name);
- if (weavePointResult)
- return weavePointResult;
- }
-
- if (level == FlowLevel.WeavePoint)
- return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
- }
-
-
- if (level != null && level < this.flowLevel)
- return null;
- FlowBase subFlow = null;
- if (_subFlowsByName.TryGetValue (name, out subFlow)) {
- if (level == null || level == subFlow.flowLevel)
- return subFlow;
- }
- return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
- }
- Parsed.Object DeepSearchForAnyLevelContent(string name)
- {
- var weaveResultSelf = ContentWithNameAtLevel (name, level:FlowLevel.WeavePoint, deepSearch: false);
- if (weaveResultSelf) {
- return weaveResultSelf;
- }
- foreach (var subFlowNamePair in _subFlowsByName) {
- var subFlow = subFlowNamePair.Value;
- var deepResult = subFlow.ContentWithNameAtLevel (name, level:null, deepSearch: true);
- if (deepResult)
- return deepResult;
- }
- return null;
- }
- public override void ResolveReferences (Story context)
- {
- if (_startingSubFlowDivert) {
- _startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path;
- }
- base.ResolveReferences(context);
-
- if (arguments != null) {
- foreach (var arg in arguments)
- context.CheckForNamingCollisions (this, arg.identifier, Story.SymbolType.Arg, "argument");
-
-
- for (int i = 0; i < arguments.Count; i++) {
- for (int j = i + 1; j < arguments.Count; j++) {
- if (arguments [i].identifier?.name == arguments [j].identifier?.name) {
- Error ("Multiple arguments with the same name: '" + arguments [i].identifier + "'");
- }
- }
- }
- }
-
- if (flowLevel != FlowLevel.Story) {
-
- var symbolType = flowLevel == FlowLevel.Knot ? Story.SymbolType.Knot : Story.SymbolType.SubFlowAndWeave;
- context.CheckForNamingCollisions (this, identifier, symbolType);
- }
- }
- void CheckForDisallowedFunctionFlowControl()
- {
- if (!(this is Knot)) {
- Error ("Functions cannot be stitches - i.e. they should be defined as '== function myFunc ==' rather than public to another knot.");
- }
-
- foreach (var subFlowAndName in _subFlowsByName) {
- var name = subFlowAndName.Key;
- var subFlow = subFlowAndName.Value;
- Error ("Functions may not contain stitches, but saw '"+name+"' within the function '"+this.identifier+"'", subFlow);
- }
- var allDiverts = _rootWeave.FindAll<Divert> ();
- foreach (var divert in allDiverts) {
- if( !divert.isFunctionCall && !(divert.parent is DivertTarget) )
- Error ("Functions may not contain diverts, but saw '"+divert.ToString()+"'", divert);
- }
- var allChoices = _rootWeave.FindAll<Choice> ();
- foreach (var choice in allChoices) {
- Error ("Functions may not contain choices, but saw '"+choice.ToString()+"'", choice);
- }
- }
- void WarningInTermination(Parsed.Object terminatingObject)
- {
- string message = "Apparent loose end exists where the flow runs out. Do you need a '-> DONE' statement, choice or divert?";
- if (terminatingObject.parent == _rootWeave && _firstChildFlow) {
- message = message + " Note that if you intend to enter '"+_firstChildFlow.identifier+"' next, you need to divert to it explicitly.";
- }
- var terminatingDivert = terminatingObject as Divert;
- if (terminatingDivert && terminatingDivert.isTunnel) {
- message = message + " When final tunnel to '"+terminatingDivert.target+" ->' returns it won't have anywhere to go.";
- }
- Warning (message, terminatingObject);
- }
- protected Dictionary<string, FlowBase> subFlowsByName {
- get {
- return _subFlowsByName;
- }
- }
- public override string typeName {
- get {
- if (isFunction) return "Function";
- else return flowLevel.ToString ();
- }
- }
- public override string ToString ()
- {
- return typeName+" '" + identifier + "'";
- }
- Weave _rootWeave;
- Dictionary<string, FlowBase> _subFlowsByName;
- Runtime.Divert _startingSubFlowDivert;
- Runtime.Object _startingSubFlowRuntime;
- FlowBase _firstChildFlow;
- }
- }