Exemplo n.º 1
0
        public override Runtime.Object GenerateRuntimeObject ()
        {
            FlowBase newDeclScope = null;
            if (isGlobalDeclaration) {
                newDeclScope = story;
            } else if(isNewTemporaryDeclaration) {
                newDeclScope = ClosestFlowBase ();
            }

            if( newDeclScope )
                newDeclScope.TryAddNewVariableDeclaration (this);

            // Global declarations don't generate actual procedural
            // runtime objects, but instead add a global variable to the story itself.
            // The story then initialises them all in one go at the start of the game.
            if( isGlobalDeclaration )
                return null;

            var container = new Runtime.Container ();

            // The expression's runtimeObject is actually another nested container
            container.AddContent (expression.runtimeObject);

            container.AddContent (new Runtime.VariableAssignment (variableName, isNewTemporaryDeclaration));

            return container;
        }
Exemplo n.º 2
0
 public override Runtime.Object GenerateRuntimeObject()
 {
     var container = new Runtime.Container ();
     if (content != null) {
         foreach (var obj in content) {
             container.AddContent (obj.runtimeObject);
         }
     }
     return container;
 }
Exemplo n.º 3
0
        // When generating the value of a constant expression,
        // we can't just keep generating the same constant expression into
        // different places where the constant value is referenced, since then
        // the same runtime objects would be used in multiple places, which
        // is impossible since each runtime object should have one parent.
        // Instead, we generate a prototype of the runtime object(s), then
        // copy them each time they're used.
        public void GenerateConstantIntoContainer(Runtime.Container container)
        {
            if( _prototypeRuntimeConstantExpression == null ) {
                _prototypeRuntimeConstantExpression = new Runtime.Container ();
                GenerateIntoContainer (_prototypeRuntimeConstantExpression);
            }

            foreach (var runtimeObj in _prototypeRuntimeConstantExpression.content) {
                container.AddContent (runtimeObj.Copy());
            }
        }
Exemplo n.º 4
0
        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();

            // Set override path for tunnel onwards (or nothing)
            container.AddContent (Runtime.ControlCommand.EvalStart ());
            if (overrideReturnPath != null) {
                _overrideDivertTarget = new Runtime.DivertTargetValue ();
                container.AddContent (_overrideDivertTarget);
            } else {
                container.AddContent (new Runtime.Void ());
            }
            container.AddContent (Runtime.ControlCommand.EvalEnd ());

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

            return container;
        }
        public override Runtime.Object GenerateRuntimeObject()
        {
            FlowBase newDeclScope = null;

            if (isGlobalDeclaration)
            {
                newDeclScope = story;
            }
            else if (isNewTemporaryDeclaration)
            {
                newDeclScope = ClosestFlowBase();
            }

            if (newDeclScope)
            {
                newDeclScope.TryAddNewVariableDeclaration(this);
            }

            // Global declarations don't generate actual procedural
            // runtime objects, but instead add a global variable to the story itself.
            // The story then initialises them all in one go at the start of the game.
            if (isGlobalDeclaration)
            {
                return(null);
            }

            var container = new Runtime.Container();

            // The expression's runtimeObject is actually another nested container
            if (expression != null)
            {
                container.AddContent(expression.runtimeObject);
            }
            else if (listDefinition != null)
            {
                container.AddContent(listDefinition.runtimeObject);
            }

            _runtimeAssignment = new Runtime.VariableAssignment(variableName, isNewTemporaryDeclaration);
            container.AddContent(_runtimeAssignment);

            return(container);
        }
Exemplo n.º 6
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            Expression constantValue = null;

            // If it's a constant reference, just generate the literal expression value
            // It's okay to access the constants at code generation time, since the
            // first thing the ExportRuntime function does it search for all the constants
            // in the story hierarchy, so they're all available.
            if (story.constants.TryGetValue(name, out constantValue))
            {
                constantValue.GenerateConstantIntoContainer(container);
                isConstantReference = true;
                return;
            }

            _runtimeVarRef = new Runtime.VariableReference(name);

            // List item reference?
            // Path might be to a list (listName.listItemName or just listItemName)
            if (path.Count == 1 || path.Count == 2)
            {
                string listItemName = null;
                string listName     = null;

                if (path.Count == 1)
                {
                    listItemName = path [0];
                }
                else
                {
                    listName     = path [0];
                    listItemName = path [1];
                }

                var listItem = story.ResolveListItem(listName, listItemName, this);
                if (listItem)
                {
                    isListItemReference = true;
                }
            }

            container.AddContent(_runtimeVarRef);
        }
Exemplo n.º 7
0
		public override Runtime.Object GenerateRuntimeObject ()
		{
            var container = new Runtime.Container ();

            // Tell Runtime to start evaluating the following content as an expression
            container.AddContent (Runtime.ControlCommand.EvalStart());

            GenerateIntoContainer (container);

            // Tell Runtime to output the result of the expression evaluation to the output stream
            if (outputWhenComplete) {
                container.AddContent (Runtime.ControlCommand.EvalOutput());
            }

            // Tell Runtime to stop evaluating the content as an expression
            container.AddContent (Runtime.ControlCommand.EvalEnd());

            return container;
		}
