- using System.Collections.Generic;
- using System.Linq;
- namespace Ink.Parsed
- {
- public class Divert : Parsed.Object
- {
- public Parsed.Path target { get; protected set; }
- public Parsed.Object targetContent { get; protected set; }
- public List<Expression> arguments { get; protected set; }
- public Runtime.Divert runtimeDivert { get; protected set; }
- public bool isFunctionCall { get; set; }
- public bool isEmpty { get; set; }
- public bool isTunnel { get; set; }
- public bool isThread { get; set; }
- public bool isEnd {
- get {
- return target != null && target.dotSeparatedComponents == "END";
- }
- }
- public bool isDone {
- get {
- return target != null && target.dotSeparatedComponents == "DONE";
- }
- }
- public Divert (Parsed.Path target, List<Expression> arguments = null)
- {
- this.target = target;
- this.arguments = arguments;
- if (arguments != null) {
- AddContent (arguments.Cast<Parsed.Object> ().ToList ());
- }
- }
- public Divert (Parsed.Object targetContent)
- {
- this.targetContent = targetContent;
- }
- public override Runtime.Object GenerateRuntimeObject ()
- {
-
-
- if (isEnd) {
- return Runtime.ControlCommand.End ();
- }
- if (isDone) {
- return Runtime.ControlCommand.Done ();
- }
- runtimeDivert = new Runtime.Divert ();
-
-
-
-
-
-
-
-
- ResolveTargetContent ();
- CheckArgumentValidity ();
-
- bool requiresArgCodeGen = arguments != null && arguments.Count > 0;
- if ( requiresArgCodeGen || isFunctionCall || isTunnel || isThread ) {
- var container = new Runtime.Container ();
-
-
-
-
-
-
-
- if (requiresArgCodeGen) {
-
- if (!isFunctionCall) {
- container.AddContent (Runtime.ControlCommand.EvalStart());
- }
- List<FlowBase.Argument> targetArguments = null;
- if( targetContent )
- targetArguments = (targetContent as FlowBase).arguments;
- for (var i = 0; i < arguments.Count; ++i) {
- Expression argToPass = arguments [i];
- FlowBase.Argument argExpected = null;
- if( targetArguments != null && i < targetArguments.Count )
- argExpected = targetArguments [i];
-
- if (argExpected != null && argExpected.isByReference) {
- var varRef = argToPass as VariableReference;
- if (varRef == null) {
- Error ("Expected variable name to pass by reference to 'ref " + argExpected.identifier + "' but saw " + argToPass.ToString ());
- break;
- }
-
- var targetPath = new Path(varRef.pathIdentifiers);
- Parsed.Object targetForCount = targetPath.ResolveFromContext (this);
- if (targetForCount != null) {
- 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.");
- break;
- }
- var varPointer = new Runtime.VariablePointerValue (varRef.name);
- container.AddContent (varPointer);
- }
-
- else {
- argToPass.GenerateIntoContainer (container);
- }
- }
-
- if (!isFunctionCall) {
- container.AddContent (Runtime.ControlCommand.EvalEnd());
- }
- }
-
-
- if (isThread) {
- container.AddContent(Runtime.ControlCommand.StartThread());
- }
-
-
- else if (isFunctionCall || isTunnel) {
- runtimeDivert.pushesToStack = true;
- runtimeDivert.stackPushType = isFunctionCall ? Runtime.PushPopType.Function : Runtime.PushPopType.Tunnel;
- }
-
- container.AddContent (runtimeDivert);
- return container;
- }
-
- else {
- return runtimeDivert;
- }
- }
-
-
-
- public string PathAsVariableName()
- {
- return target.firstComponent;
- }
- void ResolveTargetContent()
- {
- if (isEmpty || isEnd) {
- return;
- }
- if (targetContent == null) {
-
-
-
- var variableTargetName = PathAsVariableName ();
- if (variableTargetName != null) {
- var flowBaseScope = ClosestFlowBase ();
- var resolveResult = flowBaseScope.ResolveVariableWithName (variableTargetName, fromNode: this);
- if (resolveResult.found) {
-
-
- if (resolveResult.isArgument) {
- var argument = resolveResult.ownerFlow.arguments.Where (a => a.identifier.name == variableTargetName).First();
- if ( !argument.isDivertTarget ) {
- Error ("Since '" + argument.identifier + "' is used as a variable divert target (on "+this.debugMetadata+"), it should be marked as: -> " + argument.identifier, resolveResult.ownerFlow);
- }
- }
- runtimeDivert.variableDivertName = variableTargetName;
- return;
- }
- }
- targetContent = target.ResolveFromContext (this);
- }
- }
- public override void ResolveReferences(Story context)
- {
- if (isEmpty || isEnd || isDone) {
- return;
- }
- if (targetContent) {
- runtimeDivert.targetPath = targetContent.runtimePath;
- }
-
- base.ResolveReferences (context);
-
-
- var targetFlow = targetContent as FlowBase;
- if (targetFlow) {
- if (!targetFlow.isFunction && this.isFunctionCall) {
- 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 + " =='?");
- } else if (targetFlow.isFunction && !this.isFunctionCall && !(this.parent is DivertTarget)) {
- 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 + "(...)'");
- }
- }
-
- bool targetWasFound = targetContent != null;
- bool isBuiltIn = false;
- bool isExternal = false;
- if (target.numberOfComponents == 1 ) {
-
- isBuiltIn = FunctionCall.IsBuiltIn (target.firstComponent);
-
- isExternal = context.IsExternal (target.firstComponent);
- if (isBuiltIn || isExternal) {
- if (!isFunctionCall) {
- base.Error (target.firstComponent + " must be called as a function: ~ " + target.firstComponent + "()");
- }
- if (isExternal) {
- runtimeDivert.isExternal = true;
- if( arguments != null )
- runtimeDivert.externalArgs = arguments.Count;
- runtimeDivert.pushesToStack = false;
- runtimeDivert.targetPath = new Runtime.Path (this.target.firstComponent);
- CheckExternalArgumentValidity (context);
- }
- return;
- }
- }
-
- if (runtimeDivert.variableDivertName != null) {
- return;
- }
- if( !targetWasFound && !isBuiltIn && !isExternal )
- Error ("target not found: '" + target + "'");
- }
-
- void CheckArgumentValidity()
- {
- if (isEmpty)
- return;
-
- var numArgs = 0;
- if (arguments != null && arguments.Count > 0)
- numArgs = arguments.Count;
-
-
-
-
-
-
- if (targetContent == null) {
- return;
- }
- FlowBase targetFlow = targetContent as FlowBase;
-
- if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters)) {
- return;
- }
- if (targetFlow == null && numArgs > 0) {
- Error ("target needs to be a knot or stitch in order to pass arguments");
- return;
- }
- if (targetFlow.arguments == null && numArgs > 0) {
- Error ("target (" + targetFlow.name + ") doesn't take parameters");
- return;
- }
- if( this.parent is DivertTarget ) {
- if (numArgs > 0)
- Error ("can't store arguments in a divert target variable");
- return;
- }
- var paramCount = targetFlow.arguments.Count;
- if (paramCount != numArgs) {
- string butClause;
- if (numArgs == 0) {
- butClause = "but there weren't any passed to it";
- } else if (numArgs < paramCount) {
- butClause = "but only got " + numArgs;
- } else {
- butClause = "but got " + numArgs;
- }
- Error ("to '" + targetFlow.identifier + "' requires " + paramCount + " arguments, "+butClause);
- return;
- }
-
- for (int i = 0; i < paramCount; ++i) {
- FlowBase.Argument flowArg = targetFlow.arguments [i];
- Parsed.Expression divArgExpr = arguments [i];
-
- if (flowArg.isDivertTarget) {
-
- var varRef = divArgExpr as VariableReference;
- if (!(divArgExpr is DivertTarget) && varRef == null ) {
- Error ("Target '" + targetFlow.identifier + "' expects a divert target for the parameter named -> " + flowArg.identifier + " but saw " + divArgExpr, divArgExpr);
- }
-
-
- else if (varRef != null) {
-
- var knotCountPath = new Path(varRef.pathIdentifiers);
- Parsed.Object targetForCount = knotCountPath.ResolveFromContext (varRef);
- if (targetForCount != null) {
- Error ("Passing read count of '" + knotCountPath.dotSeparatedComponents + "' instead of a divert target. You probably meant '" + knotCountPath + "'");
- }
- }
- }
- }
- if (targetFlow == null) {
- Error ("Can't call as a function or with arguments unless it's a knot or stitch");
- return;
- }
- return;
- }
- void CheckExternalArgumentValidity(Story context)
- {
- string externalName = target.firstComponent;
- ExternalDeclaration external = null;
- var found = context.externals.TryGetValue(externalName, out external);
- System.Diagnostics.Debug.Assert (found, "external not found");
- int externalArgCount = external.argumentNames.Count;
- int ownArgCount = 0;
- if (arguments != null) {
- ownArgCount = arguments.Count;
- }
- if (ownArgCount != externalArgCount) {
- Error ("incorrect number of arguments sent to external function '" + externalName + "'. Expected " + externalArgCount + " but got " + ownArgCount);
- }
- }
- public override void Error (string message, Object source = null, bool isWarning = false)
- {
-
- if (source != this && source) {
- base.Error (message, source);
- return;
- }
- if (isFunctionCall) {
- base.Error ("Function call " + message, source, isWarning);
- } else {
- base.Error ("Divert " + message, source, isWarning);
- }
- }
- public override string ToString ()
- {
- if (target != null)
- return target.ToString ();
- else
- return "-> <empty divert>";
- }
- }
- }