Ejemplo n.º 1
0
        // Generate runtime code that looks like:
        //   chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping"
        //   if chosenIndex == 0, divert to s0
        //   if chosenIndex == 1, divert to s1  [etc]
        //   increment sequence
        //
        //   - s0:
        //      <content for sequence element>
        //      divert back to increment point
        //   - s1:
        //      <content for sequence element>
        //      divert back to increment point
        //
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            container.visitsShouldBeCounted = true;
            container.countingAtStartOnly   = true;

            _sequenceDivertsToResove = new List <SequenceDivertToResolve> ();

            // Get sequence read count
            container.AddContent(Runtime.ControlCommand.EvalStart());
            container.AddContent(Runtime.ControlCommand.VisitIndex());

            // Chosen sequence index:
            //  - Stopping: take the MIN(read count, num elements - 1)
            if (sequenceType == SequenceType.Stopping)
            {
                container.AddContent(new Runtime.LiteralInt(sequenceElements.Count - 1));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("MIN"));
            }

            // - Cycle: take (read count % num elements)
            else if (sequenceType == SequenceType.Cycle)
            {
                container.AddContent(new Runtime.LiteralInt(sequenceElements.Count));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("%"));
            }

            // Once: allow sequence count to be unbounded
            else if (sequenceType == SequenceType.Once)
            {
                // Do nothing - the sequence count will simply prevent
                // any content being referenced when it goes out of bounds
            }

            // Shuffle
            else if (sequenceType == SequenceType.Shuffle)
            {
                // This one's a bit more complex! Choose the index at runtime.
                container.AddContent(new Runtime.LiteralInt(sequenceElements.Count));
                container.AddContent(Runtime.ControlCommand.SequenceShuffleIndex());
            }

            // Not implemented
            else
            {
                throw new System.NotImplementedException();
            }

            container.AddContent(Runtime.ControlCommand.EvalEnd());

            // Create point to return to when sequence is complete
            var postSequenceNoOp = Runtime.ControlCommand.NoOp();

            var elIndex = 0;

            foreach (var el in sequenceElements)
            {
                // This sequence element:
                //  if( chosenIndex == this index ) divert to this sequence element
                // duplicate chosen sequence index, since it'll be consumed by "=="
                container.AddContent(Runtime.ControlCommand.EvalStart());
                container.AddContent(Runtime.ControlCommand.Duplicate());
                container.AddContent(new Runtime.LiteralInt(elIndex));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
                container.AddContent(Runtime.ControlCommand.EvalEnd());

                // Divert branch for this sequence element
                var sequenceDivert = new Runtime.Divert();
                container.AddContent(new Runtime.Branch(sequenceDivert));

                // Generate content for this sequence element
                var contentContainerForSequenceBranch = (Runtime.Container)el.runtimeObject;
                contentContainerForSequenceBranch.name = "s" + elIndex;

                // When sequence element is complete, divert back to end of sequence
                var seqBranchCompleteDivert = new Runtime.Divert();
                contentContainerForSequenceBranch.AddContent(seqBranchCompleteDivert);
                container.AddToNamedContentOnly(contentContainerForSequenceBranch);

                // Save the diverts for reference resolution later (in ResolveReferences)
                AddDivertToResolve(sequenceDivert, contentContainerForSequenceBranch);
                AddDivertToResolve(seqBranchCompleteDivert, postSequenceNoOp);

                elIndex++;
            }

            container.AddContent(postSequenceNoOp);

            return(container);
        }