Exemplo n.º 8
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            Expression constantValue = null;

            // Name can be null if it's actually a path to a knot/stitch etc for a read count
            var varName = name;

            // If it's a constant reference, just generate the literal expression value
            if (varName != null && story.constants.TryGetValue(varName, out constantValue))
            {
                constantValue.GenerateIntoContainer(container);
                isConstantReference = true;
            }
            else
            {
                _runtimeVarRef = new Runtime.VariableReference(name);
                container.AddContent(_runtimeVarRef);
            }
        }
Exemplo n.º 9
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            // Tell Runtime to start evaluating the following content as an expression
            container.AddContent(Runtime.ControlCommand.EvalStart());

            GenerateIntoContainer(container);

            // Tell Runtime to output the result of the expression evaluation to the output stream
            if (outputWhenComplete)
            {
                container.AddContent(Runtime.ControlCommand.EvalOutput());
            }

            // Tell Runtime to stop evaluating the content as an expression
            container.AddContent(Runtime.ControlCommand.EvalEnd());

            return(container);
        }
Exemplo n.º 10
0
        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();

            // Initial condition
            if (this.initialCondition) {
                container.AddContent (initialCondition.runtimeObject);
            }

            // Individual branches
            foreach (var branch in branches) {
                var branchContainer = (Container) branch.runtimeObject;
                container.AddContent (branchContainer);
            }

            // Target for branches to rejoin to
            _reJoinTarget = Runtime.ControlCommand.NoOp ();
            container.AddContent (_reJoinTarget);

            return container;
        }
Exemplo n.º 11
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            // Set override path for tunnel onwards (or nothing)
            container.AddContent(Runtime.ControlCommand.EvalStart());
            if (overrideReturnPath != null)
            {
                _overrideDivertTarget = new Runtime.DivertTargetValue();
                container.AddContent(_overrideDivertTarget);
            }
            else
            {
                container.AddContent(new Runtime.Void());
            }
            container.AddContent(Runtime.ControlCommand.EvalEnd());

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

            return(container);
        }
Exemplo n.º 12
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            // x = x + 1
            // ^^^ ^ ^ ^
            //  4  1 3 2
            // Reverse polish notation: (x 1 +) (assign to x)

            // 1.
            container.AddContent(new Runtime.VariableReference(varName));

            // 2.
            container.AddContent(new Runtime.IntValue(isInc ? 1 : -1));

            // 3.
            container.AddContent(Runtime.NativeFunctionCall.CallWithName("+"));

            // 4.
            container.AddContent(new Runtime.VariableAssignment(varName, false));

            // Finally, leave the variable on the stack so it can be used as a sub-expression
            container.AddContent(new Runtime.VariableReference(varName));
        }
Exemplo n.º 13
0
Arquivo: Gather.cs Projeto: inkle/ink
        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();
            container.name = name;

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

            container.countingAtStartOnly = true;

            // A gather can have null content, e.g. it's just purely a line with "-"
            if (content != null) {
                foreach (var c in content) {
                    container.AddContent (c.runtimeObject);
                }
            }

            return container;

        }
Exemplo n.º 14
0
Arquivo: Return.cs Projeto: inkle/ink
        public override Runtime.Object GenerateRuntimeObject ()
        {
            var container = new Runtime.Container ();

            // Evaluate expression
            if (returnedExpression) {
                container.AddContent (returnedExpression.runtimeObject);
            } 

            // Return Runtime.Void when there's no expression to evaluate
            // (This evaluation will just add the Void object to the evaluation stack)
            else {
                container.AddContent (Runtime.ControlCommand.EvalStart ());
                container.AddContent (new Runtime.Void());
                container.AddContent (Runtime.ControlCommand.EvalEnd ());
            }

            // Then pop the call stack
            // (the evaluated expression will leave the return value on the evaluation stack)
            container.AddContent (Runtime.ControlCommand.PopFunction());

            return container;
        }
Exemplo n.º 15
0
        void TryFlattenContainer(Runtime.Container container)
        {
            if (container.namedContent.Count > 0 || container.hasValidName || _dontFlattenContainers.Contains(container))
            {
                return;
            }

            // Inline all the content in container into the parent
            var parentContainer = container.parent as Runtime.Container;

            if (parentContainer)
            {
                var contentIdx = parentContainer.content.IndexOf(container);
                parentContainer.content.RemoveAt(contentIdx);

                foreach (var innerContent in container.content)
                {
                    innerContent.parent = null;
                    parentContainer.InsertContent(innerContent, contentIdx);
                    contentIdx++;
                }
            }
        }
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            container.name = name;

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

            container.countingAtStartOnly = true;

            // A gather can have null content, e.g. it's just purely a line with "-"
            if (content != null)
            {
                foreach (var c in content)
                {
                    container.AddContent(c.runtimeObject);
                }
            }

            return(container);
        }
