void AddDivertToResolve(Runtime.Divert divert, Runtime.Object targetContent) { _sequenceDivertsToResove.Add(new SequenceDivertToResolve() { divert = divert, targetContent = targetContent }); }
public override void GenerateIntoContainer(Runtime.Container container) { divert.GenerateRuntimeObject(); _runtimeDivert = (Runtime.Divert) divert.runtimeDivert; _runtimeDivertTargetValue = new Runtime.DivertTargetValue (); if (divert.arguments != null && divert.arguments.Count > 0) { Error ("Can't use a divert target as a variable if it has parameters"); return; } container.AddContent (_runtimeDivertTargetValue); }
// Found gather point: // - gather any loose ends // - set the gather as the main container to dump new content in void AddRuntimeForGather(Gather gather) { // Determine whether this Gather should be auto-entered: // - It is auto-entered if there were no choices in the last section // - A section is "since the previous gather" - so reset now bool autoEnter = !hasSeenChoiceInSection; hasSeenChoiceInSection = false; var gatherContainer = gather.runtimeContainer; if (gather.name == null) { // Use disallowed character so it's impossible to have a name collision gatherContainer.name = "g-" + _unnamedGatherCount; _unnamedGatherCount++; } // Auto-enter: include in main content if (autoEnter) { currentContainer.AddContent (gatherContainer); } // Don't auto-enter: // Add this gather to the main content, but only accessible // by name so that it isn't stepped into automatically, but only via // a divert from a loose end. else { currentContainer.AddToNamedContentOnly (gatherContainer); } // Consume loose ends: divert them to this gather foreach (Parsed.Object looseEnd in looseEnds) { // Skip gather loose ends that are at the same level // since they'll be handled by the auto-enter code below // that only jumps into the gather if (current runtime choices == 0) if (looseEnd is Gather) { var prevGather = (Gather)looseEnd; if (prevGather.indentationDepth == gather.indentationDepth) { continue; } } Runtime.Divert divert = null; if (looseEnd is Parsed.Divert) { divert = (Runtime.Divert) looseEnd.runtimeObject; } else { var looseWeavePoint = looseEnd as IWeavePoint; var looseChoice = looseWeavePoint as Parsed.Choice; if (looseChoice && looseChoice.hasTerminatingDivert) { divert = looseChoice.terminatingDivert.runtimeObject as Runtime.Divert; } else { divert = new Runtime.Divert (); looseWeavePoint.runtimeContainer.AddContent (divert); } } // Pass back knowledge of this loose end being diverted // to the FlowBase so that it can maintain a list of them, // and resolve the divert references later gatherPointsToResolve.Add (new GatherPointToResolve{ divert = divert, targetRuntimeObj = gatherContainer }); } looseEnds.Clear (); // Replace the current container itself currentContainer = gatherContainer; }
// Generate runtime code that looks like: // // chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping" // if chosenIndex == 0, divert to s0 // if chosenIndex == 1, divert to s1 [etc] // // - s0: // <content for sequence element> // divert to no-op // - s1: // <content for sequence element> // divert to no-op // - s2: // empty branch if using "once" // divert to no-op // // no-op // public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); container.visitsShouldBeCounted = true; container.countingAtStartOnly = true; _sequenceDivertsToResove = new List <SequenceDivertToResolve> (); // Get sequence read count container.AddContent(Runtime.ControlCommand.EvalStart()); container.AddContent(Runtime.ControlCommand.VisitIndex()); bool once = (sequenceType & SequenceType.Once) > 0; bool cycle = (sequenceType & SequenceType.Cycle) > 0; bool stopping = (sequenceType & SequenceType.Stopping) > 0; bool shuffle = (sequenceType & SequenceType.Shuffle) > 0; var seqBranchCount = sequenceElements.Count; if (once) { seqBranchCount++; } // Chosen sequence index: // - Stopping: take the MIN(read count, num elements - 1) // - Once: take the MIN(read count, num elements) // (the last one being empty) if (stopping || once) { //var limit = stopping ? seqBranchCount-1 : seqBranchCount; container.AddContent(new Runtime.IntValue(seqBranchCount - 1)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("MIN")); } // - Cycle: take (read count % num elements) else if (cycle) { container.AddContent(new Runtime.IntValue(sequenceElements.Count)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("%")); } // Shuffle if (shuffle) { // Create point to return to when sequence is complete var postShuffleNoOp = Runtime.ControlCommand.NoOp(); // When visitIndex == lastIdx, we skip the shuffle if (once || stopping) { // if( visitIndex == lastIdx ) -> skipShuffle int lastIdx = stopping ? sequenceElements.Count - 1 : sequenceElements.Count; container.AddContent(Runtime.ControlCommand.Duplicate()); container.AddContent(new Runtime.IntValue(lastIdx)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); var skipShuffleDivert = new Runtime.Divert(); skipShuffleDivert.isConditional = true; container.AddContent(skipShuffleDivert); AddDivertToResolve(skipShuffleDivert, postShuffleNoOp); } // This one's a bit more complex! Choose the index at runtime. var elementCountToShuffle = sequenceElements.Count; if (stopping) { elementCountToShuffle--; } container.AddContent(new Runtime.IntValue(elementCountToShuffle)); container.AddContent(Runtime.ControlCommand.SequenceShuffleIndex()); if (once || stopping) { container.AddContent(postShuffleNoOp); } } container.AddContent(Runtime.ControlCommand.EvalEnd()); // Create point to return to when sequence is complete var postSequenceNoOp = Runtime.ControlCommand.NoOp(); // Each of the main sequence branches, and one extra empty branch if // we have a "once" sequence. for (var elIndex = 0; elIndex < seqBranchCount; elIndex++) { // This sequence element: // if( chosenIndex == this index ) divert to this sequence element // duplicate chosen sequence index, since it'll be consumed by "==" container.AddContent(Runtime.ControlCommand.EvalStart()); container.AddContent(Runtime.ControlCommand.Duplicate()); container.AddContent(new Runtime.IntValue(elIndex)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); container.AddContent(Runtime.ControlCommand.EvalEnd()); // Divert branch for this sequence element var sequenceDivert = new Runtime.Divert(); sequenceDivert.isConditional = true; container.AddContent(sequenceDivert); Runtime.Container contentContainerForSequenceBranch; // Generate content for this sequence element if (elIndex < sequenceElements.Count) { var el = sequenceElements[elIndex]; contentContainerForSequenceBranch = (Runtime.Container)el.runtimeObject; } // Final empty branch for "once" sequences else { contentContainerForSequenceBranch = new Runtime.Container(); } contentContainerForSequenceBranch.name = "s" + elIndex; contentContainerForSequenceBranch.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0); // When sequence element is complete, divert back to end of sequence var seqBranchCompleteDivert = new Runtime.Divert(); contentContainerForSequenceBranch.AddContent(seqBranchCompleteDivert); container.AddToNamedContentOnly(contentContainerForSequenceBranch); // Save the diverts for reference resolution later (in ResolveReferences) AddDivertToResolve(sequenceDivert, contentContainerForSequenceBranch); AddDivertToResolve(seqBranchCompleteDivert, postSequenceNoOp); } container.AddContent(postSequenceNoOp); return(container); }
public override Runtime.Object GenerateRuntimeObject () { // End = end flow immediately // Done = return from thread or instruct the flow that it's safe to exit if (isEnd) { return Runtime.ControlCommand.End (); } if (isDone) { return Runtime.ControlCommand.Done (); } runtimeDivert = new Runtime.Divert (); // Normally we resolve the target content during the // Resolve phase, since we expect all runtime objects to // be available in order to find the final runtime path for // the destination. However, we need to resolve the target // (albeit without the runtime target) early so that // we can get information about the arguments - whether // they're by reference - since it affects the code we // generate here. ResolveTargetContent (); CheckArgumentValidity (); // Passing arguments to the knot bool requiresArgCodeGen = arguments != null && arguments.Count > 0; if ( requiresArgCodeGen || isFunctionCall || isTunnel || isThread ) { var container = new Runtime.Container (); // Generate code for argument evaluation // This argument generation is coded defensively - it should // attempt to generate the code for all the parameters, even if // they don't match the expected arguments. This is so that the // parameter objects themselves are generated correctly and don't // get into a state of attempting to resolve references etc // without being generated. if (requiresArgCodeGen) { // Function calls already in an evaluation context 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]; // Pass by reference: argument needs to be a variable reference if (argExpected != null && argExpected.isByReference) { var varRef = argToPass as VariableReference; if (varRef == null) { Error ("Expected variable name to pass by reference to 'ref " + argExpected.name + "' but saw " + argToPass.ToString ()); break; } var varPointer = new Runtime.VariablePointerValue (varRef.name); container.AddContent (varPointer); } // Normal value being passed: evaluate it as normal else { argToPass.GenerateIntoContainer (container); } } // Function calls were already in an evaluation context if (!isFunctionCall) { container.AddContent (Runtime.ControlCommand.EvalEnd()); } } // Starting a thread? A bit like a push to the call stack below... but not. // It sort of puts the call stack on a thread stack (argh!) - forks the full flow. if (isThread) { container.AddContent(Runtime.ControlCommand.StartThread()); } // If this divert is a function call, tunnel, we push to the call stack // so we can return again else if (isFunctionCall || isTunnel) { runtimeDivert.pushesToStack = true; runtimeDivert.stackPushType = isFunctionCall ? Runtime.PushPopType.Function : Runtime.PushPopType.Tunnel; } // Jump into the "function" (knot/stitch) container.AddContent (runtimeDivert); return container; } // Simple divert else { return runtimeDivert; } }
public override Runtime.Object GenerateRuntimeObject() { Return foundReturn = null; if (isFunction) { CheckForDisallowedFunctionFlowControl(); } // Non-functon: Make sure knots and stitches don't attempt to use Return statement 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); // Run through content defined for this knot/stitch: // - First of all, any initial content before a sub-stitch // or any weave content is added to the main content container // - The first inner knot/stitch is automatically entered, while // the others are only accessible by an explicit divert // - The exception to this rule is if the knot/stitch takes // parameters, in which case it can't be auto-entered. // - Any Choices and Gathers (i.e. IWeavePoint) found are // processsed by GenerateFlowContent. int contentIdx = 0; while (content != null && contentIdx < content.Count) { Parsed.Object obj = content [contentIdx]; // Inner knots and stitches if (obj is FlowBase) { var childFlow = (FlowBase)obj; var childFlowRuntime = childFlow.runtimeObject; // First inner stitch - automatically step into it // 20/09/2016 - let's not auto step into knots if (contentIdx == 0 && !childFlow.hasParameters && this.flowLevel == FlowLevel.Knot) { _startingSubFlowDivert = new Runtime.Divert(); container.AddContent(_startingSubFlowDivert); _startingSubFlowRuntime = childFlowRuntime; } // Check for duplicate knots/stitches with same name 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); } // Other content (including entire Weaves that were grouped in the constructor) // At the time of writing, all FlowBases have a maximum of one piece of "other content" // and it's always the root Weave else { container.AddContent(obj.runtimeObject); } contentIdx++; } // CHECK FOR FINAL LOOSE ENDS! // Notes: // - Functions don't need to terminate - they just implicitly return // - If return statement was found, don't continue finding warnings for missing control flow, // since it's likely that a return statement has been used instead of a ->-> or something, // or the writer failed to mark the knot as a function. // - _rootWeave may be null if it's a knot that only has stitches if (flowLevel != FlowLevel.Story && !this.isFunction && _rootWeave != null && foundReturn == null) { _rootWeave.ValidateTermination(WarningInTermination); } return(container); }
public override Runtime.Object GenerateRuntimeObject () { // Check whether flow has a loose end: // - Most flows should end in a choice or a divert (otherwise issue a warning) // - Functions need a return, otherwise an implicit one is added ValidateTermination(); if (isFunction) { CheckForDisallowedFunctionFlowControl (); } var container = new Runtime.Container (); container.name = name; // Count visits on all knots and stitches container.visitsShouldBeCounted = true; container.turnIndexShouldBeCounted = true; GenerateArgumentVariableAssignments (container); // Run through content defined for this knot/stitch: // - First of all, any initial content before a sub-stitch // or any weave content is added to the main content container // - The first inner knot/stitch is automatically entered, while // the others are only accessible by an explicit divert // - The exception to this rule is if the knot/stitch takes // parameters, in which case it can't be auto-entered. // - Any Choices and Gathers (i.e. IWeavePoint) found are // processsed by GenerateFlowContent. int contentIdx = 0; while (content != null && contentIdx < content.Count) { Parsed.Object obj = content [contentIdx]; // Inner knots and stitches if (obj is FlowBase) { var childFlow = (FlowBase)obj; var childFlowRuntime = childFlow.runtimeObject; // First inner stitch - automatically step into it // 20/09/2016 - let's not auto step into knots if (contentIdx == 0 && !childFlow.hasParameters && this.flowLevel == FlowLevel.Knot) { _startingSubFlowDivert = new Runtime.Divert (); container.AddContent(_startingSubFlowDivert); _startingSubFlowRuntime = childFlowRuntime; } // Check for duplicate knots/stitches with same name 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); } // Other content (including entire Weaves that were grouped in the constructor) // At the time of writing, all FlowBases have a maximum of one piece of "other content" // and it's always the root Weave else { container.AddContent (obj.runtimeObject); } contentIdx++; } // Tie up final loose ends to the very end if (_rootWeave && _rootWeave.looseEnds != null && _rootWeave.looseEnds.Count > 0) { foreach (var looseEnd in _rootWeave.looseEnds) { if (looseEnd is Divert) { if (_finalLooseEnds == null) { _finalLooseEnds = new List<Ink.Runtime.Divert> (); _finalLooseEndTarget = Runtime.ControlCommand.NoOp (); container.AddContent (_finalLooseEndTarget); } _finalLooseEnds.Add ((Runtime.Divert)looseEnd.runtimeObject); } } } return container; }
public override Runtime.Object GenerateRuntimeObject() { _outerContainer = new Runtime.Container(); // Content names for different types of choice: // * start content [choice only content] inner content // * start content -> divert // * start content // * [choice only content] // Hmm, this structure has become slightly insane! // // [ // EvalStart // assign $r = $r1 -- return target = return label 1 // BeginString // -> s // [(r1)] -- return label 1 (after start content) // EndString // BeginString // ... choice only content // EndEval // Condition expression // choice: -> "c-0" // (s) = [ // start content // -> r -- goto return label 1 or 2 // ] // ] // // in parent's container: (the inner content for the choice) // // (c-0) = [ // EvalStart // assign $r = $r2 -- return target = return label 2 // EndEval // -> s // [(r2)] -- return label 1 (after start content) // inner content // ] // _runtimeChoice = new Runtime.ChoicePoint(onceOnly); _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault; if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent(Runtime.ControlCommand.EvalStart()); } // Start content is put into a named container that's referenced both // when displaying the choice initially, and when generating the text // when the choice is chosen. if (startContent) { // Generate start content and return // - We can't use a function since it uses a call stack element, which would // put temporary values out of scope. Instead we manually divert around. // - $r is a variable divert target contains the return point _returnToR1 = new Runtime.DivertTargetValue(); _outerContainer.AddContent(_returnToR1); var varAssign = new Runtime.VariableAssignment("$r", true); _outerContainer.AddContent(varAssign); // Mark the start of the choice text generation, so that the runtime // knows where to rewind to to extract the content from the output stream. _outerContainer.AddContent(Runtime.ControlCommand.BeginString()); _divertToStartContentOuter = new Runtime.Divert(); _outerContainer.AddContent(_divertToStartContentOuter); // Start content itself in a named container _startContentRuntimeContainer = startContent.GenerateRuntimeObject() as Runtime.Container; _startContentRuntimeContainer.name = "s"; // Effectively, the "return" statement - return to the point specified by $r var varDivert = new Runtime.Divert(); varDivert.variableDivertName = "$r"; _startContentRuntimeContainer.AddContent(varDivert); // Add the container _outerContainer.AddToNamedContentOnly(_startContentRuntimeContainer); // This is the label to return to _r1Label = new Runtime.Container(); _r1Label.name = "$r1"; _outerContainer.AddContent(_r1Label); _outerContainer.AddContent(Runtime.ControlCommand.EndString()); _runtimeChoice.hasStartContent = true; } // Choice only content - mark the start, then generate it directly into the outer container if (choiceOnlyContent) { _outerContainer.AddContent(Runtime.ControlCommand.BeginString()); var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject() as Runtime.Container; _outerContainer.AddContentsOfContainer(choiceOnlyRuntimeContent); _outerContainer.AddContent(Runtime.ControlCommand.EndString()); _runtimeChoice.hasChoiceOnlyContent = true; } // Generate any condition for this choice if (condition) { condition.GenerateIntoContainer(_outerContainer); _runtimeChoice.hasCondition = true; } if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent(Runtime.ControlCommand.EvalEnd()); } // Add choice itself _outerContainer.AddContent(_runtimeChoice); // Container that choice points to for when it's chosen _innerContentContainer = new Runtime.Container(); // Repeat start content by diverting to its container if (startContent) { // Set the return point when jumping back into the start content // - In this case, it's the $r2 point, within the choice content "c". _returnToR2 = new Runtime.DivertTargetValue(); _innerContentContainer.AddContent(Runtime.ControlCommand.EvalStart()); _innerContentContainer.AddContent(_returnToR2); _innerContentContainer.AddContent(Runtime.ControlCommand.EvalEnd()); var varAssign = new Runtime.VariableAssignment("$r", true); _innerContentContainer.AddContent(varAssign); // Main divert into start content _divertToStartContentInner = new Runtime.Divert(); _innerContentContainer.AddContent(_divertToStartContentInner); // Define label to return to _r2Label = new Runtime.Container(); _r2Label.name = "$r2"; _innerContentContainer.AddContent(_r2Label); } // Choice's own inner content if (innerContent) { var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject() as Runtime.Container; _innerContentContainer.AddContentsOfContainer(innerChoiceOnlyContent); } if (this.story.countAllVisits) { _innerContentContainer.visitsShouldBeCounted = true; } _innerContentContainer.countingAtStartOnly = true; return(_outerContainer); }
// Runtime content can be summarised as follows: // - Evaluate an expression if necessary to branch on // - Branch to a named container if true // - Divert back to main flow // (owner Conditional is in control of this target point) public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Are we testing against a condition that's used for more than just this // branch? If so, the first thing we need to do is replicate the value that's // on the evaluation stack so that we don't fully consume it, in case other // branches need to use it. bool usingValueOnStack = (isBoolCondition || shouldMatchEquality) && !alwaysMatch; if (usingValueOnStack) { container.AddContent(Runtime.ControlCommand.Duplicate()); } _divertOnBranch = new Runtime.Divert(); Runtime.Branch branch; if (isBoolCondition) { if (boolRequired == true) { branch = new Runtime.Branch(trueDivert: _divertOnBranch); } else { branch = new Runtime.Branch(falseDivert: _divertOnBranch); } } else { bool needsEval = ownExpression || alwaysMatch; if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalStart()); } if (ownExpression) { ownExpression.GenerateIntoContainer(container); } if (shouldMatchEquality) { container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); } if (alwaysMatch) { container.AddContent(new Runtime.LiteralInt(1)); } if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalEnd()); } branch = new Runtime.Branch(trueDivert: _divertOnBranch); } container.AddContent(branch); _contentContainer = GenerateRuntimeForContent(); _contentContainer.name = "b"; if (usingValueOnStack) { _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0); } container.AddToNamedContentOnly(_contentContainer); returnDivert = new Runtime.Divert(); _contentContainer.AddContent(returnDivert); return(container); }
// Runtime content can be summarised as follows: // - Evaluate an expression if necessary to branch on // - Branch to a named container if true // - Divert back to main flow // (owner Conditional is in control of this target point) public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container (); // Are we testing against a condition that's used for more than just this // branch? If so, the first thing we need to do is replicate the value that's // on the evaluation stack so that we don't fully consume it, in case other // branches need to use it. bool duplicatesStackValue = shouldMatchEquality && !isElse; if ( duplicatesStackValue ) container.AddContent (Runtime.ControlCommand.Duplicate ()); _conditionalDivert = new Runtime.Divert (); // else clause is unconditional catch-all, otherwise the divert is conditional _conditionalDivert.isConditional = !isElse; // Need extra evaluation? if( !isTrueBranch && !isElse ) { bool needsEval = ownExpression != null; if( needsEval ) container.AddContent (Runtime.ControlCommand.EvalStart ()); if (ownExpression) ownExpression.GenerateIntoContainer (container); // Uses existing duplicated value if (shouldMatchEquality) container.AddContent (Runtime.NativeFunctionCall.CallWithName ("==")); if( needsEval ) container.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Will pop from stack if conditional container.AddContent (_conditionalDivert); _contentContainer = GenerateRuntimeForContent (); _contentContainer.name = "b"; if( duplicatesStackValue ) _contentContainer.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0); container.AddToNamedContentOnly (_contentContainer); returnDivert = new Runtime.Divert (); _contentContainer.AddContent (returnDivert); return container; }
// Runtime content can be summarised as follows: // - Evaluate an expression if necessary to branch on // - Branch to a named container if true // - Divert back to main flow // (owner Conditional is in control of this target point) public override Runtime.Object GenerateRuntimeObject () { // Check for common mistake, of putting "else:" instead of "- else:" if (_innerWeave) { foreach (var c in _innerWeave.content) { var text = c as Parsed.Text; if (text) { // Don't need to trim at the start since the parser handles that already if (text.text.StartsWith ("else:", System.StringComparison.InvariantCulture)) { Warning ("Saw the text 'else:' which is being treated as content. Did you mean '- else:'?", text); } } } } var container = new Runtime.Container (); // Are we testing against a condition that's used for more than just this // branch? If so, the first thing we need to do is replicate the value that's // on the evaluation stack so that we don't fully consume it, in case other // branches need to use it. bool duplicatesStackValue = shouldMatchEquality && !isElse; if ( duplicatesStackValue ) container.AddContent (Runtime.ControlCommand.Duplicate ()); _conditionalDivert = new Runtime.Divert (); // else clause is unconditional catch-all, otherwise the divert is conditional _conditionalDivert.isConditional = !isElse; // Need extra evaluation? if( !isTrueBranch && !isElse ) { bool needsEval = ownExpression != null; if( needsEval ) container.AddContent (Runtime.ControlCommand.EvalStart ()); if (ownExpression) ownExpression.GenerateIntoContainer (container); // Uses existing duplicated value if (shouldMatchEquality) container.AddContent (Runtime.NativeFunctionCall.CallWithName ("==")); if( needsEval ) container.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Will pop from stack if conditional container.AddContent (_conditionalDivert); _contentContainer = GenerateRuntimeForContent (); _contentContainer.name = "b"; if( duplicatesStackValue ) _contentContainer.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0); container.AddToNamedContentOnly (_contentContainer); returnDivert = new Runtime.Divert (); _contentContainer.AddContent (returnDivert); return container; }
public override Runtime.Object GenerateRuntimeObject() { _outerContainer = new Runtime.Container (); // Content names for different types of choice: // * start content [choice only content] inner content // * start content -> divert // * start content // * [choice only content] // Hmm, this structure has become slightly insane! // // [ // EvalStart // BeginString // PUSH (function) // -> s // EndString // BeginString // ... choice only content // EndEval // Condition expression // choice: -> "c" // (s) = [ // start content // ] // (c) = [ // PUSH (function) // -> s // inner content // ] // ] _runtimeChoice = new Runtime.ChoicePoint (onceOnly); _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault; if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ()); } // Start content is put into a named container that's referenced both // when displaying the choice initially, and when generating the text // when the choice is chosen. if (startContent) { // Mark the start of the choice text generation, so that the runtime // knows where to rewind to to extract the content from the output stream. _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); // "Function call" to generate start content _divertToStartContentOuter = new Runtime.Divert (Runtime.PushPopType.Function); _outerContainer.AddContent (_divertToStartContentOuter); // Start content itself in a named container _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container; _startContentRuntimeContainer.name = "s"; _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasStartContent = true; } // Choice only content - mark the start, then generate it directly into the outer container if (choiceOnlyContent) { _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container; _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasChoiceOnlyContent = true; } // Generate any condition for this choice if (condition) { condition.GenerateIntoContainer (_outerContainer); _runtimeChoice.hasCondition = true; } if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Add choice itself _outerContainer.AddContent (_runtimeChoice); // Container that choice points to for when it's chosen _innerContentContainer = new Runtime.Container (); // Repeat start content by diverting to its container if (startContent) { _divertToStartContentInner = new Runtime.Divert (Runtime.PushPopType.Function); _innerContentContainer.AddContent (_divertToStartContentInner); } // Choice's own inner content if (innerContent) { var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container; _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent); } // Fully parsed choice will be a full line, so it needs to be terminated if (startContent || innerContent) { _innerContentContainer.AddContent(new Runtime.StringValue("\n")); } // Use "c" as the destination name within the choice's outer container _innerContentContainer.name = "c"; _outerContainer.AddToNamedContentOnly (_innerContentContainer); if (this.story.countAllVisits) { _innerContentContainer.visitsShouldBeCounted = true; _innerContentContainer.turnIndexShouldBeCounted = true; } _innerContentContainer.countingAtStartOnly = true; // Does this choice end in an explicit divert? if (terminatingDivert) { _innerContentContainer.AddContent (terminatingDivert.runtimeObject); } return _outerContainer; }
public override Runtime.Object GenerateRuntimeObject () { _outerContainer = new Runtime.Container (); // Content names for different types of choice: // * start content [choice only content] inner content // * start content -> divert // * start content // * [choice only content] // Hmm, this structure has become slightly insane! // // [ // EvalStart // assign $r = $r1 -- return target = return label 1 // BeginString // -> s // [(r1)] -- return label 1 (after start content) // EndString // BeginString // ... choice only content // EndEval // Condition expression // choice: -> "c" // (s) = [ // start content // -> r -- goto return label 1 or 2 // ] // (c) = [ // EvalStart // assign $r = $r2 -- return target = return label 2 // EndEval // -> s // [(r2)] -- return label 1 (after start content) // inner content // ] // ] _runtimeChoice = new Runtime.ChoicePoint (onceOnly); _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault; if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ()); } // Start content is put into a named container that's referenced both // when displaying the choice initially, and when generating the text // when the choice is chosen. if (startContent) { // Generate start content and return // - We can't use a function since it uses a call stack element, which would // put temporary values out of scope. Instead we manually divert around. // - $r is a variable divert target contains the return point _returnToR1 = new Runtime.DivertTargetValue (); _outerContainer.AddContent (_returnToR1); var varAssign = new Runtime.VariableAssignment ("$r", true); _outerContainer.AddContent (varAssign); // Mark the start of the choice text generation, so that the runtime // knows where to rewind to to extract the content from the output stream. _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); _divertToStartContentOuter = new Runtime.Divert (); _outerContainer.AddContent (_divertToStartContentOuter); // Start content itself in a named container _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container; _startContentRuntimeContainer.name = "s"; // Effectively, the "return" statement - return to the point specified by $r var varDivert = new Runtime.Divert (); varDivert.variableDivertName = "$r"; _startContentRuntimeContainer.AddContent (varDivert); // Add the container _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer); // This is the label to return to _r1Label = new Runtime.Container (); _r1Label.name = "$r1"; _outerContainer.AddContent (_r1Label); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasStartContent = true; } // Choice only content - mark the start, then generate it directly into the outer container if (choiceOnlyContent) { _outerContainer.AddContent (Runtime.ControlCommand.BeginString ()); var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container; _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent); _outerContainer.AddContent (Runtime.ControlCommand.EndString ()); _runtimeChoice.hasChoiceOnlyContent = true; } // Generate any condition for this choice if (condition) { condition.GenerateIntoContainer (_outerContainer); _runtimeChoice.hasCondition = true; } if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Add choice itself _outerContainer.AddContent (_runtimeChoice); // Container that choice points to for when it's chosen _innerContentContainer = new Runtime.Container (); // Repeat start content by diverting to its container if (startContent) { // Set the return point when jumping back into the start content // - In this case, it's the $r2 point, within the choice content "c". _returnToR2 = new Runtime.DivertTargetValue (); _innerContentContainer.AddContent (Runtime.ControlCommand.EvalStart ()); _innerContentContainer.AddContent (_returnToR2); _innerContentContainer.AddContent (Runtime.ControlCommand.EvalEnd ()); var varAssign = new Runtime.VariableAssignment ("$r", true); _innerContentContainer.AddContent (varAssign); // Main divert into start content _divertToStartContentInner = new Runtime.Divert (); _innerContentContainer.AddContent (_divertToStartContentInner); // Define label to return to _r2Label = new Runtime.Container (); _r2Label.name = "$r2"; _innerContentContainer.AddContent (_r2Label); } // Choice's own inner content if (innerContent) { var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container; _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent); } // Fully parsed choice will be a full line, so it needs to be terminated if (startContent || innerContent) { _innerContentContainer.AddContent(new Runtime.StringValue("\n")); } // Use "c" as the destination name within the choice's outer container _innerContentContainer.name = "c"; _outerContainer.AddToNamedContentOnly (_innerContentContainer); if (this.story.countAllVisits) { _innerContentContainer.visitsShouldBeCounted = true; _innerContentContainer.turnIndexShouldBeCounted = true; } _innerContentContainer.countingAtStartOnly = true; // Does this choice end in an explicit divert? if (terminatingDivert) { _innerContentContainer.AddContent (terminatingDivert.runtimeObject); } return _outerContainer; }
public override Runtime.Object GenerateRuntimeObject() { // Check whether flow has a loose end: // - Most flows should end in a choice or a divert (otherwise issue a warning) // - Functions need a return, otherwise an implicit one is added ValidateTermination(); if (isFunction) { CheckForDisallowedFunctionFlowControl(); } var container = new Runtime.Container(); container.name = name; if (this.story.countAllVisits) { container.visitsShouldBeCounted = true; container.turnIndexShouldBeCounted = true; } GenerateArgumentVariableAssignments(container); // Run through content defined for this knot/stitch: // - First of all, any initial content before a sub-stitch // or any weave content is added to the main content container // - The first inner knot/stitch is automatically entered, while // the others are only accessible by an explicit divert // - The exception to this rule is if the knot/stitch takes // parameters, in which case it can't be auto-entered. // - Any Choices and Gathers (i.e. IWeavePoint) found are // processsed by GenerateFlowContent. int contentIdx = 0; while (content != null && contentIdx < content.Count) { Parsed.Object obj = content [contentIdx]; // Inner knots and stitches if (obj is FlowBase) { var childFlow = (FlowBase)obj; var childFlowRuntime = childFlow.runtimeObject; // First inner knot/stitch - automatically step into it if (contentIdx == 0 && !childFlow.hasParameters) { _startingSubFlowDivert = new Runtime.Divert(); container.AddContent(_startingSubFlowDivert); _startingSubFlowRuntime = childFlowRuntime; } // Check for duplicate knots/stitches with same name 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); } // Other content (including entire Weaves that were grouped in the constructor) // At the time of writing, all FlowBases have a maximum of one piece of "other content" // and it's always the root Weave else { container.AddContent(obj.runtimeObject); } contentIdx++; } // Tie up final loose ends to the very end if (_rootWeave && _rootWeave.looseEnds != null && _rootWeave.looseEnds.Count > 0) { foreach (var looseEnd in _rootWeave.looseEnds) { if (looseEnd is Divert) { if (_finalLooseEnds == null) { _finalLooseEnds = new List <Ink.Runtime.Divert> (); _finalLooseEndTarget = Runtime.ControlCommand.NoOp(); container.AddContent(_finalLooseEndTarget); } _finalLooseEnds.Add((Runtime.Divert)looseEnd.runtimeObject); } } } return(container); }
// Runtime content can be summarised as follows: // - Evaluate an expression if necessary to branch on // - Branch to a named container if true // - Divert back to main flow // (owner Conditional is in control of this target point) public override Runtime.Object GenerateRuntimeObject() { // Check for common mistake, of putting "else:" instead of "- else:" if (_innerWeave) { foreach (var c in _innerWeave.content) { var text = c as Parsed.Text; if (text) { // Don't need to trim at the start since the parser handles that already if (text.text.StartsWith("else:")) { Warning("Saw the text 'else:' which is being treated as content. Did you mean '- else:'?", text); } } } } var container = new Runtime.Container(); // Are we testing against a condition that's used for more than just this // branch? If so, the first thing we need to do is replicate the value that's // on the evaluation stack so that we don't fully consume it, in case other // branches need to use it. bool duplicatesStackValue = matchingEquality && !isElse; if (duplicatesStackValue) { container.AddContent(Runtime.ControlCommand.Duplicate()); } _conditionalDivert = new Runtime.Divert(); // else clause is unconditional catch-all, otherwise the divert is conditional _conditionalDivert.isConditional = !isElse; // Need extra evaluation? if (!isTrueBranch && !isElse) { bool needsEval = ownExpression != null; if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalStart()); } if (ownExpression) { ownExpression.GenerateIntoContainer(container); } // Uses existing duplicated value if (matchingEquality) { container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); } if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalEnd()); } } // Will pop from stack if conditional container.AddContent(_conditionalDivert); _contentContainer = GenerateRuntimeForContent(); _contentContainer.name = "b"; // Multi-line conditionals get a newline at the start of each branch // (as opposed to the start of the multi-line conditional since the condition // may evaluate to false.) if (!isInline) { _contentContainer.InsertContent(new Runtime.StringValue("\n"), 0); } if (duplicatesStackValue || (isElse && matchingEquality)) { _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0); } container.AddToNamedContentOnly(_contentContainer); returnDivert = new Runtime.Divert(); _contentContainer.AddContent(returnDivert); return(container); }
// Found gather point: // - gather any loose ends // - set the gather as the main container to dump new content in void AddRuntimeForGather(Gather gather) { // Determine whether this Gather should be auto-entered: // - It is auto-entered if there were no choices in the last section // - A section is "since the previous gather" - so reset now bool autoEnter = !hasSeenChoiceInSection; hasSeenChoiceInSection = false; var gatherContainer = gather.runtimeContainer; if (gather.name == null) { // Use disallowed character so it's impossible to have a name collision gatherContainer.name = "g-" + _unnamedGatherCount; _unnamedGatherCount++; } // Auto-enter: include in main content if (autoEnter) { currentContainer.AddContent(gatherContainer); } // Don't auto-enter: // Add this gather to the main content, but only accessible // by name so that it isn't stepped into automatically, but only via // a divert from a loose end. else { _rootContainer.AddToNamedContentOnly(gatherContainer); } // Consume loose ends: divert them to this gather foreach (IWeavePoint looseEndWeavePoint in looseEnds) { var looseEnd = (Parsed.Object)looseEndWeavePoint; // Skip gather loose ends that are at the same level // since they'll be handled by the auto-enter code below // that only jumps into the gather if (current runtime choices == 0) if (looseEnd is Gather) { var prevGather = (Gather)looseEnd; if (prevGather.indentationDepth == gather.indentationDepth) { continue; } } Runtime.Divert divert = null; if (looseEnd is Parsed.Divert) { divert = (Runtime.Divert)looseEnd.runtimeObject; } else { divert = new Runtime.Divert(); var looseWeavePoint = looseEnd as IWeavePoint; looseWeavePoint.runtimeContainer.AddContent(divert); } // Pass back knowledge of this loose end being diverted // to the FlowBase so that it can maintain a list of them, // and resolve the divert references later gatherPointsToResolve.Add(new GatherPointToResolve { divert = divert, targetRuntimeObj = gatherContainer }); } looseEnds.Clear(); // Replace the current container itself currentContainer = gatherContainer; }
public override Runtime.Object GenerateRuntimeObject() { _outerContainer = new Runtime.Container(); // Content names for different types of choice: // * start content [choice only content] inner content // * start content -> divert // * start content // * [choice only content] // Hmm, this structure has become slightly insane! // // [ // EvalStart // BeginString // PUSH (function) // -> s // EndString // BeginString // ... choice only content // EndEval // Condition expression // choice: -> "c" // (s) = [ // start content // ] // (c) = [ // PUSH (function) // -> s // inner content // ] // ] _runtimeChoice = new Runtime.ChoicePoint(onceOnly); _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault; if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent(Runtime.ControlCommand.EvalStart()); } // Start content is put into a named container that's referenced both // when displaying the choice initially, and when generating the text // when the choice is chosen. if (startContent) { // Mark the start of the choice text generation, so that the runtime // knows where to rewind to to extract the content from the output stream. _outerContainer.AddContent(Runtime.ControlCommand.BeginString()); // "Function call" to generate start content _divertToStartContentOuter = new Runtime.Divert(Runtime.PushPopType.Function); _outerContainer.AddContent(_divertToStartContentOuter); // Start content itself in a named container _startContentRuntimeContainer = startContent.GenerateRuntimeObject() as Runtime.Container; _startContentRuntimeContainer.name = "s"; _outerContainer.AddToNamedContentOnly(_startContentRuntimeContainer); _outerContainer.AddContent(Runtime.ControlCommand.EndString()); _runtimeChoice.hasStartContent = true; } // Choice only content - mark the start, then generate it directly into the outer container if (choiceOnlyContent) { _outerContainer.AddContent(Runtime.ControlCommand.BeginString()); var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject() as Runtime.Container; _outerContainer.AddContentsOfContainer(choiceOnlyRuntimeContent); _outerContainer.AddContent(Runtime.ControlCommand.EndString()); _runtimeChoice.hasChoiceOnlyContent = true; } // Generate any condition for this choice if (condition) { condition.GenerateIntoContainer(_outerContainer); _runtimeChoice.hasCondition = true; } if (startContent || choiceOnlyContent || condition) { _outerContainer.AddContent(Runtime.ControlCommand.EvalEnd()); } // Add choice itself _outerContainer.AddContent(_runtimeChoice); // Container that choice points to for when it's chosen _innerContentContainer = new Runtime.Container(); // Repeat start content by diverting to its container if (startContent) { _divertToStartContentInner = new Runtime.Divert(Runtime.PushPopType.Function); _innerContentContainer.AddContent(_divertToStartContentInner); } // Choice's own inner content if (innerContent) { var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject() as Runtime.Container; _innerContentContainer.AddContentsOfContainer(innerChoiceOnlyContent); } // Fully parsed choice will be a full line, so it needs to be terminated if (startContent || innerContent) { _innerContentContainer.AddContent(new Runtime.StringValue("\n")); } // Use "c" as the destination name within the choice's outer container _innerContentContainer.name = "c"; _outerContainer.AddToNamedContentOnly(_innerContentContainer); if (this.story.countAllVisits) { _innerContentContainer.visitsShouldBeCounted = true; _innerContentContainer.turnIndexShouldBeCounted = true; } _innerContentContainer.countingAtStartOnly = true; // Does this choice end in an explicit divert? if (terminatingDivert) { _innerContentContainer.AddContent(terminatingDivert.runtimeObject); } return(_outerContainer); }
// Generate runtime code that looks like: // chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping" // if chosenIndex == 0, divert to s0 // if chosenIndex == 1, divert to s1 [etc] // increment sequence // // - s0: // <content for sequence element> // divert back to increment point // - s1: // <content for sequence element> // divert back to increment point // public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); container.visitsShouldBeCounted = true; container.countingAtStartOnly = true; _sequenceDivertsToResove = new List <SequenceDivertToResolve> (); // Get sequence read count container.AddContent(Runtime.ControlCommand.EvalStart()); container.AddContent(Runtime.ControlCommand.VisitIndex()); // Chosen sequence index: // - Stopping: take the MIN(read count, num elements - 1) if (sequenceType == SequenceType.Stopping) { container.AddContent(new Runtime.LiteralInt(sequenceElements.Count - 1)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("MIN")); } // - Cycle: take (read count % num elements) else if (sequenceType == SequenceType.Cycle) { container.AddContent(new Runtime.LiteralInt(sequenceElements.Count)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("%")); } // Once: allow sequence count to be unbounded else if (sequenceType == SequenceType.Once) { // Do nothing - the sequence count will simply prevent // any content being referenced when it goes out of bounds } // Shuffle else if (sequenceType == SequenceType.Shuffle) { // This one's a bit more complex! Choose the index at runtime. container.AddContent(new Runtime.LiteralInt(sequenceElements.Count)); container.AddContent(Runtime.ControlCommand.SequenceShuffleIndex()); } // Not implemented else { throw new System.NotImplementedException(); } container.AddContent(Runtime.ControlCommand.EvalEnd()); // Create point to return to when sequence is complete var postSequenceNoOp = Runtime.ControlCommand.NoOp(); var elIndex = 0; foreach (var el in sequenceElements) { // This sequence element: // if( chosenIndex == this index ) divert to this sequence element // duplicate chosen sequence index, since it'll be consumed by "==" container.AddContent(Runtime.ControlCommand.EvalStart()); container.AddContent(Runtime.ControlCommand.Duplicate()); container.AddContent(new Runtime.LiteralInt(elIndex)); container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); container.AddContent(Runtime.ControlCommand.EvalEnd()); // Divert branch for this sequence element var sequenceDivert = new Runtime.Divert(); container.AddContent(new Runtime.Branch(sequenceDivert)); // Generate content for this sequence element var contentContainerForSequenceBranch = (Runtime.Container)el.runtimeObject; contentContainerForSequenceBranch.name = "s" + elIndex; // When sequence element is complete, divert back to end of sequence var seqBranchCompleteDivert = new Runtime.Divert(); contentContainerForSequenceBranch.AddContent(seqBranchCompleteDivert); container.AddToNamedContentOnly(contentContainerForSequenceBranch); // Save the diverts for reference resolution later (in ResolveReferences) AddDivertToResolve(sequenceDivert, contentContainerForSequenceBranch); AddDivertToResolve(seqBranchCompleteDivert, postSequenceNoOp); elIndex++; } container.AddContent(postSequenceNoOp); return(container); }
// Generate runtime code that looks like: // chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping" // if chosenIndex == 0, divert to s0 // if chosenIndex == 1, divert to s1 [etc] // increment sequence // // - s0: // <content for sequence element> // divert back to increment point // - s1: // <content for sequence element> // divert back to increment point // public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); container.visitsShouldBeCounted = true; container.countingAtStartOnly = true; _sequenceDivertsToResove = new List<SequenceDivertToResolve> (); // Get sequence read count container.AddContent (Runtime.ControlCommand.EvalStart ()); container.AddContent (Runtime.ControlCommand.VisitIndex ()); // Chosen sequence index: // - Stopping: take the MIN(read count, num elements - 1) if (sequenceType == SequenceType.Stopping) { container.AddContent (new Runtime.IntValue (sequenceElements.Count - 1)); container.AddContent (Runtime.NativeFunctionCall.CallWithName ("MIN")); } // - Cycle: take (read count % num elements) else if (sequenceType == SequenceType.Cycle) { container.AddContent (new Runtime.IntValue (sequenceElements.Count)); container.AddContent (Runtime.NativeFunctionCall.CallWithName ("%")); } // Once: allow sequence count to be unbounded else if (sequenceType == SequenceType.Once) { // Do nothing - the sequence count will simply prevent // any content being referenced when it goes out of bounds } // Shuffle else if (sequenceType == SequenceType.Shuffle) { // This one's a bit more complex! Choose the index at runtime. container.AddContent (new Runtime.IntValue (sequenceElements.Count)); container.AddContent (Runtime.ControlCommand.SequenceShuffleIndex ()); } // Not implemented else { throw new System.NotImplementedException (); } container.AddContent (Runtime.ControlCommand.EvalEnd ()); // Create point to return to when sequence is complete var postSequenceNoOp = Runtime.ControlCommand.NoOp (); var elIndex = 0; foreach (var el in sequenceElements) { // This sequence element: // if( chosenIndex == this index ) divert to this sequence element // duplicate chosen sequence index, since it'll be consumed by "==" container.AddContent (Runtime.ControlCommand.EvalStart ()); container.AddContent (Runtime.ControlCommand.Duplicate ()); container.AddContent (new Runtime.IntValue (elIndex)); container.AddContent (Runtime.NativeFunctionCall.CallWithName ("==")); container.AddContent (Runtime.ControlCommand.EvalEnd ()); // Divert branch for this sequence element var sequenceDivert = new Runtime.Divert (); sequenceDivert.isConditional = true; container.AddContent (sequenceDivert); // Generate content for this sequence element var contentContainerForSequenceBranch = (Runtime.Container) el.runtimeObject; contentContainerForSequenceBranch.name = "s" + elIndex; contentContainerForSequenceBranch.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0); // When sequence element is complete, divert back to end of sequence var seqBranchCompleteDivert = new Runtime.Divert (); contentContainerForSequenceBranch.AddContent (seqBranchCompleteDivert); container.AddToNamedContentOnly (contentContainerForSequenceBranch); // Save the diverts for reference resolution later (in ResolveReferences) AddDivertToResolve (sequenceDivert, contentContainerForSequenceBranch); AddDivertToResolve (seqBranchCompleteDivert, postSequenceNoOp); elIndex++; } container.AddContent (postSequenceNoOp); return container; }
public override Runtime.Object GenerateRuntimeObject() { // End = end flow immediately // Done = return from thread or instruct the flow that it's safe to exit if (isEnd) { return(Runtime.ControlCommand.End()); } if (isDone) { return(Runtime.ControlCommand.Done()); } runtimeDivert = new Runtime.Divert(); // Normally we resolve the target content during the // Resolve phase, since we expect all runtime objects to // be available in order to find the final runtime path for // the destination. However, we need to resolve the target // (albeit without the runtime target) early so that // we can get information about the arguments - whether // they're by reference - since it affects the code we // generate here. ResolveTargetContent(); CheckArgumentValidity(); // Passing arguments to the knot bool requiresArgCodeGen = arguments != null && arguments.Count > 0; if (requiresArgCodeGen || isFunctionCall || isTunnel || isThread) { var container = new Runtime.Container(); // Generate code for argument evaluation // This argument generation is coded defensively - it should // attempt to generate the code for all the parameters, even if // they don't match the expected arguments. This is so that the // parameter objects themselves are generated correctly and don't // get into a state of attempting to resolve references etc // without being generated. if (requiresArgCodeGen) { // Function calls already in an evaluation context 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]; } // Pass by reference: argument needs to be a variable reference if (argExpected != null && argExpected.isByReference) { var varRef = argToPass as VariableReference; if (varRef == null) { Error("Expected variable name to pass by reference to 'ref " + argExpected.name + "' but saw " + argToPass.ToString()); break; } // Check that we're not attempting to pass a read count by reference var targetPath = new Path(varRef.path); 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); } // Normal value being passed: evaluate it as normal else { argToPass.GenerateIntoContainer(container); } } // Function calls were already in an evaluation context if (!isFunctionCall) { container.AddContent(Runtime.ControlCommand.EvalEnd()); } } // Starting a thread? A bit like a push to the call stack below... but not. // It sort of puts the call stack on a thread stack (argh!) - forks the full flow. if (isThread) { container.AddContent(Runtime.ControlCommand.StartThread()); } // If this divert is a function call, tunnel, we push to the call stack // so we can return again else if (isFunctionCall || isTunnel) { runtimeDivert.pushesToStack = true; runtimeDivert.stackPushType = isFunctionCall ? Runtime.PushPopType.Function : Runtime.PushPopType.Tunnel; } // Jump into the "function" (knot/stitch) container.AddContent(runtimeDivert); return(container); } // Simple divert else { return(runtimeDivert); } }
// Runtime content can be summarised as follows: // - Evaluate an expression if necessary to branch on // - Branch to a named container if true // - Divert back to main flow // (owner Conditional is in control of this target point) public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Are we testing against a condition that's used for more than just this // branch? If so, the first thing we need to do is replicate the value that's // on the evaluation stack so that we don't fully consume it, in case other // branches need to use it. bool duplicatesStackValue = shouldMatchEquality && !isElse; if (duplicatesStackValue) { container.AddContent(Runtime.ControlCommand.Duplicate()); } _conditionalDivert = new Runtime.Divert(); // else clause is unconditional catch-all, otherwise the divert is conditional _conditionalDivert.isConditional = !isElse; // Need extra evaluation? if (!isTrueBranch && !isElse) { bool needsEval = ownExpression != null; if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalStart()); } if (ownExpression) { ownExpression.GenerateIntoContainer(container); } // Uses existing duplicated value if (shouldMatchEquality) { container.AddContent(Runtime.NativeFunctionCall.CallWithName("==")); } if (needsEval) { container.AddContent(Runtime.ControlCommand.EvalEnd()); } } // Will pop from stack if conditional container.AddContent(_conditionalDivert); _contentContainer = GenerateRuntimeForContent(); _contentContainer.name = "b"; if (duplicatesStackValue) { _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0); } container.AddToNamedContentOnly(_contentContainer); returnDivert = new Runtime.Divert(); _contentContainer.AddContent(returnDivert); return(container); }