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 Parsed.Object ContentWithNameAtLevel(string name, FlowLevel?level = null, bool deepSearch = false) { // Referencing self? if (level == this.flowLevel || level == null) { if (name == this.name) { return(this); } } if (level == FlowLevel.WeavePoint || level == null) { Parsed.Object weavePointResult = null; if (_rootWeave) { weavePointResult = (Parsed.Object)_rootWeave.WeavePointNamed(name); if (weavePointResult) { return(weavePointResult); } } // Stop now if we only wanted a result if it's a weave point? if (level == FlowLevel.WeavePoint) { return(deepSearch ? DeepSearchForAnyLevelContent(name) : null); } } // If this flow would be incapable of containing the requested level, early out // (e.g. asking for a Knot from a Stitch) if (level != null && level < this.flowLevel) { return(null); } FlowBase subFlow = null; if (_subFlowsByName.TryGetValue(name, out subFlow)) { if (level == null || level == subFlow.flowLevel) { return(subFlow); } } return(deepSearch ? DeepSearchForAnyLevelContent(name) : null); }
// Returns false if there's an error void CheckArgumentValidity() { if (isEmpty) { return; } // Argument passing: Check for errors in number of arguments var numArgs = 0; if (arguments != null && arguments.Count > 0) { numArgs = arguments.Count; } // Missing content? // Can't check arguments properly. It'll be due to some // other error though, so although there's a problem and // we report false, we don't need to report a specific error. // It may also be because it's a valid call to an external // function, that we check at the resolve stage. if (targetContent == null) { return; } FlowBase targetFlow = targetContent as FlowBase; // No error, crikey! if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters)) { return; } if (targetFlow == null && numArgs > 0) { Error("target needs to be a knot or stitch in order to pass arguments"); return; } if (targetFlow.arguments == null && numArgs > 0) { Error("target (" + targetFlow.name + ") doesn't take parameters"); return; } if (this.parent is DivertTarget) { if (numArgs > 0) { Error("can't store arguments in a divert target variable"); } return; } var paramCount = targetFlow.arguments.Count; if (paramCount != numArgs) { string butClause; if (numArgs == 0) { butClause = "but there weren't any passed to it"; } else if (numArgs < paramCount) { butClause = "but only got " + numArgs; } else { butClause = "but got " + numArgs; } Error("to '" + targetFlow.name + "' requires " + paramCount + " arguments, " + butClause); return; } // Light type-checking for divert target arguments for (int i = 0; i < paramCount; ++i) { FlowBase.Argument flowArg = targetFlow.arguments [i]; Parsed.Expression divArgExpr = arguments [i]; // Expecting a divert target as an argument, let's do some basic type checking if (flowArg.isDivertTarget) { // Not passing a divert target or any kind of variable reference? var varRef = divArgExpr as VariableReference; if (!(divArgExpr is DivertTarget) && varRef == null) { Error("Target '" + targetFlow.name + "' expects a divert target for the parameter named -> " + flowArg.name + " but saw " + divArgExpr, divArgExpr); } // Passing 'a' instead of '-> a'? // i.e. read count instead of divert target else if (varRef != null) { // Unfortunately have to manually resolve here since we're still in code gen var knotCountPath = new Path(varRef.path); Parsed.Object targetForCount = knotCountPath.ResolveFromContext(varRef); if (targetForCount != null) { Error("Passing read count of '" + knotCountPath.dotSeparatedComponents + "' instead of a divert target. You probably meant '" + knotCountPath + "'"); } } } } if (targetFlow == null) { Error("Can't call as a function or with arguments unless it's a knot or stitch"); return; } return; }
public Parsed.Path PathRelativeTo(Parsed.Object otherObj) { var ownAncestry = ancestry; var otherAncestry = otherObj.ancestry; Parsed.Object highestCommonAncestor = null; int minLength = System.Math.Min(ownAncestry.Count, otherAncestry.Count); for (int i = 0; i < minLength; ++i) { var a1 = ancestry [i]; var a2 = otherAncestry [i]; if (a1 == a2) { highestCommonAncestor = a1; } else { break; } } FlowBase commonFlowAncestor = highestCommonAncestor as FlowBase; if (commonFlowAncestor == null) { commonFlowAncestor = highestCommonAncestor.ClosestFlowBase(); } var pathComponents = new List <string> (); bool hasWeavePoint = false; FlowLevel baseFlow = FlowLevel.WeavePoint; var ancestor = this; while (ancestor && (ancestor != commonFlowAncestor) && !(ancestor is Fiction)) { if (ancestor == commonFlowAncestor) { break; } if (!hasWeavePoint) { var weavePointAncestor = ancestor as IWeavePoint; if (weavePointAncestor != null && weavePointAncestor.name != null) { pathComponents.Add(weavePointAncestor.name); hasWeavePoint = true; continue; } } var flowAncestor = ancestor as FlowBase; if (flowAncestor) { pathComponents.Add(flowAncestor.name); baseFlow = flowAncestor.flowLevel; } ancestor = ancestor.parent; } pathComponents.Reverse(); if (pathComponents.Count > 0) { return(new Path(baseFlow, pathComponents)); } return(null); }
// Check given symbol type against everything that's of a higher priority in the ordered SymbolType enum (above). // When the given symbol type level is reached, we early-out / return. public void CheckForNamingCollisions(Parsed.Object obj, Identifier identifier, SymbolType symbolType, string typeNameOverride = null) { string typeNameToPrint = typeNameOverride ?? obj.typeName; if (IsReservedKeyword(identifier?.name)) { obj.Error("'" + name + "' cannot be used for the name of a " + typeNameToPrint.ToLower() + " because it's a reserved keyword"); return; } if (FunctionCall.IsBuiltIn(identifier?.name)) { obj.Error("'" + name + "' cannot be used for the name of a " + typeNameToPrint.ToLower() + " because it's a built in function"); return; } // Top level knots FlowBase knotOrFunction = ContentWithNameAtLevel(identifier?.name, FlowLevel.Knot) as FlowBase; if (knotOrFunction && (knotOrFunction != obj || symbolType == SymbolType.Arg)) { NameConflictError(obj, identifier?.name, knotOrFunction, typeNameToPrint); return; } if (symbolType < SymbolType.List) { return; } // Lists foreach (var namedListDef in _listDefs) { var listDefName = namedListDef.Key; var listDef = namedListDef.Value; if (identifier?.name == listDefName && obj != listDef && listDef.variableAssignment != obj) { NameConflictError(obj, identifier?.name, listDef, typeNameToPrint); } // We don't check for conflicts between individual elements in // different lists because they are namespaced. if (!(obj is ListElementDefinition)) { foreach (var item in listDef.itemDefinitions) { if (identifier?.name == item.name) { NameConflictError(obj, identifier?.name, item, typeNameToPrint); } } } } // Don't check for VAR->VAR conflicts because that's handled separately // (necessary since checking looks up in a dictionary) if (symbolType <= SymbolType.Var) { return; } // Global variable collision VariableAssignment varDecl = null; if (variableDeclarations.TryGetValue(identifier?.name, out varDecl)) { if (varDecl != obj && varDecl.isGlobalDeclaration && varDecl.listDefinition == null) { NameConflictError(obj, identifier?.name, varDecl, typeNameToPrint); } } if (symbolType < SymbolType.SubFlowAndWeave) { return; } // Stitches, Choices and Gathers var path = new Path(identifier); var targetContent = path.ResolveFromContext(obj); if (targetContent && targetContent != obj) { NameConflictError(obj, identifier?.name, targetContent, typeNameToPrint); return; } if (symbolType < SymbolType.Arg) { return; } // Arguments to the current flow if (symbolType != SymbolType.Arg) { FlowBase flow = obj as FlowBase; if (flow == null) { flow = obj.ClosestFlowBase(); } if (flow && flow.hasParameters) { foreach (var arg in flow.arguments) { if (arg.identifier?.name == identifier?.name) { obj.Error(typeNameToPrint + " '" + name + "': Name has already been used for a argument to " + flow.identifier + " on " + flow.debugMetadata); return; } } } } }
// Returns false if there's an error void CheckArgumentValidity() { if (isToGather) { return; } // Argument passing: Check for errors in number of arguments var numArgs = 0; if (arguments != null && arguments.Count > 0) { numArgs = arguments.Count; } // Missing content? // Can't check arguments properly. It'll be due to some // other error though, so although there's a problem and // we report false, we don't need to report a specific error. // It may also be because it's a valid call to an external // function, that we check at the resolve stage. if (targetContent == null) { return; } FlowBase targetFlow = targetContent as FlowBase; // No error, crikey! if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters)) { return; } if (targetFlow == null && numArgs > 0) { Error("target needs to be a knot or stitch in order to pass arguments"); return; } if (targetFlow.arguments == null && numArgs > 0) { Error("target (" + targetFlow.name + ") doesn't take parameters"); return; } var paramCount = targetFlow.arguments.Count; if (paramCount > 0 && this.parent is DivertTarget) { Error("Can't store a link to a knot that takes parameters in a variable"); return; } if (paramCount != numArgs) { string butClause; if (numArgs == 0) { butClause = "but there weren't any passed to it"; } else if (numArgs < paramCount) { butClause = "but only got " + numArgs; } else { butClause = "but got " + numArgs; } Error("to '" + targetFlow.name + "' requires " + paramCount + " arguments, " + butClause); return; } // Light type-checking for divert target arguments for (int i = 0; i < paramCount; ++i) { FlowBase.Argument flowArg = targetFlow.arguments [i]; Parsed.Expression divArgExpr = arguments [i]; if (flowArg.isDivertTarget) { if (!(divArgExpr is VariableReference || divArgExpr is DivertTarget)) { Error("Target '" + targetFlow.name + "' expects a divert target for the parameter named -> " + flowArg.name + " but saw " + divArgExpr, divArgExpr); } } } if (targetFlow == null) { Error("Can't call as a function or with arguments unless it's a knot or stitch"); return; } return; }