Exemplo n.º 17
0
Arquivo: Weave.cs Projeto: tomkail/ink
        // Found gather point:
        //  - gather any loose ends
        //  - set the gather as the main container to dump new content in
        void AddRuntimeForGather(Gather gather)
        {
            // Determine whether this Gather should be auto-entered:
            //  - It is auto-entered if there were no choices in the last section
            //  - A section is "since the previous gather" - so reset now
            bool autoEnter = !hasSeenChoiceInSection;
            hasSeenChoiceInSection = false;

            var gatherContainer = gather.runtimeContainer;

            if (gather.name == null) {
                // Use disallowed character so it's impossible to have a name collision
                gatherContainer.name = "g-" + _unnamedGatherCount;
                _unnamedGatherCount++;
            }

            // Auto-enter: include in main content
            if (autoEnter) {
                currentContainer.AddContent (gatherContainer);
            }

            // Don't auto-enter:
            // Add this gather to the main content, but only accessible
            // by name so that it isn't stepped into automatically, but only via
            // a divert from a loose end.
            else {
                currentContainer.AddToNamedContentOnly (gatherContainer);
            }

            // Consume loose ends: divert them to this gather
            foreach (Parsed.Object looseEnd in looseEnds) {

                // Skip gather loose ends that are at the same level
                // since they'll be handled by the auto-enter code below
                // that only jumps into the gather if (current runtime choices == 0)
                if (looseEnd is Gather) {
                    var prevGather = (Gather)looseEnd;
                    if (prevGather.indentationDepth == gather.indentationDepth) {
                        continue;
                    }
                }

                Runtime.Divert divert = null;

                if (looseEnd is Parsed.Divert) {
                    divert = (Runtime.Divert) looseEnd.runtimeObject;
                } else {
                    var looseWeavePoint = looseEnd as IWeavePoint;

                    var looseChoice = looseWeavePoint as Parsed.Choice;
                    if (looseChoice && looseChoice.hasTerminatingDivert) {
                        divert = looseChoice.terminatingDivert.runtimeObject as Runtime.Divert;
                    } else {
                        divert = new Runtime.Divert ();
                        looseWeavePoint.runtimeContainer.AddContent (divert);
                    }
                }

                // Pass back knowledge of this loose end being diverted
                // to the FlowBase so that it can maintain a list of them,
                // and resolve the divert references later
                gatherPointsToResolve.Add (new GatherPointToResolve{ divert = divert, targetRuntimeObj = gatherContainer });
            }
            looseEnds.Clear ();

            // Replace the current container itself
            currentContainer = gatherContainer;
        }
Exemplo n.º 18
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);
        }
Exemplo n.º 19
0
Arquivo: Weave.cs Projeto: tomkail/ink
        public override Runtime.Object GenerateRuntimeObject()
        {
            _rootContainer = currentContainer = new Runtime.Container();
            looseEnds = new List<Parsed.Object> ();

            gatherPointsToResolve = new List<GatherPointToResolve> ();

            // Iterate through content for the block at this level of indentation
            //  - Normal content is nested under Choices and Gathers
            //  - Blocks that are further indented cause recursion
            //  - Keep track of loose ends so that they can be diverted to Gathers
            foreach(var obj in content) {

                // Choice or Gather
                if (obj is IWeavePoint) {
                    AddRuntimeForWeavePoint ((IWeavePoint)obj);
                }

                // Non-weave point
                else {

                    // Nested weave
                    if (obj is Weave) {
                        var weave = (Weave)obj;
                        AddRuntimeForNestedWeave (weave);
                        gatherPointsToResolve.AddRange (weave.gatherPointsToResolve);
                    }

                    // Other object
                    // May be complex object that contains statements - e.g. a multi-line conditional
                    else {

                        // Find any nested explicit gather points within this object
                        // (including the object itself)
                        // i.e. instances of "->" without a target that's meant to go
                        // to the next gather point.
                        var innerExplicitGathers = obj.FindAll<Divert> (d => d.isToGather);
                        if (innerExplicitGathers.Count > 0)
                            looseEnds.AddRange (innerExplicitGathers.ToArray());

                        // Add content
                        AddGeneralRuntimeContent (obj.runtimeObject);
                    }

                    // Keep track of nested choices within this (possibly complex) object,
                    // so that the next Gather knows whether to auto-enter
                    // (it auto-enters when there are no choices)
                    var innerChoices = obj.FindAll<Choice> ();
                    if (innerChoices.Count > 0)
                        hasSeenChoiceInSection = true;

                }
            }

            // Pass any loose ends up the hierarhcy
            PassLooseEndsToAncestors();

            return _rootContainer;
        }
Exemplo n.º 20
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);
        }
Exemplo n.º 21
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);
        }
Exemplo n.º 22
0
Arquivo: Choice.cs Projeto: 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;
		}
