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(varIdentifier?.name)); // 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. _runtimeAssignment = new Runtime.VariableAssignment(varIdentifier?.name, false); container.AddContent(_runtimeAssignment); }
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 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 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); }
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); }
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 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 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; }
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); } }