Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Ink / InkLibs / InkCompiler / ParsedHierarchy / Divert.cs
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. namespace Ink.Parsed
  4. {
  5. public class Divert : Parsed.Object
  6. {
  7. public Parsed.Path target { get; protected set; }
  8. public Parsed.Object targetContent { get; protected set; }
  9. public List<Expression> arguments { get; protected set; }
  10. public Runtime.Divert runtimeDivert { get; protected set; }
  11. public bool isFunctionCall { get; set; }
  12. public bool isEmpty { get; set; }
  13. public bool isTunnel { get; set; }
  14. public bool isThread { get; set; }
  15. public bool isEnd {
  16. get {
  17. return target != null && target.dotSeparatedComponents == "END";
  18. }
  19. }
  20. public bool isDone {
  21. get {
  22. return target != null && target.dotSeparatedComponents == "DONE";
  23. }
  24. }
  25. public Divert (Parsed.Path target, List<Expression> arguments = null)
  26. {
  27. this.target = target;
  28. this.arguments = arguments;
  29. if (arguments != null) {
  30. AddContent (arguments.Cast<Parsed.Object> ().ToList ());
  31. }
  32. }
  33. public Divert (Parsed.Object targetContent)
  34. {
  35. this.targetContent = targetContent;
  36. }
  37. public override Runtime.Object GenerateRuntimeObject ()
  38. {
  39. // End = end flow immediately
  40. // Done = return from thread or instruct the flow that it's safe to exit
  41. if (isEnd) {
  42. return Runtime.ControlCommand.End ();
  43. }
  44. if (isDone) {
  45. return Runtime.ControlCommand.Done ();
  46. }
  47. runtimeDivert = new Runtime.Divert ();
  48. // Normally we resolve the target content during the
  49. // Resolve phase, since we expect all runtime objects to
  50. // be available in order to find the final runtime path for
  51. // the destination. However, we need to resolve the target
  52. // (albeit without the runtime target) early so that
  53. // we can get information about the arguments - whether
  54. // they're by reference - since it affects the code we
  55. // generate here.
  56. ResolveTargetContent ();
  57. CheckArgumentValidity ();
  58. // Passing arguments to the knot
  59. bool requiresArgCodeGen = arguments != null && arguments.Count > 0;
  60. if ( requiresArgCodeGen || isFunctionCall || isTunnel || isThread ) {
  61. var container = new Runtime.Container ();
  62. // Generate code for argument evaluation
  63. // This argument generation is coded defensively - it should
  64. // attempt to generate the code for all the parameters, even if
  65. // they don't match the expected arguments. This is so that the
  66. // parameter objects themselves are generated correctly and don't
  67. // get into a state of attempting to resolve references etc
  68. // without being generated.
  69. if (requiresArgCodeGen) {
  70. // Function calls already in an evaluation context
  71. if (!isFunctionCall) {
  72. container.AddContent (Runtime.ControlCommand.EvalStart());
  73. }
  74. List<FlowBase.Argument> targetArguments = null;
  75. if( targetContent )
  76. targetArguments = (targetContent as FlowBase).arguments;
  77. for (var i = 0; i < arguments.Count; ++i) {
  78. Expression argToPass = arguments [i];
  79. FlowBase.Argument argExpected = null;
  80. if( targetArguments != null && i < targetArguments.Count )
  81. argExpected = targetArguments [i];
  82. // Pass by reference: argument needs to be a variable reference
  83. if (argExpected != null && argExpected.isByReference) {
  84. var varRef = argToPass as VariableReference;
  85. if (varRef == null) {
  86. Error ("Expected variable name to pass by reference to 'ref " + argExpected.identifier + "' but saw " + argToPass.ToString ());
  87. break;
  88. }
  89. // Check that we're not attempting to pass a read count by reference
  90. var targetPath = new Path(varRef.pathIdentifiers);
  91. Parsed.Object targetForCount = targetPath.ResolveFromContext (this);
  92. if (targetForCount != null) {
  93. Error ("can't pass a read count by reference. '" + targetPath.dotSeparatedComponents+"' is a knot/stitch/label, but '"+target.dotSeparatedComponents+"' requires the name of a VAR to be passed.");
  94. break;
  95. }
  96. var varPointer = new Runtime.VariablePointerValue (varRef.name);
  97. container.AddContent (varPointer);
  98. }
  99. // Normal value being passed: evaluate it as normal
  100. else {
  101. argToPass.GenerateIntoContainer (container);
  102. }
  103. }
  104. // Function calls were already in an evaluation context
  105. if (!isFunctionCall) {
  106. container.AddContent (Runtime.ControlCommand.EvalEnd());
  107. }
  108. }
  109. // Starting a thread? A bit like a push to the call stack below... but not.
  110. // It sort of puts the call stack on a thread stack (argh!) - forks the full flow.
  111. if (isThread) {
  112. container.AddContent(Runtime.ControlCommand.StartThread());
  113. }
  114. // If this divert is a function call, tunnel, we push to the call stack
  115. // so we can return again
  116. else if (isFunctionCall || isTunnel) {
  117. runtimeDivert.pushesToStack = true;
  118. runtimeDivert.stackPushType = isFunctionCall ? Runtime.PushPopType.Function : Runtime.PushPopType.Tunnel;
  119. }
  120. // Jump into the "function" (knot/stitch)
  121. container.AddContent (runtimeDivert);
  122. return container;
  123. }
  124. // Simple divert
  125. else {
  126. return runtimeDivert;
  127. }
  128. }
  129. // When the divert is to a target that's actually a variable name
  130. // rather than an explicit knot/stitch name, try interpretting it
  131. // as such by getting the variable name.
  132. public string PathAsVariableName()
  133. {
  134. return target.firstComponent;
  135. }
  136. void ResolveTargetContent()
  137. {
  138. if (isEmpty || isEnd) {
  139. return;
  140. }
  141. if (targetContent == null) {
  142. // Is target of this divert a variable name that will be de-referenced
  143. // at runtime? If so, there won't be any further reference resolution
  144. // we can do at this point.
  145. var variableTargetName = PathAsVariableName ();
  146. if (variableTargetName != null) {
  147. var flowBaseScope = ClosestFlowBase ();
  148. var resolveResult = flowBaseScope.ResolveVariableWithName (variableTargetName, fromNode: this);
  149. if (resolveResult.found) {
  150. // Make sure that the flow was typed correctly, given that we know that this
  151. // is meant to be a divert target
  152. if (resolveResult.isArgument) {
  153. var argument = resolveResult.ownerFlow.arguments.Where (a => a.identifier.name == variableTargetName).First();
  154. if ( !argument.isDivertTarget ) {
  155. Error ("Since '" + argument.identifier + "' is used as a variable divert target (on "+this.debugMetadata+"), it should be marked as: -> " + argument.identifier, resolveResult.ownerFlow);
  156. }
  157. }
  158. runtimeDivert.variableDivertName = variableTargetName;
  159. return;
  160. }
  161. }
  162. targetContent = target.ResolveFromContext (this);
  163. }
  164. }
  165. public override void ResolveReferences(Story context)
  166. {
  167. if (isEmpty || isEnd || isDone) {
  168. return;
  169. }
  170. if (targetContent) {
  171. runtimeDivert.targetPath = targetContent.runtimePath;
  172. }
  173. // Resolve children (the arguments)
  174. base.ResolveReferences (context);
  175. // May be null if it's a built in function (e.g. TURNS_SINCE)
  176. // or if it's a variable target.
  177. var targetFlow = targetContent as FlowBase;
  178. if (targetFlow) {
  179. if (!targetFlow.isFunction && this.isFunctionCall) {
  180. base.Error (targetFlow.identifier + " hasn't been marked as a function, but it's being called as one. Do you need to delcare the knot as '== function " + targetFlow.identifier + " =='?");
  181. } else if (targetFlow.isFunction && !this.isFunctionCall && !(this.parent is DivertTarget)) {
  182. base.Error (targetFlow.identifier + " can't be diverted to. It can only be called as a function since it's been marked as such: '" + targetFlow.identifier + "(...)'");
  183. }
  184. }
  185. // Check validity of target content
  186. bool targetWasFound = targetContent != null;
  187. bool isBuiltIn = false;
  188. bool isExternal = false;
  189. if (target.numberOfComponents == 1 ) {
  190. // BuiltIn means TURNS_SINCE, CHOICE_COUNT, RANDOM or SEED_RANDOM
  191. isBuiltIn = FunctionCall.IsBuiltIn (target.firstComponent);
  192. // Client-bound function?
  193. isExternal = context.IsExternal (target.firstComponent);
  194. if (isBuiltIn || isExternal) {
  195. if (!isFunctionCall) {
  196. base.Error (target.firstComponent + " must be called as a function: ~ " + target.firstComponent + "()");
  197. }
  198. if (isExternal) {
  199. runtimeDivert.isExternal = true;
  200. if( arguments != null )
  201. runtimeDivert.externalArgs = arguments.Count;
  202. runtimeDivert.pushesToStack = false;
  203. runtimeDivert.targetPath = new Runtime.Path (this.target.firstComponent);
  204. CheckExternalArgumentValidity (context);
  205. }
  206. return;
  207. }
  208. }
  209. // Variable target?
  210. if (runtimeDivert.variableDivertName != null) {
  211. return;
  212. }
  213. if( !targetWasFound && !isBuiltIn && !isExternal )
  214. Error ("target not found: '" + target + "'");
  215. }
  216. // Returns false if there's an error
  217. void CheckArgumentValidity()
  218. {
  219. if (isEmpty)
  220. return;
  221. // Argument passing: Check for errors in number of arguments
  222. var numArgs = 0;
  223. if (arguments != null && arguments.Count > 0)
  224. numArgs = arguments.Count;
  225. // Missing content?
  226. // Can't check arguments properly. It'll be due to some
  227. // other error though, so although there's a problem and
  228. // we report false, we don't need to report a specific error.
  229. // It may also be because it's a valid call to an external
  230. // function, that we check at the resolve stage.
  231. if (targetContent == null) {
  232. return;
  233. }
  234. FlowBase targetFlow = targetContent as FlowBase;
  235. // No error, crikey!
  236. if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters)) {
  237. return;
  238. }
  239. if (targetFlow == null && numArgs > 0) {
  240. Error ("target needs to be a knot or stitch in order to pass arguments");
  241. return;
  242. }
  243. if (targetFlow.arguments == null && numArgs > 0) {
  244. Error ("target (" + targetFlow.name + ") doesn't take parameters");
  245. return;
  246. }
  247. if( this.parent is DivertTarget ) {
  248. if (numArgs > 0)
  249. Error ("can't store arguments in a divert target variable");
  250. return;
  251. }
  252. var paramCount = targetFlow.arguments.Count;
  253. if (paramCount != numArgs) {
  254. string butClause;
  255. if (numArgs == 0) {
  256. butClause = "but there weren't any passed to it";
  257. } else if (numArgs < paramCount) {
  258. butClause = "but only got " + numArgs;
  259. } else {
  260. butClause = "but got " + numArgs;
  261. }
  262. Error ("to '" + targetFlow.identifier + "' requires " + paramCount + " arguments, "+butClause);
  263. return;
  264. }
  265. // Light type-checking for divert target arguments
  266. for (int i = 0; i < paramCount; ++i) {
  267. FlowBase.Argument flowArg = targetFlow.arguments [i];
  268. Parsed.Expression divArgExpr = arguments [i];
  269. // Expecting a divert target as an argument, let's do some basic type checking
  270. if (flowArg.isDivertTarget) {
  271. // Not passing a divert target or any kind of variable reference?
  272. var varRef = divArgExpr as VariableReference;
  273. if (!(divArgExpr is DivertTarget) && varRef == null ) {
  274. Error ("Target '" + targetFlow.identifier + "' expects a divert target for the parameter named -> " + flowArg.identifier + " but saw " + divArgExpr, divArgExpr);
  275. }
  276. // Passing 'a' instead of '-> a'?
  277. // i.e. read count instead of divert target
  278. else if (varRef != null) {
  279. // Unfortunately have to manually resolve here since we're still in code gen
  280. var knotCountPath = new Path(varRef.pathIdentifiers);
  281. Parsed.Object targetForCount = knotCountPath.ResolveFromContext (varRef);
  282. if (targetForCount != null) {
  283. Error ("Passing read count of '" + knotCountPath.dotSeparatedComponents + "' instead of a divert target. You probably meant '" + knotCountPath + "'");
  284. }
  285. }
  286. }
  287. }
  288. if (targetFlow == null) {
  289. Error ("Can't call as a function or with arguments unless it's a knot or stitch");
  290. return;
  291. }
  292. return;
  293. }
  294. void CheckExternalArgumentValidity(Story context)
  295. {
  296. string externalName = target.firstComponent;
  297. ExternalDeclaration external = null;
  298. var found = context.externals.TryGetValue(externalName, out external);
  299. System.Diagnostics.Debug.Assert (found, "external not found");
  300. int externalArgCount = external.argumentNames.Count;
  301. int ownArgCount = 0;
  302. if (arguments != null) {
  303. ownArgCount = arguments.Count;
  304. }
  305. if (ownArgCount != externalArgCount) {
  306. Error ("incorrect number of arguments sent to external function '" + externalName + "'. Expected " + externalArgCount + " but got " + ownArgCount);
  307. }
  308. }
  309. public override void Error (string message, Object source = null, bool isWarning = false)
  310. {
  311. // Could be getting an error from a nested Divert
  312. if (source != this && source) {
  313. base.Error (message, source);
  314. return;
  315. }
  316. if (isFunctionCall) {
  317. base.Error ("Function call " + message, source, isWarning);
  318. } else {
  319. base.Error ("Divert " + message, source, isWarning);
  320. }
  321. }
  322. public override string ToString ()
  323. {
  324. if (target != null)
  325. return target.ToString ();
  326. else
  327. return "-> <empty divert>";
  328. }
  329. }
  330. }