Exemplo n.º 23
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            if (isChoiceCount)
            {
                if (arguments.Count > 0)
                {
                    Error("The CHOICE_COUNT() function shouldn't take any arguments");
                }

                container.AddContent(Runtime.ControlCommand.ChoiceCount());
            }
            else if (isTurnsSince)
            {
                var divertTarget         = arguments [0] as DivertTarget;
                var variableDivertTarget = arguments [0] as VariableReference;

                if (arguments.Count != 1 || (divertTarget == null && variableDivertTarget == null))
                {
                    Error("The TURNS_SINCE() function should take one argument: a divert target to the target knot, stitch, gather or choice you want to check. e.g. TURNS_SINCE(-> myKnot)");
                    return;
                }

                if (divertTarget)
                {
                    _turnCountDivertTarget = divertTarget;
                    AddContent(_turnCountDivertTarget);

                    _turnCountDivertTarget.GenerateIntoContainer(container);
                }
                else
                {
                    _turnCountVariableReference = variableDivertTarget;
                    AddContent(_turnCountVariableReference);

                    _turnCountVariableReference.GenerateIntoContainer(container);

                    if (!story.countAllVisits)
                    {
                        Error("Attempting to get TURNS_SINCE for a variable target without -c compiler option. You need the compiler switch turned on so that it can track turn counts for everything, not just those you directly reference.");
                    }
                }


                container.AddContent(Runtime.ControlCommand.TurnsSince());
            }

            else if (isRandom)
            {
                if (arguments.Count != 2)
                {
                    Error("RANDOM should take 2 parameters: a minimum and a maximum integer");
                }

                // We can type check single values, but not complex expressions
                for (int arg = 0; arg < arguments.Count; arg++)
                {
                    if (arguments [arg] is Number)
                    {
                        var num = arguments [arg] as Number;
                        if (!(num.value is int))
                        {
                            string paramName = arg == 0 ? "minimum" : "maximum";
                            Error("RANDOM's " + paramName + " parameter should be an integer");
                        }
                    }

                    arguments[arg].GenerateIntoContainer(container);
                }

                container.AddContent(Runtime.ControlCommand.Random());
            }

            else if (isSeedRandom)
            {
                if (arguments.Count != 1)
                {
                    Error("SEED_RANDOM should take 1 parameter - an integer seed");
                }

                var num = arguments [0] as Number;
                if (num && !(num.value is int))
                {
                    Error("SEED_RANDOM's parameter should be an integer seed");
                }

                arguments [0].GenerateIntoContainer(container);

                container.AddContent(Runtime.ControlCommand.SeedRandom());
            }

            // Normal function call
            else
            {
                container.AddContent(_proxyDivert.runtimeObject);
            }

            // Function calls that are used alone on a tilda-based line:
            //  ~ func()
            // Should tidy up any returned value from the evaluation stack,
            // since it's unused.
            if (shouldPopReturnedValue)
            {
                container.AddContent(Runtime.ControlCommand.PopEvaluatedValue());
            }
        }
Exemplo n.º 24
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            if (isChoiceCount)
            {
                if (arguments.Count > 0)
                {
                    Error("The CHOICE_COUNT() function shouldn't take any arguments");
                }

                container.AddContent(Runtime.ControlCommand.ChoiceCount());
            }

            else if (isTurnsSince)
            {
                var divertTarget         = arguments [0] as DivertTarget;
                var variableDivertTarget = arguments[0] as VariableReference;

                if (arguments.Count != 1 || (divertTarget == null && variableDivertTarget == null))
                {
                    Error("The TURNS_SINCE() function should take one argument: a divert target to the target knot, stitch, gather or choice you want to check. e.g. TURNS_SINCE(-> myKnot)");
                    return;
                }

                if (divertTarget)
                {
                    _turnCountDivertTarget = divertTarget;
                    AddContent(_turnCountDivertTarget);

                    _turnCountDivertTarget.GenerateIntoContainer(container);
                }

                else
                {
                    _turnCountVariableReference = variableDivertTarget;
                    AddContent(_turnCountVariableReference);

                    _turnCountVariableReference.GenerateIntoContainer(container);

                    if (!story.countAllVisits)
                    {
                        Error("Attempting to get TURNS_SINCE for a variable target without -c compiler option. You need the compiler switch turned on so that it can track turn counts for everything, not just those you directly reference.");
                    }
                }


                container.AddContent(Runtime.ControlCommand.TurnsSince());
            }

            // Normal function call
            else
            {
                container.AddContent(_proxyDivert.runtimeObject);
            }

            // Function calls that are used alone on a tilda-based line:
            //  ~ func()
            // Should tidy up any returned value from the evaluation stack,
            // since it's unused.
            if (shouldPopReturnedValue)
            {
                container.AddContent(Runtime.ControlCommand.PopEvaluatedValue());
            }
        }
Exemplo n.º 25
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;
        }
Exemplo n.º 26
0
 public abstract void GenerateIntoContainer(Runtime.Container container);