Ejemplo n.º 2
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            _outerContainer = new Runtime.Container();

            // Content names for different types of choice:
            //  * start content [choice only content] inner content
            //  * start content   -> divert
            //  * start content
            //  * [choice only content]

            // Hmm, this structure has become slightly insane!
            //
            // [
            //     EvalStart
            //     assign $r = $r1   -- return target = return label 1
            //     BeginString
            //     -> s
            //     [(r1)]            -- return label 1 (after start content)
            //     EndString
            //     BeginString
            //     ... choice only content
            //     EndEval
            //     Condition expression
            //     choice: -> "c-0"
            //     (s) = [
            //         start content
            //         -> r          -- goto return label 1 or 2
            //     ]
            //  ]
            //
            //  in parent's container: (the inner content for the choice)
            //
            //  (c-0) = [
            //      EvalStart
            //      assign $r = $r2   -- return target = return label 2
            //      EndEval
            //      -> s
            //      [(r2)]            -- return label 1 (after start content)
            //      inner content
            //  ]
            //

            _runtimeChoice = new Runtime.ChoicePoint(onceOnly);
            _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault;

            if (startContent || choiceOnlyContent || condition)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.EvalStart());
            }

            // Start content is put into a named container that's referenced both
            // when displaying the choice initially, and when generating the text
            // when the choice is chosen.
            if (startContent)
            {
                // Generate start content and return
                //  - We can't use a function since it uses a call stack element, which would
                //    put temporary values out of scope. Instead we manually divert around.
                //  - $r is a variable divert target contains the return point
                _returnToR1 = new Runtime.DivertTargetValue();
                _outerContainer.AddContent(_returnToR1);
                var varAssign = new Runtime.VariableAssignment("$r", true);
                _outerContainer.AddContent(varAssign);

                // Mark the start of the choice text generation, so that the runtime
                // knows where to rewind to to extract the content from the output stream.
                _outerContainer.AddContent(Runtime.ControlCommand.BeginString());

                _divertToStartContentOuter = new Runtime.Divert();
                _outerContainer.AddContent(_divertToStartContentOuter);

                // Start content itself in a named container
                _startContentRuntimeContainer      = startContent.GenerateRuntimeObject() as Runtime.Container;
                _startContentRuntimeContainer.name = "s";

                // Effectively, the "return" statement - return to the point specified by $r
                var varDivert = new Runtime.Divert();
                varDivert.variableDivertName = "$r";
                _startContentRuntimeContainer.AddContent(varDivert);

                // Add the container
                _outerContainer.AddToNamedContentOnly(_startContentRuntimeContainer);

                // This is the label to return to
                _r1Label      = new Runtime.Container();
                _r1Label.name = "$r1";
                _outerContainer.AddContent(_r1Label);

                _outerContainer.AddContent(Runtime.ControlCommand.EndString());

                _runtimeChoice.hasStartContent = true;
            }

            // Choice only content - mark the start, then generate it directly into the outer container
            if (choiceOnlyContent)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.BeginString());

                var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject() as Runtime.Container;
                _outerContainer.AddContentsOfContainer(choiceOnlyRuntimeContent);

                _outerContainer.AddContent(Runtime.ControlCommand.EndString());

                _runtimeChoice.hasChoiceOnlyContent = true;
            }

            // Generate any condition for this choice
            if (condition)
            {
                condition.GenerateIntoContainer(_outerContainer);
                _runtimeChoice.hasCondition = true;
            }

            if (startContent || choiceOnlyContent || condition)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.EvalEnd());
            }

            // Add choice itself
            _outerContainer.AddContent(_runtimeChoice);

            // Container that choice points to for when it's chosen
            _innerContentContainer = new Runtime.Container();

            // Repeat start content by diverting to its container
            if (startContent)
            {
                // Set the return point when jumping back into the start content
                //  - In this case, it's the $r2 point, within the choice content "c".
                _returnToR2 = new Runtime.DivertTargetValue();
                _innerContentContainer.AddContent(Runtime.ControlCommand.EvalStart());
                _innerContentContainer.AddContent(_returnToR2);
                _innerContentContainer.AddContent(Runtime.ControlCommand.EvalEnd());
                var varAssign = new Runtime.VariableAssignment("$r", true);
                _innerContentContainer.AddContent(varAssign);

                // Main divert into start content
                _divertToStartContentInner = new Runtime.Divert();
                _innerContentContainer.AddContent(_divertToStartContentInner);

                // Define label to return to
                _r2Label      = new Runtime.Container();
                _r2Label.name = "$r2";
                _innerContentContainer.AddContent(_r2Label);
            }

            // Choice's own inner content
            if (innerContent)
            {
                var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject() as Runtime.Container;
                _innerContentContainer.AddContentsOfContainer(innerChoiceOnlyContent);
            }

            if (this.story.countAllVisits)
            {
                _innerContentContainer.visitsShouldBeCounted = true;
            }

            _innerContentContainer.countingAtStartOnly = true;

            return(_outerContainer);
        }
