namespace Ink.Parsed { public class DivertTarget : Expression { public Divert divert; public DivertTarget (Divert divert) { this.divert = AddContent(divert); } public override void GenerateIntoContainer (Runtime.Container container) { divert.GenerateRuntimeObject(); _runtimeDivert = (Runtime.Divert) divert.runtimeDivert; _runtimeDivertTargetValue = new Runtime.DivertTargetValue (); container.AddContent (_runtimeDivertTargetValue); } public override void ResolveReferences (Story context) { base.ResolveReferences (context); if( divert.isDone || divert.isEnd ) { Error("Can't Can't use -> DONE or -> END as variable divert targets", this); return; } Parsed.Object usageContext = this; while (usageContext && usageContext is Expression) { bool badUsage = false; bool foundUsage = false; var usageParent = usageContext.parent; if (usageParent is BinaryExpression) { // Only allowed to compare for equality var binaryExprParent = usageParent as BinaryExpression; if (binaryExprParent.opName != "==" && binaryExprParent.opName != "!=") { badUsage = true; } else { if (!(binaryExprParent.leftExpression is DivertTarget || binaryExprParent.leftExpression is VariableReference)) { badUsage = true; } if (!(binaryExprParent.rightExpression is DivertTarget || binaryExprParent.rightExpression is VariableReference)) { badUsage = true; } } foundUsage = true; } else if( usageParent is FunctionCall ) { var funcCall = usageParent as FunctionCall; if( !funcCall.isTurnsSince && !funcCall.isReadCount ) { badUsage = true; } foundUsage = true; } else if (usageParent is Expression) { badUsage = true; foundUsage = true; } else if (usageParent is MultipleConditionExpression) { badUsage = true; foundUsage = true; } else if (usageParent is Choice && ((Choice)usageParent).condition == usageContext) { badUsage = true; foundUsage = true; } else if (usageParent is Conditional || usageParent is ConditionalSingleBranch) { badUsage = true; foundUsage = true; } if (badUsage) { Error ("Can't use a divert target like that. Did you intend to call '" + divert.target + "' as a function: likeThis(), or check the read count: likeThis, with no arrows?", this); } if (foundUsage) break; usageContext = usageParent; } // Example ink for this case: // // VAR x = -> blah // // ...which means that "blah" is expected to be a literal stitch target rather // than a variable name. We can't really intelligently recover from this (e.g. if blah happens to // contain a divert target itself) since really we should be generating a variable reference // rather than a concrete DivertTarget, so we list it as an error. if (_runtimeDivert.hasVariableTarget) Error ("Since '"+divert.target.dotSeparatedComponents+"' is a variable, it shouldn't be preceded by '->' here."); // Main resolve _runtimeDivertTargetValue.targetPath = _runtimeDivert.targetPath; // Tell hard coded (yet variable) divert targets that they also need to be counted // TODO: Only detect DivertTargets that are values rather than being used directly for // read or turn counts. Should be able to detect this by looking for other uses of containerForCounting var targetContent = this.divert.targetContent; if (targetContent != null ) { var target = targetContent.containerForCounting; if (target != null) { // Purpose is known: used directly in TURNS_SINCE(-> divTarg) var parentFunc = this.parent as FunctionCall; if( parentFunc && parentFunc.isTurnsSince ) { target.turnIndexShouldBeCounted = true; } // Unknown purpose, count everything else { target.visitsShouldBeCounted = true; target.turnIndexShouldBeCounted = true; } } // Unfortunately not possible: // https://github.com/inkle/ink/issues/538 // // VAR func = -> double // // === function double(ref x) // ~ x = x * 2 // // Because when generating the parameters for a function // to be called, it needs to know ahead of time when // compiling whether to pass a variable reference or value. // var targetFlow = (targetContent as FlowBase); if (targetFlow != null && targetFlow.arguments != null) { foreach(var arg in targetFlow.arguments) { if(arg.isByReference) { Error("Can't store a divert target to a knot or function that has by-reference arguments ('"+targetFlow.identifier+"' has 'ref "+arg.identifier+"')."); } } } } } // Equals override necessary in order to check for CONST multiple definition equality public override bool Equals (object obj) { var otherDivTarget = obj as DivertTarget; if (otherDivTarget == null) return false; var targetStr = this.divert.target.dotSeparatedComponents; var otherTargetStr = otherDivTarget.divert.target.dotSeparatedComponents; return targetStr.Equals (otherTargetStr); } public override int GetHashCode () { var targetStr = this.divert.target.dotSeparatedComponents; return targetStr.GetHashCode (); } Runtime.DivertTargetValue _runtimeDivertTargetValue; Runtime.Divert _runtimeDivert; } }