public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Initial condition if (this.initialCondition) { container.AddContent(initialCondition.runtimeObject); } // Individual branches foreach (var branch in branches) { var branchContainer = (Container)branch.runtimeObject; container.AddContent(branchContainer); } // If it's a switch-like conditional, each branch // will have a "duplicate" operation for the original // switched value. If there's no final else clause // and we fall all the way through, we need to clean up. // (An else clause doesn't dup but it *does* pop) if (this.initialCondition != null && branches [0].ownExpression != null && !branches [branches.Count - 1].isElse) { container.AddContent(Runtime.ControlCommand.PopEvaluatedValue()); } // Target for branches to rejoin to _reJoinTarget = Runtime.ControlCommand.NoOp(); container.AddContent(_reJoinTarget); return(container); }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Initial condition if (this.initialCondition) { container.AddContent(initialCondition.runtimeObject); } // Individual branches foreach (var branch in branches) { var branchContainer = (Container)branch.runtimeObject; container.AddContent(branchContainer); } // If no branches matched, tidy up after ourselves if (this.initialCondition) { container.AddContent(Runtime.ControlCommand.PopEvaluatedValue()); } // Target for branches to rejoin to _reJoinTarget = Runtime.ControlCommand.NoOp(); container.AddContent(_reJoinTarget); return(container); }
public override void GenerateIntoContainer(Runtime.Container container) { // x = x + y // ^^^ ^ ^ ^ // 4 1 3 2 // Reverse polish notation: (x 1 +) (assign to x) // 1. container.AddContent(new Runtime.VariableReference(varName)); // 2. // - Expression used in the form ~ x += y // - Simple version: ~ x++ if (expression) { expression.GenerateIntoContainer(container); } else { container.AddContent(new Runtime.IntValue(1)); } // 3. container.AddContent(Runtime.NativeFunctionCall.CallWithName(isInc ? "+" : "-")); // 4. container.AddContent(new Runtime.VariableAssignment(varName, false)); }
public override Runtime.Object GenerateRuntimeObject() { FlowBase newDeclScope = null; if (isGlobalDeclaration) { newDeclScope = story; } else if (isNewTemporaryDeclaration) { newDeclScope = ClosestFlowBase(); } if (newDeclScope) { newDeclScope.TryAddNewVariableDeclaration(this); } // Global declarations don't generate actual procedural // runtime objects, but instead add a global variable to the story itself. // The story then initialises them all in one go at the start of the game. if (isGlobalDeclaration) { return(null); } var container = new Runtime.Container(); // The expression's runtimeObject is actually another nested container container.AddContent(expression.runtimeObject); container.AddContent(new Runtime.VariableAssignment(variableName, isNewTemporaryDeclaration)); return(container); }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Evaluate expression if (returnedExpression) { container.AddContent(returnedExpression.runtimeObject); } // Return Runtime.Void when there's no expression to evaluate // (This evaluation will just add the Void object to the evaluation stack) else { container.AddContent(Runtime.ControlCommand.EvalStart()); container.AddContent(new Runtime.Void()); container.AddContent(Runtime.ControlCommand.EvalEnd()); } // Then pop the call stack // (the evaluated expression will leave the return value on the evaluation stack) container.AddContent(Runtime.ControlCommand.PopFunction()); return(container); }
public override Runtime.Object GenerateRuntimeObject () { FlowBase newDeclScope = null; if (isGlobalDeclaration) { newDeclScope = story; } else if(isNewTemporaryDeclaration) { newDeclScope = ClosestFlowBase (); } if( newDeclScope ) newDeclScope.TryAddNewVariableDeclaration (this); // Global declarations don't generate actual procedural // runtime objects, but instead add a global variable to the story itself. // The story then initialises them all in one go at the start of the game. if( isGlobalDeclaration ) return null; var container = new Runtime.Container (); // The expression's runtimeObject is actually another nested container container.AddContent (expression.runtimeObject); container.AddContent (new Runtime.VariableAssignment (variableName, isNewTemporaryDeclaration)); return container; }
public override void GenerateIntoContainer(Runtime.Container container) { if (value is int) { container.AddContent(new Runtime.IntValue((int)value)); } else if (value is float) { container.AddContent(new Runtime.FloatValue((float)value)); } }
public override void GenerateIntoContainer(Runtime.Container container) { container.AddContent(Runtime.ControlCommand.BeginString()); foreach (var c in content) { container.AddContent(c.runtimeObject); } container.AddContent(Runtime.ControlCommand.EndString()); }
public override void GenerateIntoContainer(Runtime.Container container) { if (isChoiceCount) { if (arguments.Count > 0) { Error("The CHOICE_COUNT() function shouldn't take any arguments"); } container.AddContent(Runtime.ControlCommand.ChoiceCount()); } else if (isTurnsSince) { var literalDivertTarget = arguments [0] as DivertTarget; var variableDivertTarget = arguments[0] as VariableReference; if (arguments.Count != 1 || (literalDivertTarget == null && variableDivertTarget == null)) { Error("The TURNS_SINCE() function should take one argument: a divert target to the target knot, stitch, gather or choice you want to check. e.g. TURNS_SINCE(-> myKnot)"); return; } if (literalDivertTarget) { _turnCountDivertTarget = literalDivertTarget; AddContent(_turnCountDivertTarget); _turnCountDivertTarget.GenerateIntoContainer(container); } else { _turnCountVariableReference = variableDivertTarget; AddContent(_turnCountVariableReference); _turnCountVariableReference.GenerateIntoContainer(container); if (!story.countAllVisits) { Error("Attempting to get TURNS_SINCE for a variable target without -c compiler option. You need the compiler switch turned on so that it can track turn counts for everything, not just those you directly reference."); } } container.AddContent(Runtime.ControlCommand.TurnsSince()); } // Normal function call else { container.AddContent(_proxyDivert.runtimeObject); } }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); container.name = name; if (this.story.countAllVisits) { container.visitsShouldBeCounted = true; container.turnIndexShouldBeCounted = true; } container.countingAtStartOnly = true; // A gather can have null content, e.g. it's just purely a line with "-" if (content != null) { foreach (var c in content) { container.AddContent(c.runtimeObject); } } return(container); }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); if (content != null) { foreach (var obj in content) { var contentObjRuntime = obj.runtimeObject; // Some objects (e.g. author warnings) don't generate runtime objects if (contentObjRuntime) { container.AddContent(contentObjRuntime); } } } if (dontFlatten) { story.DontFlattenContainer(container); } return(container); }
public override void GenerateIntoContainer(Runtime.Container container) { leftExpression.GenerateIntoContainer(container); rightExpression.GenerateIntoContainer(container); opName = NativeNameForOp(opName); container.AddContent(Runtime.NativeFunctionCall.CallWithName(opName)); }
public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); // Set override path for tunnel onwards (or nothing) container.AddContent (Runtime.ControlCommand.EvalStart ()); if (overrideReturnPath != null) { _overrideDivertTarget = new Runtime.DivertTargetValue (); container.AddContent (_overrideDivertTarget); } else { container.AddContent (new Runtime.Void ()); } container.AddContent (Runtime.ControlCommand.EvalEnd ()); container.AddContent (Runtime.ControlCommand.PopTunnel ()); return container; }
public override void GenerateIntoContainer(Runtime.Container container) { divert.GenerateRuntimeObject(); _runtimeDivert = (Runtime.Divert)divert.runtimeDivert; _runtimeDivertTargetValue = new Runtime.DivertTargetValue(); container.AddContent(_runtimeDivertTargetValue); }
public override void GenerateIntoContainer(Runtime.Container container) { var runtimeRawList = new Runtime.InkList(); if (itemIdentifierList != null) { foreach (var itemIdentifier in itemIdentifierList) { var nameParts = itemIdentifier?.name.Split('.'); string listName = null; string listItemName = null; if (nameParts.Length > 1) { listName = nameParts [0]; listItemName = nameParts [1]; } else { listItemName = nameParts [0]; } var listItem = story.ResolveListItem(listName, listItemName, this); if (listItem == null) { if (listName == null) { Error("Could not find list definition that contains item '" + itemIdentifier + "'"); } else { Error("Could not find list item " + itemIdentifier); } } else { if (listName == null) { listName = ((ListDefinition)listItem.parent).identifier?.name; } var item = new Runtime.InkListItem(listName, listItem.name); if (runtimeRawList.ContainsKey(item)) { Warning("Duplicate of item '" + itemIdentifier + "' in list."); } else { runtimeRawList [item] = listItem.seriesValue; } } } } container.AddContent(new Runtime.ListValue(runtimeRawList)); }
public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); // Tell Runtime to start evaluating the following content as an expression container.AddContent (Runtime.ControlCommand.EvalStart()); GenerateIntoContainer (container); // Tell Runtime to output the result of the expression evaluation to the output stream if (outputWhenComplete) { container.AddContent (Runtime.ControlCommand.EvalOutput()); } // Tell Runtime to stop evaluating the content as an expression container.AddContent (Runtime.ControlCommand.EvalEnd()); return container; }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container (); if (content != null) { foreach (var obj in content) { container.AddContent (obj.runtimeObject); } } return container; }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Tell Runtime to start evaluating the following content as an expression container.AddContent(Runtime.ControlCommand.EvalStart()); GenerateIntoContainer(container); // Tell Runtime to output the result of the expression evaluation to the output stream if (outputWhenComplete) { container.AddContent(Runtime.ControlCommand.EvalOutput()); } // Tell Runtime to stop evaluating the content as an expression container.AddContent(Runtime.ControlCommand.EvalEnd()); return(container); }
public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); // Initial condition if (this.initialCondition) { container.AddContent (initialCondition.runtimeObject); } // Individual branches foreach (var branch in branches) { var branchContainer = (Container) branch.runtimeObject; container.AddContent (branchContainer); } // Target for branches to rejoin to _reJoinTarget = Runtime.ControlCommand.NoOp (); container.AddContent (_reJoinTarget); return container; }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Set override path for tunnel onwards (or nothing) container.AddContent(Runtime.ControlCommand.EvalStart()); if (overrideReturnPath != null) { _overrideDivertTarget = new Runtime.DivertTargetValue(); container.AddContent(_overrideDivertTarget); } else { container.AddContent(new Runtime.Void()); } container.AddContent(Runtime.ControlCommand.EvalEnd()); container.AddContent(Runtime.ControlCommand.PopTunnel()); return(container); }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); if (content != null) { foreach (var obj in content) { container.AddContent(obj.runtimeObject); } } return(container); }
public override void GenerateIntoContainer(Runtime.Container container) { // x = x + 1 // ^^^ ^ ^ ^ // 4 1 3 2 // Reverse polish notation: (x 1 +) (assign to x) // 1. container.AddContent(new Runtime.VariableReference(varName)); // 2. container.AddContent(new Runtime.IntValue(isInc ? 1 : -1)); // 3. container.AddContent(Runtime.NativeFunctionCall.CallWithName("+")); // 4. container.AddContent(new Runtime.VariableAssignment(varName, false)); // Finally, leave the variable on the stack so it can be used as a sub-expression container.AddContent(new Runtime.VariableReference(varName)); }
// When generating the value of a constant expression, // we can't just keep generating the same constant expression into // different places where the constant value is referenced, since then // the same runtime objects would be used in multiple places, which // is impossible since each runtime object should have one parent. // Instead, we generate a prototype of the runtime object(s), then // copy them each time they're used. public void GenerateConstantIntoContainer(Runtime.Container container) { if (_prototypeRuntimeConstantExpression == null) { _prototypeRuntimeConstantExpression = new Runtime.Container(); GenerateIntoContainer(_prototypeRuntimeConstantExpression); } foreach (var runtimeObj in _prototypeRuntimeConstantExpression.content) { container.AddContent(runtimeObj.Copy()); } }
public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); // Evaluate expression if (returnedExpression) { container.AddContent (returnedExpression.runtimeObject); } // Return Runtime.Void when there's no expression to evaluate // (This evaluation will just add the Void object to the evaluation stack) else { container.AddContent (Runtime.ControlCommand.EvalStart ()); container.AddContent (new Runtime.Void()); container.AddContent (Runtime.ControlCommand.EvalEnd ()); } // Then pop the call stack // (the evaluated expression will leave the return value on the evaluation stack) container.AddContent (Runtime.ControlCommand.PopFunction()); return container; }
public override void GenerateIntoContainer(Runtime.Container container) { divert.GenerateRuntimeObject(); _runtimeDivert = (Runtime.Divert)divert.runtimeDivert; _runtimeLiteralDivertTarget = new Runtime.LiteralDivertTarget(); 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(_runtimeLiteralDivertTarget); }
void GenerateArgumentVariableAssignments(Runtime.Container container) { if (this.arguments == null || this.arguments.Count == 0) { return; } // Assign parameters in reverse since they'll be popped off the evaluation stack // No need to generate EvalStart and EvalEnd since there's nothing being pushed // back onto the evaluation stack. for (int i = arguments.Count - 1; i >= 0; --i) { var paramName = arguments [i].name; var assign = new Runtime.VariableAssignment(paramName, isNewDeclaration: true); container.AddContent(assign); } }
public override void GenerateIntoContainer(Runtime.Container container) { // A && B && C && D // => (((A B &&) C &&) D &&) etc bool isFirst = true; foreach (var conditionExpr in subExpressions) { conditionExpr.GenerateIntoContainer(container); if (!isFirst) { container.AddContent(Runtime.NativeFunctionCall.CallWithName("&&")); } isFirst = false; } }
public override void GenerateIntoContainer(Runtime.Container container) { Expression constantValue = null; // Name can be null if it's actually a path to a knot/stitch etc for a read count var varName = name; // If it's a constant reference, just generate the literal expression value if (varName != null && story.constants.TryGetValue(varName, out constantValue)) { constantValue.GenerateIntoContainer(container); isConstantReference = true; } else { _runtimeVarRef = new Runtime.VariableReference(name); container.AddContent(_runtimeVarRef); } }
public override void GenerateIntoContainer(Runtime.Container container) { Expression constantValue = null; // If it's a constant reference, just generate the literal expression value // It's okay to access the constants at code generation time, since the // first thing the ExportRuntime function does it search for all the constants // in the story hierarchy, so they're all available. if (story.constants.TryGetValue(name, out constantValue)) { constantValue.GenerateConstantIntoContainer(container); isConstantReference = true; return; } _runtimeVarRef = new Runtime.VariableReference(name); // List item reference? // Path might be to a list (listName.listItemName or just listItemName) if (path.Count == 1 || path.Count == 2) { string listItemName = null; string listName = null; if (path.Count == 1) { listItemName = path [0]; } else { listName = path [0]; listItemName = path [1]; } var listItem = story.ResolveListItem(listName, listItemName, this); if (listItem) { isListItemReference = true; } } container.AddContent(_runtimeVarRef); }
public override Runtime.Object GenerateRuntimeObject () { var container = new Runtime.Container (); container.name = name; if (this.story.countAllVisits) { container.visitsShouldBeCounted = true; container.turnIndexShouldBeCounted = true; } container.countingAtStartOnly = true; // A gather can have null content, e.g. it's just purely a line with "-" if (content != null) { foreach (var c in content) { container.AddContent (c.runtimeObject); } } 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); } }
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; // 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 () { // 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; } }
// 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 void GenerateIntoContainer(Runtime.Container container) { innerExpression.GenerateIntoContainer(container); container.AddContent(Runtime.NativeFunctionCall.CallWithName(nativeNameForOp)); }
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; }
// 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 Runtime.Story ExportRuntime(ErrorHandler errorHandler = null) { _errorHandler = errorHandler; // Find all constants before main export begins, so that VariableReferences know // whether to generate a runtime variable reference or the literal value constants = new Dictionary<string, Expression> (); foreach (var constDecl in FindAll<ConstantDeclaration> ()) { // Check for duplicate definitions Parsed.Expression existingDefinition = null; if (constants.TryGetValue (constDecl.constantName, out existingDefinition)) { if (!existingDefinition.Equals (constDecl.expression)) { var errorMsg = string.Format ("CONST '{0}' has been redefined with a different value. Multiple definitions of the same CONST are valid so long as they contain the same value. Initial definition was on {1}.", constDecl.constantName, existingDefinition.debugMetadata); Error (errorMsg, constDecl, isWarning:false); } } constants [constDecl.constantName] = constDecl.expression; } externals = new Dictionary<string, ExternalDeclaration> (); // Get default implementation of runtimeObject, which calls ContainerBase's generation method var rootContainer = runtimeObject as Runtime.Container; // Export initialisation of global variables // TODO: We *could* add this as a declarative block to the story itself... var variableInitialisation = new Runtime.Container (); variableInitialisation.AddContent (Runtime.ControlCommand.EvalStart ()); // Global variables are those that are local to the story and marked as global foreach (var nameDeclPair in variableDeclarations) { var varName = nameDeclPair.Key; var varDecl = nameDeclPair.Value; if (varDecl.isGlobalDeclaration) { varDecl.expression.GenerateIntoContainer (variableInitialisation); var runtimeVarAss = new Runtime.VariableAssignment (varName, isNewDeclaration:true); runtimeVarAss.isGlobal = true; variableInitialisation.AddContent (runtimeVarAss); } } variableInitialisation.AddContent (Runtime.ControlCommand.EvalEnd ()); variableInitialisation.AddContent (Runtime.ControlCommand.End ()); if (variableDeclarations.Count > 0) { variableInitialisation.name = "global decl"; rootContainer.AddToNamedContentOnly (variableInitialisation); } // Signal that it's safe to exit without error, even if there are no choices generated // (this only happens at the end of top level content that isn't in any particular knot) rootContainer.AddContent (Runtime.ControlCommand.Done ()); // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase var runtimeStory = new Runtime.Story (rootContainer); runtimeObject = runtimeStory; if (hadError) return null; // Optimisation step - inline containers that can be FlattenContainersIn (rootContainer); // Now that the story has been fulled parsed into a hierarchy, // and the derived runtime hierarchy has been built, we can // resolve referenced symbols such as variables and paths. // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName) // We don't make any assumptions that the INKPath follows the same // conventions as the script format, so we resolve to actual objects before // translating into an INKPath. (This also allows us to choose whether // we want the paths to be absolute) ResolveReferences (this); if (hadError) return null; runtimeStory.ResetState (); return runtimeStory; }
// 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; }
// 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); }
public override void GenerateIntoContainer(Runtime.Container container) { if (isChoiceCount) { if (arguments.Count > 0) { Error("The CHOICE_COUNT() function shouldn't take any arguments"); } container.AddContent(Runtime.ControlCommand.ChoiceCount()); } else if (isTurnsSince) { var divertTarget = arguments [0] as DivertTarget; var variableDivertTarget = arguments [0] as VariableReference; if (arguments.Count != 1 || (divertTarget == null && variableDivertTarget == null)) { Error("The TURNS_SINCE() function should take one argument: a divert target to the target knot, stitch, gather or choice you want to check. e.g. TURNS_SINCE(-> myKnot)"); return; } if (divertTarget) { _turnCountDivertTarget = divertTarget; AddContent(_turnCountDivertTarget); _turnCountDivertTarget.GenerateIntoContainer(container); } else { _turnCountVariableReference = variableDivertTarget; AddContent(_turnCountVariableReference); _turnCountVariableReference.GenerateIntoContainer(container); if (!story.countAllVisits) { Error("Attempting to get TURNS_SINCE for a variable target without -c compiler option. You need the compiler switch turned on so that it can track turn counts for everything, not just those you directly reference."); } } container.AddContent(Runtime.ControlCommand.TurnsSince()); } else if (isRandom) { if (arguments.Count != 2) { Error("RANDOM should take 2 parameters: a minimum and a maximum integer"); } // We can type check single values, but not complex expressions for (int arg = 0; arg < arguments.Count; arg++) { if (arguments [arg] is Number) { var num = arguments [arg] as Number; if (!(num.value is int)) { string paramName = arg == 0 ? "minimum" : "maximum"; Error("RANDOM's " + paramName + " parameter should be an integer"); } } arguments[arg].GenerateIntoContainer(container); } container.AddContent(Runtime.ControlCommand.Random()); } else if (isSeedRandom) { if (arguments.Count != 1) { Error("SEED_RANDOM should take 1 parameter - an integer seed"); } var num = arguments [0] as Number; if (num && !(num.value is int)) { Error("SEED_RANDOM's parameter should be an integer seed"); } arguments [0].GenerateIntoContainer(container); container.AddContent(Runtime.ControlCommand.SeedRandom()); } // Normal function call else { container.AddContent(_proxyDivert.runtimeObject); } // Function calls that are used alone on a tilda-based line: // ~ func() // Should tidy up any returned value from the evaluation stack, // since it's unused. if (shouldPopReturnedValue) { container.AddContent(Runtime.ControlCommand.PopEvaluatedValue()); } }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); // Set override path for tunnel onwards (or nothing) container.AddContent(Runtime.ControlCommand.EvalStart()); if (divertAfter) { // Generate runtime object's generated code and steal the arguments runtime code var returnRuntimeObj = divertAfter.GenerateRuntimeObject(); var returnRuntimeContainer = returnRuntimeObj as Runtime.Container; if (returnRuntimeContainer) { // Steal all code for generating arguments from the divert var args = divertAfter.arguments; if (args != null && args.Count > 0) { // Steal everything betwen eval start and eval end int evalStart = -1; int evalEnd = -1; for (int i = 0; i < returnRuntimeContainer.content.Count; i++) { var cmd = returnRuntimeContainer.content [i] as Runtime.ControlCommand; if (cmd) { if (evalStart == -1 && cmd.commandType == Runtime.ControlCommand.CommandType.EvalStart) { evalStart = i; } else if (cmd.commandType == Runtime.ControlCommand.CommandType.EvalEnd) { evalEnd = i; } } } for (int i = evalStart + 1; i < evalEnd; i++) { var obj = returnRuntimeContainer.content [i]; obj.parent = null; // prevent error of being moved between owners container.AddContent(returnRuntimeContainer.content [i]); } } } // Supply the divert target for the tunnel onwards target, either variable or more commonly, the explicit name var returnDivertObj = returnRuntimeObj as Runtime.Divert; if (returnDivertObj != null && returnDivertObj.hasVariableTarget) { var runtimeVarRef = new Runtime.VariableReference(returnDivertObj.variableDivertName); container.AddContent(runtimeVarRef); } else { _overrideDivertTarget = new Runtime.DivertTargetValue(); container.AddContent(_overrideDivertTarget); } } // No divert after tunnel onwards else { container.AddContent(new Runtime.Void()); } container.AddContent(Runtime.ControlCommand.EvalEnd()); container.AddContent(Runtime.ControlCommand.PopTunnel()); 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); }