Ejemplo n.º 3
0
        // Generate runtime code that looks like:
        //
        //   chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping"
        //   if chosenIndex == 0, divert to s0
        //   if chosenIndex == 1, divert to s1  [etc]
        //
        //   - s0:
        //      <content for sequence element>
        //      divert to no-op
        //   - s1:
        //      <content for sequence element>
        //      divert to no-op
        //   - s2:
        //      empty branch if using "once"
        //      divert to no-op
        //
        //    no-op
        //
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            container.visitsShouldBeCounted = true;
            container.countingAtStartOnly   = true;

            _sequenceDivertsToResove = new List <SequenceDivertToResolve> ();

            // Get sequence read count
            container.AddContent(Runtime.ControlCommand.EvalStart());
            container.AddContent(Runtime.ControlCommand.VisitIndex());

            bool once     = (sequenceType & SequenceType.Once) > 0;
            bool cycle    = (sequenceType & SequenceType.Cycle) > 0;
            bool stopping = (sequenceType & SequenceType.Stopping) > 0;
            bool shuffle  = (sequenceType & SequenceType.Shuffle) > 0;

            var seqBranchCount = sequenceElements.Count;

            if (once)
            {
                seqBranchCount++;
            }

            // Chosen sequence index:
            //  - Stopping: take the MIN(read count, num elements - 1)
            //  - Once: take the MIN(read count, num elements)
            //    (the last one being empty)
            if (stopping || once)
            {
                //var limit = stopping ? seqBranchCount-1 : seqBranchCount;
                container.AddContent(new Runtime.IntValue(seqBranchCount - 1));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("MIN"));
            }

            // - Cycle: take (read count % num elements)
            else if (cycle)
            {
                container.AddContent(new Runtime.IntValue(sequenceElements.Count));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("%"));
            }

            // Shuffle
            if (shuffle)
            {
                // Create point to return to when sequence is complete
                var postShuffleNoOp = Runtime.ControlCommand.NoOp();

                // When visitIndex == lastIdx, we skip the shuffle
                if (once || stopping)
                {
                    // if( visitIndex == lastIdx ) -> skipShuffle
                    int lastIdx = stopping ? sequenceElements.Count - 1 : sequenceElements.Count;
                    container.AddContent(Runtime.ControlCommand.Duplicate());
                    container.AddContent(new Runtime.IntValue(lastIdx));
                    container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));

                    var skipShuffleDivert = new Runtime.Divert();
                    skipShuffleDivert.isConditional = true;
                    container.AddContent(skipShuffleDivert);

                    AddDivertToResolve(skipShuffleDivert, postShuffleNoOp);
                }

                // This one's a bit more complex! Choose the index at runtime.
                var elementCountToShuffle = sequenceElements.Count;
                if (stopping)
                {
                    elementCountToShuffle--;
                }
                container.AddContent(new Runtime.IntValue(elementCountToShuffle));
                container.AddContent(Runtime.ControlCommand.SequenceShuffleIndex());
                if (once || stopping)
                {
                    container.AddContent(postShuffleNoOp);
                }
            }

            container.AddContent(Runtime.ControlCommand.EvalEnd());

            // Create point to return to when sequence is complete
            var postSequenceNoOp = Runtime.ControlCommand.NoOp();

            // Each of the main sequence branches, and one extra empty branch if
            // we have a "once" sequence.
            for (var elIndex = 0; elIndex < seqBranchCount; elIndex++)
            {
                // This sequence element:
                //  if( chosenIndex == this index ) divert to this sequence element
                // duplicate chosen sequence index, since it'll be consumed by "=="
                container.AddContent(Runtime.ControlCommand.EvalStart());
                container.AddContent(Runtime.ControlCommand.Duplicate());
                container.AddContent(new Runtime.IntValue(elIndex));
                container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
                container.AddContent(Runtime.ControlCommand.EvalEnd());

                // Divert branch for this sequence element
                var sequenceDivert = new Runtime.Divert();
                sequenceDivert.isConditional = true;
                container.AddContent(sequenceDivert);

                Runtime.Container contentContainerForSequenceBranch;

                // Generate content for this sequence element
                if (elIndex < sequenceElements.Count)
                {
                    var el = sequenceElements[elIndex];
                    contentContainerForSequenceBranch = (Runtime.Container)el.runtimeObject;
                }

                // Final empty branch for "once" sequences
                else
                {
                    contentContainerForSequenceBranch = new Runtime.Container();
                }

                contentContainerForSequenceBranch.name = "s" + elIndex;
                contentContainerForSequenceBranch.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0);

                // When sequence element is complete, divert back to end of sequence
                var seqBranchCompleteDivert = new Runtime.Divert();
                contentContainerForSequenceBranch.AddContent(seqBranchCompleteDivert);
                container.AddToNamedContentOnly(contentContainerForSequenceBranch);

                // Save the diverts for reference resolution later (in ResolveReferences)
                AddDivertToResolve(sequenceDivert, contentContainerForSequenceBranch);
                AddDivertToResolve(seqBranchCompleteDivert, postSequenceNoOp);
            }

            container.AddContent(postSequenceNoOp);

            return(container);
        }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