Exemplo n.º 27
0
        public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null)
        {
            _errorHandler = errorHandler;

            // Find all constants before main export begins, so that VariableReferences know
            // whether to generate a runtime variable reference or the literal value
            constants = new Dictionary <string, Expression> ();
            foreach (var constDecl in FindAll <ConstantDeclaration> ())
            {
                constants [constDecl.constantName] = constDecl.expression;
            }

            externals = new Dictionary <string, ExternalDeclaration> ();

            // Get default implementation of runtimeObject, which calls ContainerBase's generation method
            var rootContainer = runtimeObject as Runtime.Container;

            // Export initialisation of global variables
            // TODO: We *could* add this as a declarative block to the story itself...
            var variableInitialisation = new Runtime.Container();

            variableInitialisation.AddContent(Runtime.ControlCommand.EvalStart());

            // Global variables are those that are local to the story and marked as global
            foreach (var nameDeclPair in variableDeclarations)
            {
                var varName = nameDeclPair.Key;
                var varDecl = nameDeclPair.Value;
                if (varDecl.isGlobalDeclaration)
                {
                    varDecl.expression.GenerateIntoContainer(variableInitialisation);
                    var runtimeVarAss = new Runtime.VariableAssignment(varName, isNewDeclaration: true);
                    runtimeVarAss.isGlobal = true;
                    variableInitialisation.AddContent(runtimeVarAss);
                }
            }

            variableInitialisation.AddContent(Runtime.ControlCommand.EvalEnd());
            variableInitialisation.AddContent(Runtime.ControlCommand.End());

            if (variableDeclarations.Count > 0)
            {
                variableInitialisation.name = "global decl";
                rootContainer.AddToNamedContentOnly(variableInitialisation);
            }

            // Signal that it's safe to exit without error, even if there are no choices generated
            // (this only happens at the end of top level content that isn't in any particular knot)
            rootContainer.AddContent(Runtime.ControlCommand.Done());

            // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase
            var runtimeStory = new Runtime.Story(rootContainer);

            runtimeObject = runtimeStory;

            if (hadError)
            {
                return(null);
            }

            // Now that the story has been fulled parsed into a hierarchy,
            // and the derived runtime hierarchy has been built, we can
            // resolve referenced symbols such as variables and paths.
            // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName)
            // We don't make any assumptions that the INKPath follows the same
            // conventions as the script format, so we resolve to actual objects before
            // translating into an INKPath. (This also allows us to choose whether
            // we want the paths to be absolute)
            ResolveReferences(this);

            if (hadError)
            {
                return(null);
            }

            runtimeStory.ResetState();

            return(runtimeStory);
        }
Exemplo n.º 28
0
Arquivo: Story.cs Projeto: inkle/ink
        public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null)
		{
            _errorHandler = errorHandler;

            // Find all constants before main export begins, so that VariableReferences know
            // whether to generate a runtime variable reference or the literal value
            constants = new Dictionary<string, Expression> ();
            foreach (var constDecl in FindAll<ConstantDeclaration> ()) {

                // Check for duplicate definitions
                Parsed.Expression existingDefinition = null;
                if (constants.TryGetValue (constDecl.constantName, out existingDefinition)) {
                    if (!existingDefinition.Equals (constDecl.expression)) {
                        var errorMsg = string.Format ("CONST '{0}' has been redefined with a different value. Multiple definitions of the same CONST are valid so long as they contain the same value. Initial definition was on {1}.", constDecl.constantName, existingDefinition.debugMetadata);
                        Error (errorMsg, constDecl, isWarning:false);
                    }
                }

                constants [constDecl.constantName] = constDecl.expression;
            }

            externals = new Dictionary<string, ExternalDeclaration> ();

			// Get default implementation of runtimeObject, which calls ContainerBase's generation method
            var rootContainer = runtimeObject as Runtime.Container;

            // Export initialisation of global variables
            // TODO: We *could* add this as a declarative block to the story itself...
            var variableInitialisation = new Runtime.Container ();
            variableInitialisation.AddContent (Runtime.ControlCommand.EvalStart ());

            // Global variables are those that are local to the story and marked as global
            foreach (var nameDeclPair in variableDeclarations) {
                var varName = nameDeclPair.Key;
                var varDecl = nameDeclPair.Value;
                if (varDecl.isGlobalDeclaration) {
                    varDecl.expression.GenerateIntoContainer (variableInitialisation);
                    var runtimeVarAss = new Runtime.VariableAssignment (varName, isNewDeclaration:true);
                    runtimeVarAss.isGlobal = true;
                    variableInitialisation.AddContent (runtimeVarAss);
                }
            }

            variableInitialisation.AddContent (Runtime.ControlCommand.EvalEnd ());
            variableInitialisation.AddContent (Runtime.ControlCommand.End ());

            if (variableDeclarations.Count > 0) {
                variableInitialisation.name = "global decl";
                rootContainer.AddToNamedContentOnly (variableInitialisation);
            }

            // Signal that it's safe to exit without error, even if there are no choices generated
            // (this only happens at the end of top level content that isn't in any particular knot)
            rootContainer.AddContent (Runtime.ControlCommand.Done ());

			// Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase
            var runtimeStory = new Runtime.Story (rootContainer);
			runtimeObject = runtimeStory;

            if (hadError)
                return null;

            // Optimisation step - inline containers that can be
            FlattenContainersIn (rootContainer);

			// Now that the story has been fulled parsed into a hierarchy,
			// and the derived runtime hierarchy has been built, we can
			// resolve referenced symbols such as variables and paths.
			// e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName)
			// We don't make any assumptions that the INKPath follows the same
			// conventions as the script format, so we resolve to actual objects before
			// translating into an INKPath. (This also allows us to choose whether
			// we want the paths to be absolute)
			ResolveReferences (this);

            if (hadError)
                return null;

            runtimeStory.ResetState ();

			return runtimeStory;
		}
