Пример #1
0
        // 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);
        }
Пример #2
0
        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);
            }
        }
Пример #3
0
        // 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);
        }
Пример #4
0
        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");
     }
 }
Пример #6
0
        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;
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
            }
        }
Пример #9
0
        // 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);
        }
Пример #10
0
        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++;
            }
        }
Пример #11
0
        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);
        }
Пример #12
0
        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);
        }
Пример #13
0
        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);
            }
        }
Пример #14
0
        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);
        }
Пример #15
0
        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;
                    }
                }
            }
        }
Пример #16
0
        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;
        }
Пример #17
0
        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);
        }
Пример #18
0
        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);
        }
Пример #19
0
        // 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);
        }
Пример #20
0
        // 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);
        }
Пример #21
0
        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);
            }
        }
Пример #22
0
 public Divert(Parsed.Object targetContent)
 {
     this.targetContent = targetContent;
 }
Пример #23
0
        // 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;
        }
Пример #24
0
        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);
            }
        }
Пример #25
0
        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;
        }
Пример #26
0
        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);
        }
Пример #27
0
        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);
            }
        }
Пример #28
0
        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 + "').");
                        }
                    }
                }
            }
        }
Пример #29
0
        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);
        }
Пример #30
0
 public void Warning(string message, Parsed.Object source = null)
 {
     Error(message, source, isWarning: true);
 }
Пример #31
0
        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);
        }