0
        // Generate runtime code that looks like:
        //   chosenIndex = MIN(sequence counter, num elements) e.g. for "Stopping"
        //   if chosenIndex == 0, divert to s0
        //   if chosenIndex == 1, divert to s1  [etc]
        //   increment sequence
        //
        //   - s0:
        //      <content for sequence element>
        //      divert back to increment point
        //   - s1:
        //      <content for sequence element>
        //      divert back to increment point
        //
        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();
            container.visitsShouldBeCounted = true;
            container.countingAtStartOnly = true;

            _sequenceDivertsToResove = new List<SequenceDivertToResolve> ();

            // Get sequence read count
            container.AddContent (Runtime.ControlCommand.EvalStart ());
            container.AddContent (Runtime.ControlCommand.VisitIndex ());

            // Chosen sequence index:
            //  - Stopping: take the MIN(read count, num elements - 1)
            if (sequenceType == SequenceType.Stopping) {
                container.AddContent (new Runtime.IntValue (sequenceElements.Count - 1));
                container.AddContent (Runtime.NativeFunctionCall.CallWithName ("MIN"));
            } 

            // - Cycle: take (read count % num elements)
            else if (sequenceType == SequenceType.Cycle) {
                container.AddContent (new Runtime.IntValue (sequenceElements.Count));
                container.AddContent (Runtime.NativeFunctionCall.CallWithName ("%"));
            }

            // Once: allow sequence count to be unbounded
            else if (sequenceType == SequenceType.Once) {
                // Do nothing - the sequence count will simply prevent
                // any content being referenced when it goes out of bounds
            } 

            // Shuffle
            else if (sequenceType == SequenceType.Shuffle) {
                // This one's a bit more complex! Choose the index at runtime.
                container.AddContent (new Runtime.IntValue (sequenceElements.Count));
                container.AddContent (Runtime.ControlCommand.SequenceShuffleIndex ());
            }

            // Not implemented
            else {
                throw new System.NotImplementedException ();
            }

            container.AddContent (Runtime.ControlCommand.EvalEnd ());

            // Create point to return to when sequence is complete
            var postSequenceNoOp = Runtime.ControlCommand.NoOp ();

            var elIndex = 0;
            foreach (var el in sequenceElements) {

                // This sequence element:
                //  if( chosenIndex == this index ) divert to this sequence element
                // duplicate chosen sequence index, since it'll be consumed by "=="
                container.AddContent (Runtime.ControlCommand.EvalStart ());
                container.AddContent (Runtime.ControlCommand.Duplicate ()); 
                container.AddContent (new Runtime.IntValue (elIndex));
                container.AddContent (Runtime.NativeFunctionCall.CallWithName ("=="));
                container.AddContent (Runtime.ControlCommand.EvalEnd ());

                // Divert branch for this sequence element
                var sequenceDivert = new Runtime.Divert ();
                sequenceDivert.isConditional = true;
                container.AddContent (sequenceDivert);

                // Generate content for this sequence element
                var contentContainerForSequenceBranch = (Runtime.Container) el.runtimeObject;
                contentContainerForSequenceBranch.name = "s" + elIndex;
                contentContainerForSequenceBranch.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0);

                // When sequence element is complete, divert back to end of sequence
                var seqBranchCompleteDivert = new Runtime.Divert ();
                contentContainerForSequenceBranch.AddContent (seqBranchCompleteDivert);
                container.AddToNamedContentOnly (contentContainerForSequenceBranch);

                // Save the diverts for reference resolution later (in ResolveReferences)
                AddDivertToResolve (sequenceDivert, contentContainerForSequenceBranch);
                AddDivertToResolve (seqBranchCompleteDivert, postSequenceNoOp);

                elIndex++;
            }

            container.AddContent (postSequenceNoOp);

            return container;
        }
Ejemplo n.º 6
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);
        }
        // Runtime content can be summarised as follows:
        //  - Evaluate an expression if necessary to branch on
        //  - Branch to a named container if true
        //       - Divert back to main flow
        //         (owner Conditional is in control of this target point)
        public override Runtime.Object GenerateRuntimeObject()
        {
            // Check for common mistake, of putting "else:" instead of "- else:"
            if (_innerWeave)
            {
                foreach (var c in _innerWeave.content)
                {
                    var text = c as Parsed.Text;
                    if (text)
                    {
                        // Don't need to trim at the start since the parser handles that already
                        if (text.text.StartsWith("else:"))
                        {
                            Warning("Saw the text 'else:' which is being treated as content. Did you mean '- else:'?", text);
                        }
                    }
                }
            }

            var container = new Runtime.Container();

            // Are we testing against a condition that's used for more than just this
            // branch? If so, the first thing we need to do is replicate the value that's
            // on the evaluation stack so that we don't fully consume it, in case other
            // branches need to use it.
            bool duplicatesStackValue = matchingEquality && !isElse;

            if (duplicatesStackValue)
            {
                container.AddContent(Runtime.ControlCommand.Duplicate());
            }

            _conditionalDivert = new Runtime.Divert();

            // else clause is unconditional catch-all, otherwise the divert is conditional
            _conditionalDivert.isConditional = !isElse;

            // Need extra evaluation?
            if (!isTrueBranch && !isElse)
            {
                bool needsEval = ownExpression != null;
                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalStart());
                }

                if (ownExpression)
                {
                    ownExpression.GenerateIntoContainer(container);
                }

                // Uses existing duplicated value
                if (matchingEquality)
                {
                    container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
                }

                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalEnd());
                }
            }

            // Will pop from stack if conditional
            container.AddContent(_conditionalDivert);

            _contentContainer      = GenerateRuntimeForContent();
            _contentContainer.name = "b";

            // Multi-line conditionals get a newline at the start of each branch
            // (as opposed to the start of the multi-line conditional since the condition
            //  may evaluate to false.)
            if (!isInline)
            {
                _contentContainer.InsertContent(new Runtime.StringValue("\n"), 0);
            }

            if (duplicatesStackValue || (isElse && matchingEquality))
            {
                _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0);
            }

            container.AddToNamedContentOnly(_contentContainer);

            returnDivert = new Runtime.Divert();
            _contentContainer.AddContent(returnDivert);

            return(container);
        }