Exemplo n.º 29
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            var foundList = story.ResolveList(name);

            bool usingProxyDivert = false;

            if (isChoiceCount)
            {
                if (arguments.Count > 0)
                {
                    Error("The CHOICE_COUNT() function shouldn't take any arguments");
                }

                container.AddContent(Runtime.ControlCommand.ChoiceCount());
            }
            else if (isTurns)
            {
                if (arguments.Count > 0)
                {
                    Error("The TURNS() function shouldn't take any arguments");
                }

                container.AddContent(Runtime.ControlCommand.Turns());
            }
            else if (isTurnsSince || isReadCount)
            {
                var divertTarget         = arguments [0] as DivertTarget;
                var variableDivertTarget = arguments [0] as VariableReference;

                if (arguments.Count != 1 || (divertTarget == null && variableDivertTarget == null))
                {
                    Error("The " + name + "() function should take one argument: a divert target to the target knot, stitch, gather or choice you want to check. e.g. TURNS_SINCE(-> myKnot)");
                    return;
                }

                if (divertTarget)
                {
                    _divertTargetToCount = divertTarget;
                    AddContent(_divertTargetToCount);

                    _divertTargetToCount.GenerateIntoContainer(container);
                }
                else
                {
                    _variableReferenceToCount = variableDivertTarget;
                    AddContent(_variableReferenceToCount);

                    _variableReferenceToCount.GenerateIntoContainer(container);
                }

                if (isTurnsSince)
                {
                    container.AddContent(Runtime.ControlCommand.TurnsSince());
                }
                else
                {
                    container.AddContent(Runtime.ControlCommand.ReadCount());
                }
            }
            else if (isRandom)
            {
                if (arguments.Count != 2)
                {
                    Error("RANDOM should take 2 parameters: a minimum and a maximum integer");
                }

                // We can type check single values, but not complex expressions
                for (int arg = 0; arg < arguments.Count; arg++)
                {
                    if (arguments [arg] is Number)
                    {
                        var num = arguments [arg] as Number;
                        if (!(num.value is int))
                        {
                            string paramName = arg == 0 ? "minimum" : "maximum";
                            Error("RANDOM's " + paramName + " parameter should be an integer");
                        }
                    }

                    arguments [arg].GenerateIntoContainer(container);
                }

                container.AddContent(Runtime.ControlCommand.Random());
            }
            else if (isSeedRandom)
            {
                if (arguments.Count != 1)
                {
                    Error("SEED_RANDOM should take 1 parameter - an integer seed");
                }

                var num = arguments [0] as Number;
                if (num && !(num.value is int))
                {
                    Error("SEED_RANDOM's parameter should be an integer seed");
                }

                arguments [0].GenerateIntoContainer(container);

                container.AddContent(Runtime.ControlCommand.SeedRandom());
            }
            else if (isListRange)
            {
                if (arguments.Count != 3)
                {
                    Error("LIST_RANGE should take 3 parameters - a list, a min and a max");
                }

                for (int arg = 0; arg < arguments.Count; arg++)
                {
                    arguments [arg].GenerateIntoContainer(container);
                }

                container.AddContent(Runtime.ControlCommand.ListRange());
            }
            else if (isListRandom)
            {
                if (arguments.Count != 1)
                {
                    Error("LIST_RANDOM should take 1 parameter - a list");
                }

                arguments [0].GenerateIntoContainer(container);

                container.AddContent(Runtime.ControlCommand.ListRandom());
            }
            else if (Runtime.NativeFunctionCall.CallExistsWithName(name))
            {
                var nativeCall = Runtime.NativeFunctionCall.CallWithName(name);

                if (nativeCall.numberOfParameters != arguments.Count)
                {
                    var msg = name + " should take " + nativeCall.numberOfParameters + " parameter";
                    if (nativeCall.numberOfParameters > 1)
                    {
                        msg += "s";
                    }
                    Error(msg);
                }

                for (int arg = 0; arg < arguments.Count; arg++)
                {
                    arguments [arg].GenerateIntoContainer(container);
                }

                container.AddContent(Runtime.NativeFunctionCall.CallWithName(name));
            }
            else if (foundList != null)
            {
                if (arguments.Count > 1)
                {
                    Error("Can currently only construct a list from one integer (or an empty list from a given list definition)");
                }

                // List item from given int
                if (arguments.Count == 1)
                {
                    container.AddContent(new Runtime.StringValue(name));
                    arguments [0].GenerateIntoContainer(container);
                    container.AddContent(Runtime.ControlCommand.ListFromInt());
                }

                // Empty list with given origin.
                else
                {
                    var list = new Runtime.InkList();
                    list.SetInitialOriginName(name);
                    container.AddContent(new Runtime.ListValue(list));
                }
            }

            // Normal function call
            else
            {
                container.AddContent(_proxyDivert.runtimeObject);
                usingProxyDivert = true;
            }

            // Don't attempt to resolve as a divert if we're not doing a normal function call
            if (!usingProxyDivert)
            {
                content.Remove(_proxyDivert);
            }

            // Function calls that are used alone on a tilda-based line:
            //  ~ func()
            // Should tidy up any returned value from the evaluation stack,
            // since it's unused.
            if (shouldPopReturnedValue)
            {
                container.AddContent(Runtime.ControlCommand.PopEvaluatedValue());
            }
        }
        // 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);
        }
