// Find the final child from path given root, i.e.: // root.sub.finalChild Parsed.Object ResolveTailComponents(Parsed.Object rootTarget) { Parsed.Object foundComponent = rootTarget; for (int i = 1; i < _components.Count; ++i) { var compName = _components [i]; FlowLevel minimumExpectedLevel; var foundFlow = foundComponent as FlowBase; if (foundFlow != null) { minimumExpectedLevel = (FlowLevel)(foundFlow.flowLevel + 1); } else { minimumExpectedLevel = FlowLevel.WeavePoint; } foundComponent = TryGetChildFromContext(foundComponent, compName, minimumExpectedLevel); if (foundComponent == null) { break; } } return(foundComponent); }
void ValidateFlowOfObjectsTerminates(IEnumerable <Parsed.Object> objFlow, Parsed.Object defaultObj, BadTerminationHandler badTerminationHandler) { bool terminated = false; Parsed.Object terminatingObj = defaultObj; foreach (var flowObj in objFlow) { var divert = flowObj.Find <Divert> (d => !d.isThread && !d.isTunnel && !d.isFunctionCall && !(d.parent is DivertTarget)); if (divert != null) { terminated = true; } if (flowObj.Find <TunnelOnwards> () != null) { terminated = true; break; } terminatingObj = flowObj; } if (!terminated) { // Author has left a note to self here - clearly we don't need // to leave them with another warning since they know what they're doing. if (terminatingObj is AuthorWarning) { return; } badTerminationHandler(terminatingObj); } }
// See whether "context" contains a child with a given name at a given flow level // Can either be a named knot/stitch (a FlowBase) or a weave point within a Weave (Choice or Gather) // This function also ignores any other object types that are neither FlowBase nor Weave. // Called from both ResolveBase (force deep) and ResolveTail for the individual components. Parsed.Object TryGetChildFromContext(Parsed.Object context, string childName, FlowLevel?minimumLevel, bool forceDeepSearch = false) { // null childLevel means that we don't know where to find it bool ambiguousChildLevel = minimumLevel == null; // Search for WeavePoint within Weave var weaveContext = context as Weave; if (weaveContext != null && (ambiguousChildLevel || minimumLevel == FlowLevel.WeavePoint)) { return((Parsed.Object)weaveContext.WeavePointNamed(childName)); } // Search for content within Flow (either a sub-Flow or a WeavePoint) var flowContext = context as FlowBase; if (flowContext != null) { // When searching within a Knot, allow a deep searches so that // named weave points (choices and gathers) can be found within any stitch // Otherwise, we just search within the immediate object. var shouldDeepSearch = forceDeepSearch || flowContext.flowLevel == FlowLevel.Knot; return(flowContext.ContentWithNameAtLevel(childName, minimumLevel, shouldDeepSearch)); } return(null); }
void BadNestedTerminationHandler(Parsed.Object terminatingObj) { Conditional conditional = null; for (var ancestor = terminatingObj.parent; ancestor != null; ancestor = ancestor.parent) { if (ancestor is Sequence || ancestor is Conditional) { conditional = ancestor as Conditional; break; } } var errorMsg = "Choices nested in conditionals or sequences need to explicitly divert afterwards."; // Tutorialise proper choice syntax if this looks like a single choice within a condition, e.g. // { condition: // * choice // } if (conditional != null) { var numChoices = conditional.FindAll <Choice>().Count; if (numChoices == 1) { errorMsg = "Choices with conditions should be written: '* {condition} choice'. Otherwise, " + errorMsg.ToLower(); } } Error(errorMsg, terminatingObj); }
protected void DisallowIncrement(Parsed.Object expr) { if (expr is Parsed.IncDecExpression) { Error("Can't use increment/decrement here. It can only be used on a ~ line"); } }
public virtual void Error(string message, Parsed.Object source = null, bool isWarning = false) { if (source == null) { source = this; } // Only allow a single parsed object to have a single error *directly* associated with it if (source._alreadyHadError && !isWarning) { return; } if (source._alreadyHadWarning && isWarning) { return; } if (this.parent) { this.parent.Error(message, source, isWarning); } else { throw new System.Exception("No parent object to send error to: " + message); } if (isWarning) { source._alreadyHadWarning = true; } else { source._alreadyHadError = true; } }
public Parsed.Object ResolveFromContext(Parsed.Object context) { if (_components == null || _components.Count == 0) { return(null); } // Find base target of path from current context. e.g. // ==> BASE.sub.sub var baseTargetObject = ResolveBaseTarget(context); if (baseTargetObject == null) { return(null); } // Given base of path, resolve final target by working deeper into hierarchy // e.g. ==> base.mid.FINAL if (_components.Count > 1) { return(ResolveTailComponents(baseTargetObject)); } return(baseTargetObject); }
public Sequence(List <ContentList> elementContentLists, SequenceType sequenceType) { this.sequenceType = sequenceType; this.sequenceElements = new List <Parsed.Object> (); foreach (var elementContentList in elementContentLists) { var contentObjs = elementContentList.content; Parsed.Object seqElObject = null; // Don't attempt to create a weave for the sequence element // if the content list is empty. Weaves don't like it! if (contentObjs == null || contentObjs.Count == 0) { seqElObject = elementContentList; } else { seqElObject = new Weave(contentObjs); } this.sequenceElements.Add(seqElObject); AddContent(seqElObject); } }
// Find the root object from the base, i.e. root from: // root.sub1.sub2 Parsed.Object ResolveBaseTarget(Parsed.Object originalContext) { var firstComp = firstComponent; // Work up the ancestry to find the node that has the named object Parsed.Object ancestorContext = originalContext; while (ancestorContext != null) { // Only allow deep search when searching deeper from original context. // Don't allow search upward *then* downward, since that's searching *everywhere*! // Allowed examples: // - From an inner gather of a stitch, you should search up to find a knot called 'x' // at the root of a story, but not a stitch called 'x' in that knot. // - However, from within a knot, you should be able to find a gather/choice // anywhere called 'x' // (that latter example is quite loose, but we allow it) bool deepSearch = ancestorContext == originalContext; var foundBase = TryGetChildFromContext(ancestorContext, firstComp, null, deepSearch); if (foundBase != null) { return(foundBase); } ancestorContext = ancestorContext.parent; } return(null); }
void ConstructWeaveHierarchyFromIndentation() { // Find nested indentation and convert to a proper object hierarchy // (i.e. indented content is replaced with a Weave object that contains // that nested content) int contentIdx = 0; while (contentIdx < content.Count) { Parsed.Object obj = content [contentIdx]; // Choice or Gather if (obj is IWeavePoint) { var weavePoint = (IWeavePoint)obj; var weaveIndentIdx = weavePoint.indentationDepth - 1; // Inner level indentation - recurse if (weaveIndentIdx > baseIndentIndex) { // Step through content until indent jumps out again int innerWeaveStartIdx = contentIdx; while (contentIdx < content.Count) { var innerWeaveObj = content [contentIdx] as IWeavePoint; if (innerWeaveObj != null) { var innerIndentIdx = innerWeaveObj.indentationDepth - 1; if (innerIndentIdx <= baseIndentIndex) { break; } } contentIdx++; } int weaveContentCount = contentIdx - innerWeaveStartIdx; var weaveContent = content.GetRange(innerWeaveStartIdx, weaveContentCount); content.RemoveRange(innerWeaveStartIdx, weaveContentCount); var weave = new Weave(weaveContent, weaveIndentIdx); InsertContent(innerWeaveStartIdx, weave); // Continue iteration from this point contentIdx = innerWeaveStartIdx; } } contentIdx++; } }
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); }
public VariableResolveResult ResolveVariableWithName(string varName, Parsed.Object fromNode) { var result = new VariableResolveResult(); if (fromNode == null) { fromNode = this; } var ancestor = fromNode; while (ancestor) { if (ancestor is FlowBase) { var ancestorFlow = (FlowBase)ancestor; if (ancestorFlow.arguments != null) { foreach (var arg in ancestorFlow.arguments) { if (arg.name.Equals(varName)) { result.found = true; result.isArgument = true; result.ownerFlow = ancestorFlow; return(result); } } } if (ancestorFlow.variableDeclarations.ContainsKey(varName)) { result.found = true; result.ownerFlow = ancestorFlow; if (!(ancestorFlow is Story)) { result.isTemporary = true; } return(result); } } ancestor = ancestor.parent; } result.found = false; return(result); }
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); } }
void WarningInTermination(Parsed.Object terminatingObject, string additionalExplanation = null) { string message = "Apparent loose end exists where the flow runs out. Do you need a '-> DONE' statement, choice or divert?"; if (additionalExplanation != null) { message = message + " " + additionalExplanation; } if (_firstChildFlow) { message = message + " Note that if you intend to enter '" + _firstChildFlow.name + "' next, you need to divert to it explicitly."; } Warning(additionalExplanation == null ? message : message + " " + additionalExplanation, terminatingObject); }
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 Error(string message, Parsed.Object source, bool isWarning) { ErrorType errorType = isWarning ? ErrorType.Warning : ErrorType.Error; var sb = new StringBuilder(); if (source is AuthorWarning) { sb.Append("TODO: "); errorType = ErrorType.Author; } else if (isWarning) { sb.Append("WARNING: "); } else { sb.Append("ERROR: "); } if (source && source.debugMetadata != null && source.debugMetadata.startLineNumber >= 1) { if (source.debugMetadata.fileName != null) { sb.AppendFormat("'{0}' ", source.debugMetadata.fileName); } sb.AppendFormat("line {0}: ", source.debugMetadata.startLineNumber); } sb.Append(message); message = sb.ToString(); if (_errorHandler != null) { _errorHandler(message, errorType); } else { Console.WriteLine(message); } _hadError = errorType == ErrorType.Error; _hadWarning = errorType == ErrorType.Warning; }
public VariableResolveResult ResolveVariableWithName(string varName, Parsed.Object fromNode) { var result = new VariableResolveResult(); // Search in the stitch / knot that owns the node first var ownerFlow = fromNode == null ? this : fromNode.ClosestFlowBase(); // Argument if (ownerFlow.arguments != null) { foreach (var arg in ownerFlow.arguments) { if (arg.identifier.name.Equals(varName)) { result.found = true; result.isArgument = true; result.ownerFlow = ownerFlow; return(result); } } } // Temp var story = this.story; // optimisation if (ownerFlow != story && ownerFlow.variableDeclarations.ContainsKey(varName)) { result.found = true; result.ownerFlow = ownerFlow; result.isTemporary = true; return(result); } // Global if (story.variableDeclarations.ContainsKey(varName)) { result.found = true; result.ownerFlow = story; result.isGlobal = true; return(result); } result.found = false; return(result); }
void WarningInTermination(Parsed.Object terminatingObject) { string message = "Apparent loose end exists where the flow runs out. Do you need a '-> DONE' statement, choice or divert?"; if (terminatingObject.parent == _rootWeave && _firstChildFlow) { message = message + " Note that if you intend to enter '" + _firstChildFlow.identifier + "' next, you need to divert to it explicitly."; } var terminatingDivert = terminatingObject as Divert; if (terminatingDivert && terminatingDivert.isTunnel) { message = message + " When final tunnel to '" + terminatingDivert.target + " ->' returns it won't have anywhere to go."; } Warning(message, terminatingObject); }
// Find the root object from the base, i.e. root from: // root.sub1.sub2 Parsed.Object ResolveBaseTarget(Parsed.Object context) { var firstComp = firstComponent; // Work up the ancestry to find the node that has the named object while (context != null) { var foundBase = TryGetChildFromContext(context, firstComp, null, forceDeepSearch: true); if (foundBase != null) { return(foundBase); } context = context.parent; } return(null); }
// Global VARs and CONSTs are treated as "outside of the flow" // when iterating over content that follows loose ends bool IsGlobalDeclaration(Parsed.Object obj) { var varAss = obj as VariableAssignment; if (varAss && varAss.isGlobalDeclaration && varAss.isDeclaration) { return(true); } var constDecl = obj as ConstantDeclaration; if (constDecl) { return(true); } return(false); }
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 Divert(Parsed.Object targetContent) { this.targetContent = targetContent; }
// 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; }
void ResolveTargetContent() { if (isToGather || isEnd) { return; } if (targetContent == null) { // Is target of this divert a variable name that will be de-referenced // at runtime? If so, there won't be any further reference resolution // we can do at this point. var variableTargetName = PathAsVariableName (); if (variableTargetName != null) { var flowBaseScope = ClosestFlowBase (); var resolveResult = flowBaseScope.ResolveVariableWithName (variableTargetName, fromNode: this); if (resolveResult.found) { // Make sure that the flow was typed correctly, given that we know that this // is meant to be a divert target if (resolveResult.isArgument) { var argument = resolveResult.ownerFlow.arguments.Where (a => a.name == variableTargetName).First(); if ( !argument.isDivertTarget ) { Error ("Since '" + argument.name + "' is used as a variable divert target (on "+this.debugMetadata+"), it should be marked as: -> " + argument.name, resolveResult.ownerFlow); } } runtimeDivert.variableDivertName = variableTargetName; return; } } targetContent = target.ResolveFromContext (this); } }
public override void ResolveReferences(Story context) { base.ResolveReferences(context); Parsed.Object usageContext = this; while (usageContext && usageContext is Expression) { bool badUsage = false; bool foundUsage = false; var usageParent = usageContext.parent; if (usageParent is BinaryExpression) { // Only allowed to compare for equality var binaryExprParent = usageParent as BinaryExpression; if (binaryExprParent.opName != "==") { badUsage = true; } else { if (!(binaryExprParent.leftExpression is DivertTarget || binaryExprParent.leftExpression is VariableReference)) { badUsage = true; } if (!(binaryExprParent.leftExpression is DivertTarget || binaryExprParent.leftExpression is VariableReference)) { badUsage = true; } } foundUsage = true; } else if (usageParent is FunctionCall) { var funcCall = usageParent as FunctionCall; if (!funcCall.isTurnsSince) { badUsage = true; } foundUsage = true; } else if (usageParent is Expression) { badUsage = true; foundUsage = true; } else if (usageParent is MultipleConditionExpression) { badUsage = true; foundUsage = true; } else if (usageParent is Choice && ((Choice)usageParent).condition == usageContext) { badUsage = true; foundUsage = true; } else if (usageParent is Conditional || usageParent is ConditionalSingleBranch) { badUsage = true; foundUsage = true; } if (badUsage) { Error("Can't use a divert target like that. Did you intend to call '" + divert.target + "' as a function: likeThis(), or check the read count: likeThis, with no arrows?", this); } if (foundUsage) { break; } usageContext = usageParent; } _runtimeLiteralDivertTarget.targetPath = _runtimeDivert.targetPath; }
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 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); } }
public override void ResolveReferences(IFiction context) { base.ResolveReferences(context); if (divert.isDone || divert.isEnd) { Error("Can't Can't use -> DONE or -> END as variable divert targets", this); return; } Parsed.Object usageContext = this; while (usageContext && usageContext is Expression) { bool badUsage = false; bool foundUsage = false; var usageParent = usageContext.parent; if (usageParent is BinaryExpression) { // Only allowed to compare for equality var binaryExprParent = usageParent as BinaryExpression; if (binaryExprParent.opName != "==" && binaryExprParent.opName != "!=") { badUsage = true; } else { if (!(binaryExprParent.leftExpression is DivertTarget || binaryExprParent.leftExpression is VariableReference)) { badUsage = true; } if (!(binaryExprParent.rightExpression is DivertTarget || binaryExprParent.rightExpression is VariableReference)) { badUsage = true; } } foundUsage = true; } else if (usageParent is FunctionCall) { var funcCall = usageParent as FunctionCall; if (!funcCall.isTurnsSince && !funcCall.isReadCount) { badUsage = true; } foundUsage = true; } else if (usageParent is Expression) { badUsage = true; foundUsage = true; } else if (usageParent is MultipleConditionExpression) { badUsage = true; foundUsage = true; } else if (usageParent is Choice && ((Choice)usageParent).condition == usageContext) { badUsage = true; foundUsage = true; } else if (usageParent is Conditional || usageParent is ConditionalSingleBranch) { badUsage = true; foundUsage = true; } if (badUsage) { Error("Can't use a divert target like that. Did you intend to call '" + divert.target + "' as a function: likeThis(), or check the read count: likeThis, with no arrows?", this); } if (foundUsage) { break; } usageContext = usageParent; } // Example ink for this case: // // VAR x = -> blah // // ...which means that "blah" is expected to be a literal stitch target rather // than a variable name. We can't really intelligently recover from this (e.g. if blah happens to // contain a divert target itself) since really we should be generating a variable reference // rather than a concrete DivertTarget, so we list it as an error. if (_runtimeDivert.hasVariableTarget) { Error("Since '" + divert.target.dotSeparatedComponents + "' is a variable, it shouldn't be preceded by '->' here."); } // Main resolve _runtimeDivertTargetValue.targetPath = _runtimeDivert.targetPath; // Tell hard coded (yet variable) divert targets that they also need to be counted // TODO: Only detect DivertTargets that are values rather than being used directly for // read or turn counts. Should be able to detect this by looking for other uses of containerForCounting var targetContent = this.divert.targetContent; if (targetContent != null) { var target = targetContent.containerForCounting; if (target != null) { // Purpose is known: used directly in TURNS_SINCE(-> divTarg) var parentFunc = this.parent as FunctionCall; if (parentFunc && parentFunc.isTurnsSince) { target.turnIndexShouldBeCounted = true; } // Unknown purpose, count everything else { target.visitsShouldBeCounted = true; target.turnIndexShouldBeCounted = true; } } // Unfortunately not possible: // https://github.com/inkle/ink/issues/538 // // VAR func = -> double // // === function double(ref x) // ~ x = x * 2 // // Because when generating the parameters for a function // to be called, it needs to know ahead of time when // compiling whether to pass a variable reference or value. // var targetFlow = (targetContent as FlowBase); if (targetFlow != null && targetFlow.arguments != null) { foreach (var arg in targetFlow.arguments) { if (arg.isByReference) { Error("Can't store a divert target to a knot or function that has by-reference arguments ('" + targetFlow.name + "' has 'ref " + arg.name + "')."); } } } } }
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); }
public void Warning(string message, Parsed.Object source = null) { Error(message, source, isWarning: true); }
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); }