Ejemplo n.º 8
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            _outerContainer = new Runtime.Container();

            // Content names for different types of choice:
            //  * start content [choice only content] inner content
            //  * start content   -> divert
            //  * start content
            //  * [choice only content]

            // Hmm, this structure has become slightly insane!
            //
            // [
            //     EvalStart
            //     BeginString
            //     PUSH (function)
            //     -> s
            //     EndString
            //     BeginString
            //     ... choice only content
            //     EndEval
            //     Condition expression
            //     choice: -> "c"
            //     (s) = [
            //         start content
            //     ]
            //     (c) = [
            //         PUSH (function)
            //         -> s
            //         inner content
            //     ]
            // ]

            _runtimeChoice = new Runtime.ChoicePoint(onceOnly);
            _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault;

            if (startContent || choiceOnlyContent || condition)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.EvalStart());
            }

            // Start content is put into a named container that's referenced both
            // when displaying the choice initially, and when generating the text
            // when the choice is chosen.
            if (startContent)
            {
                // Mark the start of the choice text generation, so that the runtime
                // knows where to rewind to to extract the content from the output stream.
                _outerContainer.AddContent(Runtime.ControlCommand.BeginString());

                // "Function call" to generate start content
                _divertToStartContentOuter = new Runtime.Divert(Runtime.PushPopType.Function);
                _outerContainer.AddContent(_divertToStartContentOuter);

                // Start content itself in a named container
                _startContentRuntimeContainer      = startContent.GenerateRuntimeObject() as Runtime.Container;
                _startContentRuntimeContainer.name = "s";
                _outerContainer.AddToNamedContentOnly(_startContentRuntimeContainer);

                _outerContainer.AddContent(Runtime.ControlCommand.EndString());

                _runtimeChoice.hasStartContent = true;
            }

            // Choice only content - mark the start, then generate it directly into the outer container
            if (choiceOnlyContent)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.BeginString());

                var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject() as Runtime.Container;
                _outerContainer.AddContentsOfContainer(choiceOnlyRuntimeContent);

                _outerContainer.AddContent(Runtime.ControlCommand.EndString());

                _runtimeChoice.hasChoiceOnlyContent = true;
            }

            // Generate any condition for this choice
            if (condition)
            {
                condition.GenerateIntoContainer(_outerContainer);
                _runtimeChoice.hasCondition = true;
            }

            if (startContent || choiceOnlyContent || condition)
            {
                _outerContainer.AddContent(Runtime.ControlCommand.EvalEnd());
            }

            // Add choice itself
            _outerContainer.AddContent(_runtimeChoice);

            // Container that choice points to for when it's chosen
            _innerContentContainer = new Runtime.Container();

            // Repeat start content by diverting to its container
            if (startContent)
            {
                _divertToStartContentInner = new Runtime.Divert(Runtime.PushPopType.Function);
                _innerContentContainer.AddContent(_divertToStartContentInner);
            }

            // Choice's own inner content
            if (innerContent)
            {
                var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject() as Runtime.Container;
                _innerContentContainer.AddContentsOfContainer(innerChoiceOnlyContent);
            }

            // Fully parsed choice will be a full line, so it needs to be terminated
            if (startContent || innerContent)
            {
                _innerContentContainer.AddContent(new Runtime.StringValue("\n"));
            }

            // Use "c" as the destination name within the choice's outer container
            _innerContentContainer.name = "c";
            _outerContainer.AddToNamedContentOnly(_innerContentContainer);

            if (this.story.countAllVisits)
            {
                _innerContentContainer.visitsShouldBeCounted    = true;
                _innerContentContainer.turnIndexShouldBeCounted = true;
            }

            _innerContentContainer.countingAtStartOnly = true;

            // Does this choice end in an explicit divert?
            if (terminatingDivert)
            {
                _innerContentContainer.AddContent(terminatingDivert.runtimeObject);
            }

            return(_outerContainer);
        }
Ejemplo n.º 9
0
        // Runtime content can be summarised as follows:
        //  - Evaluate an expression if necessary to branch on
        //  - Branch to a named container if true
        //       - Divert back to main flow
        //         (owner Conditional is in control of this target point)
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container ();

            // Are we testing against a condition that's used for more than just this
            // branch? If so, the first thing we need to do is replicate the value that's
            // on the evaluation stack so that we don't fully consume it, in case other
            // branches need to use it.
            bool duplicatesStackValue = shouldMatchEquality && !isElse;
            if ( duplicatesStackValue )
                container.AddContent (Runtime.ControlCommand.Duplicate ());

            _conditionalDivert = new Runtime.Divert ();

            // else clause is unconditional catch-all, otherwise the divert is conditional
            _conditionalDivert.isConditional = !isElse;

            // Need extra evaluation?
            if( !isTrueBranch && !isElse ) {

                bool needsEval = ownExpression != null;
                if( needsEval )
                    container.AddContent (Runtime.ControlCommand.EvalStart ());

                if (ownExpression)
                    ownExpression.GenerateIntoContainer (container);

                // Uses existing duplicated value
                if (shouldMatchEquality)
                    container.AddContent (Runtime.NativeFunctionCall.CallWithName ("=="));

                if( needsEval )
                    container.AddContent (Runtime.ControlCommand.EvalEnd ());
            }

            // Will pop from stack if conditional
            container.AddContent (_conditionalDivert);

            _contentContainer = GenerateRuntimeForContent ();
            _contentContainer.name = "b";

            if( duplicatesStackValue )
                _contentContainer.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0);

            container.AddToNamedContentOnly (_contentContainer);

            returnDivert = new Runtime.Divert ();
            _contentContainer.AddContent (returnDivert);

            return container;
        }