Exemplo n.º 31
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            _rootContainer = currentContainer = new Runtime.Container();
            looseEnds      = new List <Parsed.Object> ();

            gatherPointsToResolve = new List <GatherPointToResolve> ();

            // Iterate through content for the block at this level of indentation
            //  - Normal content is nested under Choices and Gathers
            //  - Blocks that are further indented cause recursion
            //  - Keep track of loose ends so that they can be diverted to Gathers
            foreach (var obj in content)
            {
                // Choice or Gather
                if (obj is IWeavePoint)
                {
                    AddRuntimeForWeavePoint((IWeavePoint)obj);
                }

                // Non-weave point
                else
                {
                    // Nested weave
                    if (obj is Weave)
                    {
                        var weave = (Weave)obj;
                        AddRuntimeForNestedWeave(weave);
                        gatherPointsToResolve.AddRange(weave.gatherPointsToResolve);
                    }

                    // Other object
                    // May be complex object that contains statements - e.g. a multi-line conditional
                    else
                    {
                        // Find any nested explicit gather points within this object
                        // (including the object itself)
                        // i.e. instances of "->" without a target that's meant to go
                        // to the next gather point.
                        var innerExplicitGathers = obj.FindAll <Divert> (d => d.isToGather);
                        if (innerExplicitGathers.Count > 0)
                        {
                            looseEnds.AddRange(innerExplicitGathers.ToArray());
                        }

                        // Add content
                        AddGeneralRuntimeContent(obj.runtimeObject);
                    }

                    // Keep track of nested choices within this (possibly complex) object,
                    // so that the next Gather knows whether to auto-enter
                    // (it auto-enters when there are no choices)
                    var innerChoices = obj.FindAll <Choice> ();
                    if (innerChoices.Count > 0)
                    {
                        hasSeenChoiceInSection = true;
                    }
                }
            }

            // Pass any loose ends up the hierarhcy
            PassLooseEndsToAncestors();

            return(_rootContainer);
        }
Exemplo n.º 32
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);
        }
Exemplo n.º 33
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);
        }
Exemplo n.º 34
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;
        }
Exemplo n.º 35
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;
        }
Exemplo n.º 36
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;
        }
Exemplo n.º 37
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);
        }
Exemplo n.º 38
0
Arquivo: Divert.cs Projeto: inkle/ink
		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;
                            }

                            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;
            }			
		}
Exemplo n.º 39
0
        public Runtime.Story ExportRuntime(ErrorHandler errorHandler = null)
        {
            _errorHandler = errorHandler;

            // Find all constants before main export begins, so that VariableReferences know
            // whether to generate a runtime variable reference or the literal value
            constants = new Dictionary <string, Expression> ();
            foreach (var constDecl in FindAll <ConstantDeclaration> ())
            {
                // Check for duplicate definitions
                Parsed.Expression existingDefinition = null;
                if (constants.TryGetValue(constDecl.constantName, out existingDefinition))
                {
                    if (!existingDefinition.Equals(constDecl.expression))
                    {
                        var errorMsg = string.Format("CONST '{0}' has been redefined with a different value. Multiple definitions of the same CONST are valid so long as they contain the same value. Initial definition was on {1}.", constDecl.constantName, existingDefinition.debugMetadata);
                        Error(errorMsg, constDecl, isWarning: false);
                    }
                }

                constants [constDecl.constantName] = constDecl.expression;
            }

            // List definitions are treated like constants too - they should be usable
            // from other variable declarations.
            _listDefs = new Dictionary <string, ListDefinition> ();
            foreach (var listDef in FindAll <ListDefinition> ())
            {
                _listDefs [listDef.identifier?.name] = listDef;
            }

            externals = new Dictionary <string, ExternalDeclaration> ();

            // Resolution of weave point names has to come first, before any runtime code generation
            // since names have to be ready before diverts start getting created.
            // (It used to be done in the constructor for a weave, but didn't allow us to generate
            // errors when name resolution failed.)
            ResolveWeavePointNaming();

            // Get default implementation of runtimeObject, which calls ContainerBase's generation method
            var rootContainer = runtimeObject as Runtime.Container;

            // Export initialisation of global variables
            // TODO: We *could* add this as a declarative block to the story itself...
            var variableInitialisation = new Runtime.Container();

            variableInitialisation.AddContent(Runtime.ControlCommand.EvalStart());

            // Global variables are those that are local to the story and marked as global
            var runtimeLists = new List <Runtime.ListDefinition> ();

            foreach (var nameDeclPair in variableDeclarations)
            {
                var varName = nameDeclPair.Key;
                var varDecl = nameDeclPair.Value;
                if (varDecl.isGlobalDeclaration)
                {
                    if (varDecl.listDefinition != null)
                    {
                        _listDefs[varName] = varDecl.listDefinition;
                        variableInitialisation.AddContent(varDecl.listDefinition.runtimeObject);
                        runtimeLists.Add(varDecl.listDefinition.runtimeListDefinition);
                    }
                    else
                    {
                        varDecl.expression.GenerateIntoContainer(variableInitialisation);
                    }

                    var runtimeVarAss = new Runtime.VariableAssignment(varName, isNewDeclaration: true);
                    runtimeVarAss.isGlobal = true;
                    variableInitialisation.AddContent(runtimeVarAss);
                }
            }

            variableInitialisation.AddContent(Runtime.ControlCommand.EvalEnd());
            variableInitialisation.AddContent(Runtime.ControlCommand.End());

            if (variableDeclarations.Count > 0)
            {
                variableInitialisation.name = "global decl";
                rootContainer.AddToNamedContentOnly(variableInitialisation);
            }

            // Signal that it's safe to exit without error, even if there are no choices generated
            // (this only happens at the end of top level content that isn't in any particular knot)
            rootContainer.AddContent(Runtime.ControlCommand.Done());

            // Replace runtimeObject with Story object instead of the Runtime.Container generated by Parsed.ContainerBase
            var runtimeStory = new Runtime.Story(rootContainer, runtimeLists);

            runtimeObject = runtimeStory;

            if (_hadError)
            {
                return(null);
            }

            // Optimisation step - inline containers that can be
            FlattenContainersIn(rootContainer);

            // Now that the story has been fulled parsed into a hierarchy,
            // and the derived runtime hierarchy has been built, we can
            // resolve referenced symbols such as variables and paths.
            // e.g. for paths " -> knotName --> stitchName" into an INKPath (knotName.stitchName)
            // We don't make any assumptions that the INKPath follows the same
            // conventions as the script format, so we resolve to actual objects before
            // translating into an INKPath. (This also allows us to choose whether
            // we want the paths to be absolute)
            ResolveReferences(this);

            if (_hadError)
            {
                return(null);
            }

            runtimeStory.ResetState();

            return(runtimeStory);
        }
