using System.Collections.Generic; namespace Ink.Parsed { public class VariableAssignment : Parsed.Object { public string variableName { get { return variableIdentifier.name; } } public Identifier variableIdentifier { get; protected set; } public Expression expression { get; protected set; } public ListDefinition listDefinition { get; protected set; } public bool isGlobalDeclaration { get; set; } public bool isNewTemporaryDeclaration { get; set; } public bool isDeclaration { get { return isGlobalDeclaration || isNewTemporaryDeclaration; } } public VariableAssignment (Identifier identifier, Expression assignedExpression) { this.variableIdentifier = identifier; // Defensive programming in case parsing of assignedExpression failed if( assignedExpression ) this.expression = AddContent(assignedExpression); } public VariableAssignment (Identifier identifier, ListDefinition listDef) { this.variableIdentifier = identifier; if (listDef) { this.listDefinition = AddContent (listDef); this.listDefinition.variableAssignment = this; } // List definitions are always global isGlobalDeclaration = true; } 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 ResolveReferences (Story context) { base.ResolveReferences (context); // List definitions are checked for conflicts separately if( this.isDeclaration && listDefinition == null ) context.CheckForNamingCollisions (this, variableIdentifier, this.isGlobalDeclaration ? Story.SymbolType.Var : Story.SymbolType.Temp); // Initial VAR x = [intialValue] declaration, not re-assignment if (this.isGlobalDeclaration) { var variableReference = expression as VariableReference; if (variableReference && !variableReference.isConstantReference && !variableReference.isListItemReference) { Error ("global variable assignments cannot refer to other variables, only literal values, constants and list items"); } } if (!this.isNewTemporaryDeclaration) { var resolvedVarAssignment = context.ResolveVariableWithName(this.variableName, fromNode: this); if (!resolvedVarAssignment.found) { if (story.constants.ContainsKey (variableName)) { Error ("Can't re-assign to a constant (do you need to use VAR when declaring '" + this.variableName + "'?)", this); } else { Error ("Variable could not be found to assign to: '" + this.variableName + "'", this); } } // A runtime assignment may not have been generated if it's the initial global declaration, // since these are hoisted out and handled specially in Story.ExportRuntime. if( _runtimeAssignment != null ) _runtimeAssignment.isGlobal = resolvedVarAssignment.isGlobal; } } public override string typeName { get { if (isNewTemporaryDeclaration) return "temp"; else if (isGlobalDeclaration) return "VAR"; else return "variable assignment"; } } Runtime.VariableAssignment _runtimeAssignment; } }