Ejemplo n.º 10
0
        // Runtime content can be summarised as follows:
        //  - Evaluate an expression if necessary to branch on
        //  - Branch to a named container if true
        //       - Divert back to main flow
        //         (owner Conditional is in control of this target point)
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            // Are we testing against a condition that's used for more than just this
            // branch? If so, the first thing we need to do is replicate the value that's
            // on the evaluation stack so that we don't fully consume it, in case other
            // branches need to use it.
            bool usingValueOnStack = (isBoolCondition || shouldMatchEquality) && !alwaysMatch;

            if (usingValueOnStack)
            {
                container.AddContent(Runtime.ControlCommand.Duplicate());
            }

            _divertOnBranch = new Runtime.Divert();

            Runtime.Branch branch;

            if (isBoolCondition)
            {
                if (boolRequired == true)
                {
                    branch = new Runtime.Branch(trueDivert: _divertOnBranch);
                }
                else
                {
                    branch = new Runtime.Branch(falseDivert: _divertOnBranch);
                }
            }
            else
            {
                bool needsEval = ownExpression || alwaysMatch;

                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalStart());
                }

                if (ownExpression)
                {
                    ownExpression.GenerateIntoContainer(container);
                }

                if (shouldMatchEquality)
                {
                    container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
                }

                if (alwaysMatch)
                {
                    container.AddContent(new Runtime.LiteralInt(1));
                }

                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalEnd());
                }

                branch = new Runtime.Branch(trueDivert: _divertOnBranch);
            }

            container.AddContent(branch);

            _contentContainer      = GenerateRuntimeForContent();
            _contentContainer.name = "b";

            if (usingValueOnStack)
            {
                _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0);
            }

            container.AddToNamedContentOnly(_contentContainer);

            returnDivert = new Runtime.Divert();
            _contentContainer.AddContent(returnDivert);

            return(container);
        }
Ejemplo n.º 11
0
        // Runtime content can be summarised as follows:
        //  - Evaluate an expression if necessary to branch on
        //  - Branch to a named container if true
        //       - Divert back to main flow
        //         (owner Conditional is in control of this target point)
        public override Runtime.Object GenerateRuntimeObject ()
        {
            // Check for common mistake, of putting "else:" instead of "- else:"
            if (_innerWeave) {
                foreach (var c in _innerWeave.content) {
                    var text = c as Parsed.Text;
                    if (text) {
                        // Don't need to trim at the start since the parser handles that already
                        if (text.text.StartsWith ("else:", System.StringComparison.InvariantCulture)) {
                            Warning ("Saw the text 'else:' which is being treated as content. Did you mean '- else:'?", text);
                        }
                    }
                }
            }
                                           
            var container = new Runtime.Container ();

            // Are we testing against a condition that's used for more than just this
            // branch? If so, the first thing we need to do is replicate the value that's
            // on the evaluation stack so that we don't fully consume it, in case other
            // branches need to use it.
            bool duplicatesStackValue = shouldMatchEquality && !isElse;
            if ( duplicatesStackValue )
                container.AddContent (Runtime.ControlCommand.Duplicate ());

            _conditionalDivert = new Runtime.Divert ();

            // else clause is unconditional catch-all, otherwise the divert is conditional
            _conditionalDivert.isConditional = !isElse;

            // Need extra evaluation?
            if( !isTrueBranch && !isElse ) {

                bool needsEval = ownExpression != null;
                if( needsEval )
                    container.AddContent (Runtime.ControlCommand.EvalStart ());

                if (ownExpression)
                    ownExpression.GenerateIntoContainer (container);

                // Uses existing duplicated value
                if (shouldMatchEquality)
                    container.AddContent (Runtime.NativeFunctionCall.CallWithName ("=="));

                if( needsEval ) 
                    container.AddContent (Runtime.ControlCommand.EvalEnd ()); 
            }

            // Will pop from stack if conditional
            container.AddContent (_conditionalDivert);

            _contentContainer = GenerateRuntimeForContent ();
            _contentContainer.name = "b";

            if( duplicatesStackValue )
                _contentContainer.InsertContent (Runtime.ControlCommand.PopEvaluatedValue (), 0);

            container.AddToNamedContentOnly (_contentContainer);

            returnDivert = new Runtime.Divert ();
            _contentContainer.AddContent (returnDivert);

            return container;
        }