Exemplo n.º 40
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;
        }
Exemplo n.º 41
0
        public override void GenerateIntoContainer(Runtime.Container container)
        {
            innerExpression.GenerateIntoContainer(container);

            container.AddContent(Runtime.NativeFunctionCall.CallWithName(nativeNameForOp));
        }
Exemplo n.º 42
0
 public void DontFlattenContainer(Runtime.Container container)
 {
     _dontFlattenContainers.Add(container);
 }
Exemplo n.º 43
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);
            }
        }
Exemplo n.º 44
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);
        }
Exemplo n.º 45
0
        public override Runtime.Object GenerateRuntimeObject()
        {
            var container = new Runtime.Container();

            // Set override path for tunnel onwards (or nothing)
            container.AddContent(Runtime.ControlCommand.EvalStart());

            if (divertAfter)
            {
                // Generate runtime object's generated code and steal the arguments runtime code
                var returnRuntimeObj       = divertAfter.GenerateRuntimeObject();
                var returnRuntimeContainer = returnRuntimeObj as Runtime.Container;
                if (returnRuntimeContainer)
                {
                    // Steal all code for generating arguments from the divert
                    var args = divertAfter.arguments;
                    if (args != null && args.Count > 0)
                    {
                        // Steal everything betwen eval start and eval end
                        int evalStart = -1;
                        int evalEnd   = -1;
                        for (int i = 0; i < returnRuntimeContainer.content.Count; i++)
                        {
                            var cmd = returnRuntimeContainer.content [i] as Runtime.ControlCommand;
                            if (cmd)
                            {
                                if (evalStart == -1 && cmd.commandType == Runtime.ControlCommand.CommandType.EvalStart)
                                {
                                    evalStart = i;
                                }
                                else if (cmd.commandType == Runtime.ControlCommand.CommandType.EvalEnd)
                                {
                                    evalEnd = i;
                                }
                            }
                        }

                        for (int i = evalStart + 1; i < evalEnd; i++)
                        {
                            var obj = returnRuntimeContainer.content [i];
                            obj.parent = null; // prevent error of being moved between owners
                            container.AddContent(returnRuntimeContainer.content [i]);
                        }
                    }
                }

                // Supply the divert target for the tunnel onwards target, either variable or more commonly, the explicit name
                var returnDivertObj = returnRuntimeObj as Runtime.Divert;
                if (returnDivertObj != null && returnDivertObj.hasVariableTarget)
                {
                    var runtimeVarRef = new Runtime.VariableReference(returnDivertObj.variableDivertName);
                    container.AddContent(runtimeVarRef);
                }
                else
                {
                    _overrideDivertTarget = new Runtime.DivertTargetValue();
                    container.AddContent(_overrideDivertTarget);
                }
            }

            // No divert after tunnel onwards
            else
            {
                container.AddContent(new Runtime.Void());
            }

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

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

            return(container);
        }
Exemplo n.º 46
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);
        }