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 (); if (content != null) { foreach (var obj in content) { container.AddContent (obj.runtimeObject); } } return container; }
// 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 (); // 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() { 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 if (expression != null) { container.AddContent(expression.runtimeObject); } else if (listDefinition != null) { container.AddContent(listDefinition.runtimeObject); } _runtimeAssignment = new Runtime.VariableAssignment(variableName, isNewTemporaryDeclaration); container.AddContent(_runtimeAssignment); return(container); }
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 (); // 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 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 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 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)); }
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 (); // 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; }
void TryFlattenContainer(Runtime.Container container) { if (container.namedContent.Count > 0 || container.hasValidName || _dontFlattenContainers.Contains(container)) { return; } // Inline all the content in container into the parent var parentContainer = container.parent as Runtime.Container; if (parentContainer) { var contentIdx = parentContainer.content.IndexOf(container); parentContainer.content.RemoveAt(contentIdx); foreach (var innerContent in container.content) { innerContent.parent = null; parentContainer.InsertContent(innerContent, contentIdx); contentIdx++; } } }
public override Runtime.Object GenerateRuntimeObject() { var container = new Runtime.Container(); container.name = name; if (this.story.countAllVisits) { container.visitsShouldBeCounted = 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); }
// 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() { _rootContainer = currentContainer = new Runtime.Container(); looseEnds = new List<Parsed.Object> (); gatherPointsToResolve = new List<GatherPointToResolve> (); // Iterate through content for the block at this level of indentation // - Normal content is nested under Choices and Gathers // - Blocks that are further indented cause recursion // - Keep track of loose ends so that they can be diverted to Gathers foreach(var obj in content) { // Choice or Gather if (obj is IWeavePoint) { AddRuntimeForWeavePoint ((IWeavePoint)obj); } // Non-weave point else { // Nested weave if (obj is Weave) { var weave = (Weave)obj; AddRuntimeForNestedWeave (weave); gatherPointsToResolve.AddRange (weave.gatherPointsToResolve); } // Other object // May be complex object that contains statements - e.g. a multi-line conditional else { // Find any nested explicit gather points within this object // (including the object itself) // i.e. instances of "->" without a target that's meant to go // to the next gather point. var innerExplicitGathers = obj.FindAll<Divert> (d => d.isToGather); if (innerExplicitGathers.Count > 0) looseEnds.AddRange (innerExplicitGathers.ToArray()); // Add content AddGeneralRuntimeContent (obj.runtimeObject); } // Keep track of nested choices within this (possibly complex) object, // so that the next Gather knows whether to auto-enter // (it auto-enters when there are no choices) var innerChoices = obj.FindAll<Choice> (); if (innerChoices.Count > 0) hasSeenChoiceInSection = true; } } // Pass any loose ends up the hierarhcy PassLooseEndsToAncestors(); return _rootContainer; }
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); }
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 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 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()); } // 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()); } }
// 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 abstract void GenerateIntoContainer(Runtime.Container 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> ()) { 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); } // 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); }
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; }
public override void GenerateIntoContainer(Runtime.Container container) { var foundList = story.ResolveList(name); bool usingProxyDivert = false; if (isChoiceCount) { if (arguments.Count > 0) { Error("The CHOICE_COUNT() function shouldn't take any arguments"); } container.AddContent(Runtime.ControlCommand.ChoiceCount()); } else if (isTurns) { if (arguments.Count > 0) { Error("The TURNS() function shouldn't take any arguments"); } container.AddContent(Runtime.ControlCommand.Turns()); } else if (isTurnsSince || isReadCount) { var divertTarget = arguments [0] as DivertTarget; var variableDivertTarget = arguments [0] as VariableReference; if (arguments.Count != 1 || (divertTarget == null && variableDivertTarget == null)) { Error("The " + name + "() 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) { _divertTargetToCount = divertTarget; AddContent(_divertTargetToCount); _divertTargetToCount.GenerateIntoContainer(container); } else { _variableReferenceToCount = variableDivertTarget; AddContent(_variableReferenceToCount); _variableReferenceToCount.GenerateIntoContainer(container); } if (isTurnsSince) { container.AddContent(Runtime.ControlCommand.TurnsSince()); } else { container.AddContent(Runtime.ControlCommand.ReadCount()); } } 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()); } else if (isListRange) { if (arguments.Count != 3) { Error("LIST_RANGE should take 3 parameters - a list, a min and a max"); } for (int arg = 0; arg < arguments.Count; arg++) { arguments [arg].GenerateIntoContainer(container); } container.AddContent(Runtime.ControlCommand.ListRange()); } else if (isListRandom) { if (arguments.Count != 1) { Error("LIST_RANDOM should take 1 parameter - a list"); } arguments [0].GenerateIntoContainer(container); container.AddContent(Runtime.ControlCommand.ListRandom()); } else if (Runtime.NativeFunctionCall.CallExistsWithName(name)) { var nativeCall = Runtime.NativeFunctionCall.CallWithName(name); if (nativeCall.numberOfParameters != arguments.Count) { var msg = name + " should take " + nativeCall.numberOfParameters + " parameter"; if (nativeCall.numberOfParameters > 1) { msg += "s"; } Error(msg); } for (int arg = 0; arg < arguments.Count; arg++) { arguments [arg].GenerateIntoContainer(container); } container.AddContent(Runtime.NativeFunctionCall.CallWithName(name)); } else if (foundList != null) { if (arguments.Count > 1) { Error("Can currently only construct a list from one integer (or an empty list from a given list definition)"); } // List item from given int if (arguments.Count == 1) { container.AddContent(new Runtime.StringValue(name)); arguments [0].GenerateIntoContainer(container); container.AddContent(Runtime.ControlCommand.ListFromInt()); } // Empty list with given origin. else { var list = new Runtime.InkList(); list.SetInitialOriginName(name); container.AddContent(new Runtime.ListValue(list)); } } // Normal function call else { container.AddContent(_proxyDivert.runtimeObject); usingProxyDivert = true; } // Don't attempt to resolve as a divert if we're not doing a normal function call if (!usingProxyDivert) { content.Remove(_proxyDivert); } // 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()); } }
// 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); }
public override Runtime.Object GenerateRuntimeObject() { _rootContainer = currentContainer = new Runtime.Container(); looseEnds = new List <Parsed.Object> (); gatherPointsToResolve = new List <GatherPointToResolve> (); // Iterate through content for the block at this level of indentation // - Normal content is nested under Choices and Gathers // - Blocks that are further indented cause recursion // - Keep track of loose ends so that they can be diverted to Gathers foreach (var obj in content) { // Choice or Gather if (obj is IWeavePoint) { AddRuntimeForWeavePoint((IWeavePoint)obj); } // Non-weave point else { // Nested weave if (obj is Weave) { var weave = (Weave)obj; AddRuntimeForNestedWeave(weave); gatherPointsToResolve.AddRange(weave.gatherPointsToResolve); } // Other object // May be complex object that contains statements - e.g. a multi-line conditional else { // Find any nested explicit gather points within this object // (including the object itself) // i.e. instances of "->" without a target that's meant to go // to the next gather point. var innerExplicitGathers = obj.FindAll <Divert> (d => d.isToGather); if (innerExplicitGathers.Count > 0) { looseEnds.AddRange(innerExplicitGathers.ToArray()); } // Add content AddGeneralRuntimeContent(obj.runtimeObject); } // Keep track of nested choices within this (possibly complex) object, // so that the next Gather knows whether to auto-enter // (it auto-enters when there are no choices) var innerChoices = obj.FindAll <Choice> (); if (innerChoices.Count > 0) { hasSeenChoiceInSection = true; } } } // Pass any loose ends up the hierarhcy PassLooseEndsToAncestors(); return(_rootContainer); }
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; 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:", 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 () { // 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 // 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); }
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 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; } // List definitions are treated like constants too - they should be usable // from other variable declarations. _listDefs = new Dictionary <string, ListDefinition> (); foreach (var listDef in FindAll <ListDefinition> ()) { _listDefs [listDef.identifier?.name] = listDef; } externals = new Dictionary <string, ExternalDeclaration> (); // Resolution of weave point names has to come first, before any runtime code generation // since names have to be ready before diverts start getting created. // (It used to be done in the constructor for a weave, but didn't allow us to generate // errors when name resolution failed.) ResolveWeavePointNaming(); // 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 var runtimeLists = new List <Runtime.ListDefinition> (); foreach (var nameDeclPair in variableDeclarations) { var varName = nameDeclPair.Key; var varDecl = nameDeclPair.Value; if (varDecl.isGlobalDeclaration) { if (varDecl.listDefinition != null) { _listDefs[varName] = varDecl.listDefinition; variableInitialisation.AddContent(varDecl.listDefinition.runtimeObject); runtimeLists.Add(varDecl.listDefinition.runtimeListDefinition); } else { 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, runtimeLists); 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; }
public override void GenerateIntoContainer(Runtime.Container container) { innerExpression.GenerateIntoContainer(container); container.AddContent(Runtime.NativeFunctionCall.CallWithName(nativeNameForOp)); }
public void DontFlattenContainer(Runtime.Container container) { _dontFlattenContainers.Add(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 // 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() { 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); }
// 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); }