Ejemplo n.º 12
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            _outerContainer = new Runtime.Container ();

            // Content names for different types of choice:
            //  * start content [choice only content] inner content
            //  * start content   -> divert
            //  * start content
            //  * [choice only content]

            // Hmm, this structure has become slightly insane!
            //
            // [
            //     EvalStart
            //     BeginString
            //     PUSH (function)
            //     -> s
            //     EndString
            //     BeginString
            //     ... choice only content
            //     EndEval
            //     Condition expression
            //     choice: -> "c"
            //     (s) = [
            //         start content
            //     ]
            //     (c) = [
            //         PUSH (function)
            //         -> s
            //         inner content
            //     ]
            // ]

            _runtimeChoice = new Runtime.ChoicePoint (onceOnly);
            _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault;

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ());
            }

            // Start content is put into a named container that's referenced both
            // when displaying the choice initially, and when generating the text
            // when the choice is chosen.
            if (startContent) {

                // Mark the start of the choice text generation, so that the runtime
                // knows where to rewind to to extract the content from the output stream.
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                // "Function call" to generate start content
                _divertToStartContentOuter = new Runtime.Divert (Runtime.PushPopType.Function);
                _outerContainer.AddContent (_divertToStartContentOuter);

                // Start content itself in a named container
                _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container;
                _startContentRuntimeContainer.name = "s";
                _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasStartContent = true;
            }

            // Choice only content - mark the start, then generate it directly into the outer container
            if (choiceOnlyContent) {
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container;
                _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasChoiceOnlyContent = true;
            }

            // Generate any condition for this choice
            if (condition) {
                condition.GenerateIntoContainer (_outerContainer);
                _runtimeChoice.hasCondition = true;
            }

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ());
            }

            // Add choice itself
            _outerContainer.AddContent (_runtimeChoice);

            // Container that choice points to for when it's chosen
            _innerContentContainer = new Runtime.Container ();

            // Repeat start content by diverting to its container
            if (startContent) {
                _divertToStartContentInner = new Runtime.Divert (Runtime.PushPopType.Function);
                _innerContentContainer.AddContent (_divertToStartContentInner);
            }

            // Choice's own inner content
            if (innerContent) {
                var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container;
                _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent);
            }

            // Fully parsed choice will be a full line, so it needs to be terminated
            if (startContent || innerContent) {
                _innerContentContainer.AddContent(new Runtime.StringValue("\n"));
            }

            // Use "c" as the destination name within the choice's outer container
            _innerContentContainer.name = "c";
            _outerContainer.AddToNamedContentOnly (_innerContentContainer);

            if (this.story.countAllVisits) {
                _innerContentContainer.visitsShouldBeCounted = true;
                _innerContentContainer.turnIndexShouldBeCounted = true;
            }

            _innerContentContainer.countingAtStartOnly = true;

            // Does this choice end in an explicit divert?
            if (terminatingDivert) {
                _innerContentContainer.AddContent (terminatingDivert.runtimeObject);
            }

            return _outerContainer;
        }
