Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkCompiler / ParsedHierarchy / FlowBase.cs
  1. using System.Collections.Generic;
  2. namespace Ink.Parsed
  3. {
  4. // Base class for Knots and Stitches
  5. public abstract class FlowBase : Parsed.Object, INamedContent
  6. {
  7. public class Argument
  8. {
  9. public Identifier identifier;
  10. public bool isByReference;
  11. public bool isDivertTarget;
  12. }
  13. public string name
  14. {
  15. get { return identifier?.name; }
  16. }
  17. public Identifier identifier { get; set; }
  18. public List<Argument> arguments { get; protected set; }
  19. public bool hasParameters { get { return arguments != null && arguments.Count > 0; } }
  20. public Dictionary<string, VariableAssignment> variableDeclarations;
  21. public abstract FlowLevel flowLevel { get; }
  22. public bool isFunction { get; protected set; }
  23. public FlowBase (Identifier name = null, List<Parsed.Object> topLevelObjects = null, List<Argument> arguments = null, bool isFunction = false, bool isIncludedStory = false)
  24. {
  25. this.identifier = name;
  26. if (topLevelObjects == null) {
  27. topLevelObjects = new List<Parsed.Object> ();
  28. }
  29. // Used by story to add includes
  30. PreProcessTopLevelObjects (topLevelObjects);
  31. topLevelObjects = SplitWeaveAndSubFlowContent (topLevelObjects, isRootStory:this is Story && !isIncludedStory);
  32. AddContent(topLevelObjects);
  33. this.arguments = arguments;
  34. this.isFunction = isFunction;
  35. this.variableDeclarations = new Dictionary<string, VariableAssignment> ();
  36. }
  37. List<Parsed.Object> SplitWeaveAndSubFlowContent(List<Parsed.Object> contentObjs, bool isRootStory)
  38. {
  39. var weaveObjs = new List<Parsed.Object> ();
  40. var subFlowObjs = new List<Parsed.Object> ();
  41. _subFlowsByName = new Dictionary<string, FlowBase> ();
  42. foreach (var obj in contentObjs) {
  43. var subFlow = obj as FlowBase;
  44. if (subFlow) {
  45. if (_firstChildFlow == null)
  46. _firstChildFlow = subFlow;
  47. subFlowObjs.Add (obj);
  48. _subFlowsByName [subFlow.identifier?.name] = subFlow;
  49. } else {
  50. weaveObjs.Add (obj);
  51. }
  52. }
  53. // Implicit final gather in top level story for ending without warning that you run out of content
  54. if (isRootStory) {
  55. weaveObjs.Add (new Gather (null, 1));
  56. weaveObjs.Add (new Divert (new Path (Identifier.Done)));
  57. }
  58. var finalContent = new List<Parsed.Object> ();
  59. if (weaveObjs.Count > 0) {
  60. _rootWeave = new Weave (weaveObjs, 0);
  61. finalContent.Add (_rootWeave);
  62. }
  63. if (subFlowObjs.Count > 0) {
  64. finalContent.AddRange (subFlowObjs);
  65. }
  66. return finalContent;
  67. }
  68. protected virtual void PreProcessTopLevelObjects(List<Parsed.Object> topLevelObjects)
  69. {
  70. // empty by default, used by Story to process included file references
  71. }
  72. public struct VariableResolveResult
  73. {
  74. public bool found;
  75. public bool isGlobal;
  76. public bool isArgument;
  77. public bool isTemporary;
  78. public FlowBase ownerFlow;
  79. }
  80. public VariableResolveResult ResolveVariableWithName(string varName, Parsed.Object fromNode)
  81. {
  82. var result = new VariableResolveResult ();
  83. // Search in the stitch / knot that owns the node first
  84. var ownerFlow = fromNode == null ? this : fromNode.ClosestFlowBase ();
  85. // Argument
  86. if (ownerFlow.arguments != null ) {
  87. foreach (var arg in ownerFlow.arguments) {
  88. if (arg.identifier.name.Equals (varName)) {
  89. result.found = true;
  90. result.isArgument = true;
  91. result.ownerFlow = ownerFlow;
  92. return result;
  93. }
  94. }
  95. }
  96. // Temp
  97. var story = this.story; // optimisation
  98. if (ownerFlow != story && ownerFlow.variableDeclarations.ContainsKey (varName)) {
  99. result.found = true;
  100. result.ownerFlow = ownerFlow;
  101. result.isTemporary = true;
  102. return result;
  103. }
  104. // Global
  105. if (story.variableDeclarations.ContainsKey (varName)) {
  106. result.found = true;
  107. result.ownerFlow = story;
  108. result.isGlobal = true;
  109. return result;
  110. }
  111. result.found = false;
  112. return result;
  113. }
  114. public void TryAddNewVariableDeclaration(VariableAssignment varDecl)
  115. {
  116. var varName = varDecl.variableName;
  117. if (variableDeclarations.ContainsKey (varName)) {
  118. var prevDeclError = "";
  119. var debugMetadata = variableDeclarations [varName].debugMetadata;
  120. if (debugMetadata != null) {
  121. prevDeclError = " ("+variableDeclarations [varName].debugMetadata+")";
  122. }
  123. Error("found declaration variable '"+varName+"' that was already declared"+prevDeclError, varDecl, false);
  124. return;
  125. }
  126. variableDeclarations [varDecl.variableName] = varDecl;
  127. }
  128. public void ResolveWeavePointNaming ()
  129. {
  130. // Find all weave points and organise them by name ready for
  131. // diverting. Also detect naming collisions.
  132. if( _rootWeave )
  133. _rootWeave.ResolveWeavePointNaming ();
  134. if (_subFlowsByName != null) {
  135. foreach (var namedSubFlow in _subFlowsByName) {
  136. namedSubFlow.Value.ResolveWeavePointNaming ();
  137. }
  138. }
  139. }
  140. public override Runtime.Object GenerateRuntimeObject ()
  141. {
  142. Return foundReturn = null;
  143. if (isFunction) {
  144. CheckForDisallowedFunctionFlowControl ();
  145. }
  146. // Non-functon: Make sure knots and stitches don't attempt to use Return statement
  147. else if( flowLevel == FlowLevel.Knot || flowLevel == FlowLevel.Stitch ) {
  148. foundReturn = Find<Return> ();
  149. if (foundReturn != null) {
  150. Error ("Return statements can only be used in knots that are declared as functions: == function " + this.identifier + " ==", foundReturn);
  151. }
  152. }
  153. var container = new Runtime.Container ();
  154. container.name = identifier?.name;
  155. if( this.story.countAllVisits ) {
  156. container.visitsShouldBeCounted = true;
  157. }
  158. GenerateArgumentVariableAssignments (container);
  159. // Run through content defined for this knot/stitch:
  160. // - First of all, any initial content before a sub-stitch
  161. // or any weave content is added to the main content container
  162. // - The first inner knot/stitch is automatically entered, while
  163. // the others are only accessible by an explicit divert
  164. // - The exception to this rule is if the knot/stitch takes
  165. // parameters, in which case it can't be auto-entered.
  166. // - Any Choices and Gathers (i.e. IWeavePoint) found are
  167. // processsed by GenerateFlowContent.
  168. int contentIdx = 0;
  169. while (content != null && contentIdx < content.Count) {
  170. Parsed.Object obj = content [contentIdx];
  171. // Inner knots and stitches
  172. if (obj is FlowBase) {
  173. var childFlow = (FlowBase)obj;
  174. var childFlowRuntime = childFlow.runtimeObject;
  175. // First inner stitch - automatically step into it
  176. // 20/09/2016 - let's not auto step into knots
  177. if (contentIdx == 0 && !childFlow.hasParameters
  178. && this.flowLevel == FlowLevel.Knot) {
  179. _startingSubFlowDivert = new Runtime.Divert ();
  180. container.AddContent(_startingSubFlowDivert);
  181. _startingSubFlowRuntime = childFlowRuntime;
  182. }
  183. // Check for duplicate knots/stitches with same name
  184. var namedChild = (Runtime.INamedContent)childFlowRuntime;
  185. Runtime.INamedContent existingChild = null;
  186. if (container.namedContent.TryGetValue(namedChild.name, out existingChild) ) {
  187. var errorMsg = string.Format ("{0} already contains flow named '{1}' (at {2})",
  188. this.GetType().Name,
  189. namedChild.name,
  190. (existingChild as Runtime.Object).debugMetadata);
  191. Error (errorMsg, childFlow);
  192. }
  193. container.AddToNamedContentOnly (namedChild);
  194. }
  195. // Other content (including entire Weaves that were grouped in the constructor)
  196. // At the time of writing, all FlowBases have a maximum of one piece of "other content"
  197. // and it's always the root Weave
  198. else {
  199. container.AddContent (obj.runtimeObject);
  200. }
  201. contentIdx++;
  202. }
  203. // CHECK FOR FINAL LOOSE ENDS!
  204. // Notes:
  205. // - Functions don't need to terminate - they just implicitly return
  206. // - If return statement was found, don't continue finding warnings for missing control flow,
  207. // since it's likely that a return statement has been used instead of a ->-> or something,
  208. // or the writer failed to mark the knot as a function.
  209. // - _rootWeave may be null if it's a knot that only has stitches
  210. if (flowLevel != FlowLevel.Story && !this.isFunction && _rootWeave != null && foundReturn == null) {
  211. _rootWeave.ValidateTermination (WarningInTermination);
  212. }
  213. return container;
  214. }
  215. void GenerateArgumentVariableAssignments(Runtime.Container container)
  216. {
  217. if (this.arguments == null || this.arguments.Count == 0) {
  218. return;
  219. }
  220. // Assign parameters in reverse since they'll be popped off the evaluation stack
  221. // No need to generate EvalStart and EvalEnd since there's nothing being pushed
  222. // back onto the evaluation stack.
  223. for (int i = arguments.Count - 1; i >= 0; --i) {
  224. var paramName = arguments [i].identifier?.name;
  225. var assign = new Runtime.VariableAssignment (paramName, isNewDeclaration:true);
  226. container.AddContent (assign);
  227. }
  228. }
  229. public Parsed.Object ContentWithNameAtLevel(string name, FlowLevel? level = null, bool deepSearch = false)
  230. {
  231. // Referencing self?
  232. if (level == this.flowLevel || level == null) {
  233. if (name == this.identifier?.name) {
  234. return this;
  235. }
  236. }
  237. if ( level == FlowLevel.WeavePoint || level == null ) {
  238. Parsed.Object weavePointResult = null;
  239. if (_rootWeave) {
  240. weavePointResult = (Parsed.Object)_rootWeave.WeavePointNamed (name);
  241. if (weavePointResult)
  242. return weavePointResult;
  243. }
  244. // Stop now if we only wanted a result if it's a weave point?
  245. if (level == FlowLevel.WeavePoint)
  246. return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
  247. }
  248. // If this flow would be incapable of containing the requested level, early out
  249. // (e.g. asking for a Knot from a Stitch)
  250. if (level != null && level < this.flowLevel)
  251. return null;
  252. FlowBase subFlow = null;
  253. if (_subFlowsByName.TryGetValue (name, out subFlow)) {
  254. if (level == null || level == subFlow.flowLevel)
  255. return subFlow;
  256. }
  257. return deepSearch ? DeepSearchForAnyLevelContent(name) : null;
  258. }
  259. Parsed.Object DeepSearchForAnyLevelContent(string name)
  260. {
  261. var weaveResultSelf = ContentWithNameAtLevel (name, level:FlowLevel.WeavePoint, deepSearch: false);
  262. if (weaveResultSelf) {
  263. return weaveResultSelf;
  264. }
  265. foreach (var subFlowNamePair in _subFlowsByName) {
  266. var subFlow = subFlowNamePair.Value;
  267. var deepResult = subFlow.ContentWithNameAtLevel (name, level:null, deepSearch: true);
  268. if (deepResult)
  269. return deepResult;
  270. }
  271. return null;
  272. }
  273. public override void ResolveReferences (Story context)
  274. {
  275. if (_startingSubFlowDivert) {
  276. _startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path;
  277. }
  278. base.ResolveReferences(context);
  279. // Check validity of parameter names
  280. if (arguments != null) {
  281. foreach (var arg in arguments)
  282. context.CheckForNamingCollisions (this, arg.identifier, Story.SymbolType.Arg, "argument");
  283. // Separately, check for duplicate arugment names, since they aren't Parsed.Objects,
  284. // so have to be checked independently.
  285. for (int i = 0; i < arguments.Count; i++) {
  286. for (int j = i + 1; j < arguments.Count; j++) {
  287. if (arguments [i].identifier?.name == arguments [j].identifier?.name) {
  288. Error ("Multiple arguments with the same name: '" + arguments [i].identifier + "'");
  289. }
  290. }
  291. }
  292. }
  293. // Check naming collisions for knots and stitches
  294. if (flowLevel != FlowLevel.Story) {
  295. // Weave points aren't FlowBases, so this will only be knot or stitch
  296. var symbolType = flowLevel == FlowLevel.Knot ? Story.SymbolType.Knot : Story.SymbolType.SubFlowAndWeave;
  297. context.CheckForNamingCollisions (this, identifier, symbolType);
  298. }
  299. }
  300. void CheckForDisallowedFunctionFlowControl()
  301. {
  302. if (!(this is Knot)) {
  303. Error ("Functions cannot be stitches - i.e. they should be defined as '== function myFunc ==' rather than public to another knot.");
  304. }
  305. // Not allowed sub-flows
  306. foreach (var subFlowAndName in _subFlowsByName) {
  307. var name = subFlowAndName.Key;
  308. var subFlow = subFlowAndName.Value;
  309. Error ("Functions may not contain stitches, but saw '"+name+"' within the function '"+this.identifier+"'", subFlow);
  310. }
  311. var allDiverts = _rootWeave.FindAll<Divert> ();
  312. foreach (var divert in allDiverts) {
  313. if( !divert.isFunctionCall && !(divert.parent is DivertTarget) )
  314. Error ("Functions may not contain diverts, but saw '"+divert.ToString()+"'", divert);
  315. }
  316. var allChoices = _rootWeave.FindAll<Choice> ();
  317. foreach (var choice in allChoices) {
  318. Error ("Functions may not contain choices, but saw '"+choice.ToString()+"'", choice);
  319. }
  320. }
  321. void WarningInTermination(Parsed.Object terminatingObject)
  322. {
  323. string message = "Apparent loose end exists where the flow runs out. Do you need a '-> DONE' statement, choice or divert?";
  324. if (terminatingObject.parent == _rootWeave && _firstChildFlow) {
  325. message = message + " Note that if you intend to enter '"+_firstChildFlow.identifier+"' next, you need to divert to it explicitly.";
  326. }
  327. var terminatingDivert = terminatingObject as Divert;
  328. if (terminatingDivert && terminatingDivert.isTunnel) {
  329. message = message + " When final tunnel to '"+terminatingDivert.target+" ->' returns it won't have anywhere to go.";
  330. }
  331. Warning (message, terminatingObject);
  332. }
  333. protected Dictionary<string, FlowBase> subFlowsByName {
  334. get {
  335. return _subFlowsByName;
  336. }
  337. }
  338. public override string typeName {
  339. get {
  340. if (isFunction) return "Function";
  341. else return flowLevel.ToString ();
  342. }
  343. }
  344. public override string ToString ()
  345. {
  346. return typeName+" '" + identifier + "'";
  347. }
  348. Weave _rootWeave;
  349. Dictionary<string, FlowBase> _subFlowsByName;
  350. Runtime.Divert _startingSubFlowDivert;
  351. Runtime.Object _startingSubFlowRuntime;
  352. FlowBase _firstChildFlow;
  353. }
  354. }