public override void ResolveReferences(Story context) { base.ResolveReferences(context); // Work is already done if it's a constant reference if (isConstantReference) { return; } // Is it a read count? var parsedPath = new Path(path); Parsed.Object targetForCount = parsedPath.ResolveFromContext(this); if (targetForCount) { targetForCount.containerForCounting.visitsShouldBeCounted = true; _runtimeVarRef.pathForCount = targetForCount.runtimePath; _runtimeVarRef.name = null; // Check for very specific writer error: getting read count and // printing it as content rather than as a piece of logic // e.g. Writing {myFunc} instead of {myFunc()} var targetFlow = targetForCount as FlowBase; if (targetFlow && targetFlow.isFunction) { // Is parent context content rather than logic? if (parent is Weave || parent is ContentList || parent is FlowBase) { Warning("'" + targetFlow.name + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()"); } } return; } // Definitely a read count, but wasn't found? else if (path.Count > 1) { Error("Could not find target for read count: " + parsedPath); return; } if (!context.ResolveVariableWithName(this.name, fromNode: this).found) { Error("Unresolved variable: " + this.ToString(), this); } }
public override void ResolveReferences(Story context) { if (_finalLooseEndTarget) { var flowEndPath = _finalLooseEndTarget.path; foreach (var finalLooseEndDivert in _finalLooseEnds) { finalLooseEndDivert.targetPath = flowEndPath; } } if (_startingSubFlowDivert) { _startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path; } base.ResolveReferences(context); // Check validity of parameter names if (arguments != null) { foreach (var arg in arguments) { // Don't allow reserved words for argument names if (VariableAssignment.IsReservedKeyword(arg.name)) { Error("Argument '" + arg.name + "' is a reserved word, please choose another name"); continue; } // Does argument conflict with a knot/stitch/label? var pathOfTheoreticalTarget = new Path(arg.name); Parsed.Object target = pathOfTheoreticalTarget.ResolveFromContext(this); if (target) { Error("Argument '" + arg.name + "' conflicts with a " + target.GetType().Name + " on " + target.debugMetadata + ", "); continue; } // Does argument conflict with another variable name? if (context.ResolveVariableWithName(arg.name, fromNode: this.parent).found) { Error("Argument '" + arg.name + "' conflicts with existing variable definition at higher scope."); continue; } } } }
public override void ResolveReferences (Story context) { base.ResolveReferences (context); // Work is already done if it's a constant reference if (isConstantReference) { return; } // Is it a read count? var parsedPath = new Path (path); Parsed.Object targetForCount = parsedPath.ResolveFromContext (this); if (targetForCount) { targetForCount.containerForCounting.visitsShouldBeCounted = true; _runtimeVarRef.pathForCount = targetForCount.runtimePath; _runtimeVarRef.name = null; // Check for very specific writer error: getting read count and // printing it as content rather than as a piece of logic // e.g. Writing {myFunc} instead of {myFunc()} var targetFlow = targetForCount as FlowBase; if (targetFlow && targetFlow.isFunction) { // Is parent context content rather than logic? if ( parent is Weave || parent is ContentList || parent is FlowBase) { Warning ("'" + targetFlow.name + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()"); } } return; } // Definitely a read count, but wasn't found? else if (path.Count > 1) { Error ("Could not find target for read count: " + parsedPath); return; } if (!context.ResolveVariableWithName (this.name, fromNode: this).found) { Error("Unresolved variable: "+this.ToString(), this); } }
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); } }
// 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 override void ResolveReferences(Story context) { base.ResolveReferences(context); // Work is already done if it's a constant or list item reference if (isConstantReference || isListItemReference) { return; } // Is it a read count? var parsedPath = new Path(pathIdentifiers); Parsed.Object targetForCount = parsedPath.ResolveFromContext(this); if (targetForCount) { targetForCount.containerForCounting.visitsShouldBeCounted = true; // If this is an argument to a function that wants a variable to be // passed by reference, then the Parsed.Divert will have generated a // Runtime.VariablePointerValue instead of allowing this object // to generate its RuntimeVariableReference. This only happens under // error condition since we shouldn't be passing a read count by // reference, but we don't want it to crash! if (_runtimeVarRef == null) { return; } _runtimeVarRef.pathForCount = targetForCount.runtimePath; _runtimeVarRef.name = null; // Check for very specific writer error: getting read count and // printing it as content rather than as a piece of logic // e.g. Writing {myFunc} instead of {myFunc()} var targetFlow = targetForCount as FlowBase; if (targetFlow && targetFlow.isFunction) { // Is parent context content rather than logic? if (parent is Weave || parent is ContentList || parent is FlowBase) { Warning("'" + targetFlow.identifier + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()"); } } return; } // Couldn't find this multi-part path at all, whether as a divert // target or as a list item reference. if (path.Count > 1) { var errorMsg = "Could not find target for read count: " + parsedPath; if (path.Count <= 2) { errorMsg += ", or couldn't find list item with the name " + string.Join(",", path.ToArray()); } Error(errorMsg); return; } if (!context.ResolveVariableWithName(this.name, fromNode: this).found) { Error("Unresolved variable: " + this.ToString(), this); } }
// 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; } } } } }
public override void ResolveReferences (Story context) { if (_finalLooseEndTarget) { var flowEndPath = _finalLooseEndTarget.path; foreach (var finalLooseEndDivert in _finalLooseEnds) { finalLooseEndDivert.targetPath = flowEndPath; } } if (_startingSubFlowDivert) { _startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path; } base.ResolveReferences(context); // Check validity of parameter names if (arguments != null) { foreach (var arg in arguments) { // Don't allow reserved words for argument names if (VariableAssignment.IsReservedKeyword (arg.name)) { Error ("Argument '" + arg.name + "' is a reserved word, please choose another name"); continue; } // Does argument conflict with a knot/stitch/label? var pathOfTheoreticalTarget = new Path (arg.name); Parsed.Object target = pathOfTheoreticalTarget.ResolveFromContext (this); if (target) { Error ("Argument '" + arg.name + "' conflicts with a " + target.GetType().Name + " on " + target.debugMetadata + ", "); continue; } // Does argument conflict with another variable name? if (context.ResolveVariableWithName (arg.name, fromNode: this.parent).found) { Error("Argument '"+ arg.name + "' conflicts with existing variable definition at higher scope."); continue; } } } }