Ejemplo n.º 13
0
Archivo: Choice.cs Proyecto: inkle/ink
		public override Runtime.Object GenerateRuntimeObject ()
        {
            _outerContainer = new Runtime.Container ();

            // Content names for different types of choice:
            //  * start content [choice only content] inner content
            //  * start content   -> divert
            //  * start content
            //  * [choice only content]

            // Hmm, this structure has become slightly insane!
            //
            // [
            //     EvalStart
            //     assign $r = $r1   -- return target = return label 1
            //     BeginString
            //     -> s
            //     [(r1)]            -- return label 1 (after start content)
            //     EndString
            //     BeginString
            //     ... choice only content
            //     EndEval
            //     Condition expression
            //     choice: -> "c"
            //     (s) = [
            //         start content
            //         -> r          -- goto return label 1 or 2
            //     ]
            //     (c) = [
            //         EvalStart
            //         assign $r = $r2   -- return target = return label 2
            //         EndEval
            //         -> s
            //         [(r2)]            -- return label 1 (after start content)
            //         inner content
            //     ]
            // ]

            _runtimeChoice = new Runtime.ChoicePoint (onceOnly);
            _runtimeChoice.isInvisibleDefault = this.isInvisibleDefault;

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalStart ());
            }

            // Start content is put into a named container that's referenced both
            // when displaying the choice initially, and when generating the text
            // when the choice is chosen.
            if (startContent) {

                // Generate start content and return
                //  - We can't use a function since it uses a call stack element, which would
                //    put temporary values out of scope. Instead we manually divert around.
                //  - $r is a variable divert target contains the return point
                _returnToR1 = new Runtime.DivertTargetValue ();
                _outerContainer.AddContent (_returnToR1);
                var varAssign = new Runtime.VariableAssignment ("$r", true);
                _outerContainer.AddContent (varAssign);

                // Mark the start of the choice text generation, so that the runtime
                // knows where to rewind to to extract the content from the output stream.
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                _divertToStartContentOuter = new Runtime.Divert ();
                _outerContainer.AddContent (_divertToStartContentOuter);

                // Start content itself in a named container
                _startContentRuntimeContainer = startContent.GenerateRuntimeObject () as Runtime.Container;
                _startContentRuntimeContainer.name = "s";

                // Effectively, the "return" statement - return to the point specified by $r
                var varDivert = new Runtime.Divert ();
                varDivert.variableDivertName = "$r";
                _startContentRuntimeContainer.AddContent (varDivert);

                // Add the container
                _outerContainer.AddToNamedContentOnly (_startContentRuntimeContainer);

                // This is the label to return to
                _r1Label = new Runtime.Container ();
                _r1Label.name = "$r1";
                _outerContainer.AddContent (_r1Label);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasStartContent = true;
            }

            // Choice only content - mark the start, then generate it directly into the outer container
            if (choiceOnlyContent) {
                _outerContainer.AddContent (Runtime.ControlCommand.BeginString ());

                var choiceOnlyRuntimeContent = choiceOnlyContent.GenerateRuntimeObject () as Runtime.Container;
                _outerContainer.AddContentsOfContainer (choiceOnlyRuntimeContent);

                _outerContainer.AddContent (Runtime.ControlCommand.EndString ());

                _runtimeChoice.hasChoiceOnlyContent = true;
            }

            // Generate any condition for this choice
            if (condition) {
                condition.GenerateIntoContainer (_outerContainer);
                _runtimeChoice.hasCondition = true;
            }

            if (startContent || choiceOnlyContent || condition) {
                _outerContainer.AddContent (Runtime.ControlCommand.EvalEnd ());
            }
                
            // Add choice itself
            _outerContainer.AddContent (_runtimeChoice);

            // Container that choice points to for when it's chosen
            _innerContentContainer = new Runtime.Container ();

            // Repeat start content by diverting to its container
            if (startContent) {

                // Set the return point when jumping back into the start content
                //  - In this case, it's the $r2 point, within the choice content "c".
                _returnToR2 = new Runtime.DivertTargetValue ();
                _innerContentContainer.AddContent (Runtime.ControlCommand.EvalStart ());
                _innerContentContainer.AddContent (_returnToR2);
                _innerContentContainer.AddContent (Runtime.ControlCommand.EvalEnd ());
                var varAssign = new Runtime.VariableAssignment ("$r", true);
                _innerContentContainer.AddContent (varAssign);

                // Main divert into start content
                _divertToStartContentInner = new Runtime.Divert ();
                _innerContentContainer.AddContent (_divertToStartContentInner);

                // Define label to return to
                _r2Label = new Runtime.Container ();
                _r2Label.name = "$r2";
                _innerContentContainer.AddContent (_r2Label);
            }

            // Choice's own inner content
            if (innerContent) {
				var innerChoiceOnlyContent = innerContent.GenerateRuntimeObject () as Runtime.Container;
                _innerContentContainer.AddContentsOfContainer (innerChoiceOnlyContent);
            }

            // Fully parsed choice will be a full line, so it needs to be terminated
            if (startContent || innerContent) {
                _innerContentContainer.AddContent(new Runtime.StringValue("\n"));
            }

            // Use "c" as the destination name within the choice's outer container
            _innerContentContainer.name = "c";
            _outerContainer.AddToNamedContentOnly (_innerContentContainer);

            if (this.story.countAllVisits) {
                _innerContentContainer.visitsShouldBeCounted = true;
                _innerContentContainer.turnIndexShouldBeCounted = true;
            }

            _innerContentContainer.countingAtStartOnly = true;

            // Does this choice end in an explicit divert?
            if (terminatingDivert) {
                _innerContentContainer.AddContent (terminatingDivert.runtimeObject);
            }

            return _outerContainer;
		}
Ejemplo n.º 14
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;

            // Count visits on all knots and stitches
            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 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++;
            }

            // 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;
        }
Ejemplo n.º 15
0
        // Runtime content can be summarised as follows:
        //  - Evaluate an expression if necessary to branch on
        //  - Branch to a named container if true
        //       - Divert back to main flow
        //         (owner Conditional is in control of this target point)
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            // Are we testing against a condition that's used for more than just this
            // branch? If so, the first thing we need to do is replicate the value that's
            // on the evaluation stack so that we don't fully consume it, in case other
            // branches need to use it.
            bool duplicatesStackValue = shouldMatchEquality && !isElse;

            if (duplicatesStackValue)
            {
                container.AddContent(Runtime.ControlCommand.Duplicate());
            }

            _conditionalDivert = new Runtime.Divert();

            // else clause is unconditional catch-all, otherwise the divert is conditional
            _conditionalDivert.isConditional = !isElse;

            // Need extra evaluation?
            if (!isTrueBranch && !isElse)
            {
                bool needsEval = ownExpression != null;
                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalStart());
                }

                if (ownExpression)
                {
                    ownExpression.GenerateIntoContainer(container);
                }

                // Uses existing duplicated value
                if (shouldMatchEquality)
                {
                    container.AddContent(Runtime.NativeFunctionCall.CallWithName("=="));
                }

                if (needsEval)
                {
                    container.AddContent(Runtime.ControlCommand.EvalEnd());
                }
            }

            // Will pop from stack if conditional
            container.AddContent(_conditionalDivert);

            _contentContainer      = GenerateRuntimeForContent();
            _contentContainer.name = "b";

            if (duplicatesStackValue)
            {
                _contentContainer.InsertContent(Runtime.ControlCommand.PopEvaluatedValue(), 0);
            }

            container.AddToNamedContentOnly(_contentContainer);

            returnDivert = new Runtime.Divert();
            _contentContainer.AddContent(returnDivert);

            return(container);
        }