protected Expression ExpressionInfixRight(Parsed.Expression left, InfixOperator op)
        {
            Whitespace();

            var right = Parse(() => Expression(op.precedence));

            if (right)
            {
                // We assume that the character we use for the operator's type is the same
                // as that used internally by e.g. Runtime.Expression.Add, Runtime.Expression.Multiply etc
                var expr = new BinaryExpression(left, right, op.type);
                return(expr);
            }

            return(null);
        }
Example #2
0
        // Returns false if there's an error
        void CheckArgumentValidity()
        {
            if (isEmpty)
            {
                return;
            }

            // Argument passing: Check for errors in number of arguments
            var numArgs = 0;

            if (arguments != null && arguments.Count > 0)
            {
                numArgs = arguments.Count;
            }

            // Missing content?
            // Can't check arguments properly. It'll be due to some
            // other error though, so although there's a problem and
            // we report false, we don't need to report a specific error.
            // It may also be because it's a valid call to an external
            // function, that we check at the resolve stage.
            if (targetContent == null)
            {
                return;
            }

            FlowBase targetFlow = targetContent as FlowBase;

            // No error, crikey!
            if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters))
            {
                return;
            }

            if (targetFlow == null && numArgs > 0)
            {
                Error("target needs to be a knot or stitch in order to pass arguments");
                return;
            }

            if (targetFlow.arguments == null && numArgs > 0)
            {
                Error("target (" + targetFlow.name + ") doesn't take parameters");
                return;
            }

            if (this.parent is DivertTarget)
            {
                if (numArgs > 0)
                {
                    Error("can't store arguments in a divert target variable");
                }
                return;
            }

            var paramCount = targetFlow.arguments.Count;

            if (paramCount != numArgs)
            {
                string butClause;
                if (numArgs == 0)
                {
                    butClause = "but there weren't any passed to it";
                }
                else if (numArgs < paramCount)
                {
                    butClause = "but only got " + numArgs;
                }
                else
                {
                    butClause = "but got " + numArgs;
                }
                Error("to '" + targetFlow.name + "' requires " + paramCount + " arguments, " + butClause);
                return;
            }

            // Light type-checking for divert target arguments
            for (int i = 0; i < paramCount; ++i)
            {
                FlowBase.Argument flowArg    = targetFlow.arguments [i];
                Parsed.Expression divArgExpr = arguments [i];

                // Expecting a divert target as an argument, let's do some basic type checking
                if (flowArg.isDivertTarget)
                {
                    // Not passing a divert target or any kind of variable reference?
                    var varRef = divArgExpr as VariableReference;
                    if (!(divArgExpr is DivertTarget) && varRef == null)
                    {
                        Error("Target '" + targetFlow.name + "' expects a divert target for the parameter named -> " + flowArg.name + " but saw " + divArgExpr, divArgExpr);
                    }

                    // Passing 'a' instead of '-> a'?
                    // i.e. read count instead of divert target
                    else if (varRef != null)
                    {
                        // Unfortunately have to manually resolve here since we're still in code gen
                        var           knotCountPath  = new Path(varRef.path);
                        Parsed.Object targetForCount = knotCountPath.ResolveFromContext(varRef);
                        if (targetForCount != null)
                        {
                            Error("Passing read count of '" + knotCountPath.dotSeparatedComponents + "' instead of a divert target. You probably meant '" + knotCountPath + "'");
                        }
                    }
                }
            }

            if (targetFlow == null)
            {
                Error("Can't call as a function or with arguments unless it's a knot or stitch");
                return;
            }

            return;
        }
Example #3
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);
        }
Example #4
0
        // Returns false if there's an error
        void CheckArgumentValidity()
        {
            if (isToGather)
            {
                return;
            }

            // Argument passing: Check for errors in number of arguments
            var numArgs = 0;

            if (arguments != null && arguments.Count > 0)
            {
                numArgs = arguments.Count;
            }

            // Missing content?
            // Can't check arguments properly. It'll be due to some
            // other error though, so although there's a problem and
            // we report false, we don't need to report a specific error.
            // It may also be because it's a valid call to an external
            // function, that we check at the resolve stage.
            if (targetContent == null)
            {
                return;
            }

            FlowBase targetFlow = targetContent as FlowBase;

            // No error, crikey!
            if (numArgs == 0 && (targetFlow == null || !targetFlow.hasParameters))
            {
                return;
            }

            if (targetFlow == null && numArgs > 0)
            {
                Error("target needs to be a knot or stitch in order to pass arguments");
                return;
            }

            if (targetFlow.arguments == null && numArgs > 0)
            {
                Error("target (" + targetFlow.name + ") doesn't take parameters");
                return;
            }

            var paramCount = targetFlow.arguments.Count;

            if (paramCount > 0 && this.parent is DivertTarget)
            {
                Error("Can't store a link to a knot that takes parameters in a variable");
                return;
            }

            if (paramCount != numArgs)
            {
                string butClause;
                if (numArgs == 0)
                {
                    butClause = "but there weren't any passed to it";
                }
                else if (numArgs < paramCount)
                {
                    butClause = "but only got " + numArgs;
                }
                else
                {
                    butClause = "but got " + numArgs;
                }
                Error("to '" + targetFlow.name + "' requires " + paramCount + " arguments, " + butClause);
                return;
            }

            // Light type-checking for divert target arguments
            for (int i = 0; i < paramCount; ++i)
            {
                FlowBase.Argument flowArg    = targetFlow.arguments [i];
                Parsed.Expression divArgExpr = arguments [i];

                if (flowArg.isDivertTarget)
                {
                    if (!(divArgExpr is VariableReference || divArgExpr is DivertTarget))
                    {
                        Error("Target '" + targetFlow.name + "' expects a divert target for the parameter named -> " + flowArg.name + " but saw " + divArgExpr, divArgExpr);
                    }
                }
            }

            if (targetFlow == null)
            {
                Error("Can't call as a function or with arguments unless it's a knot or stitch");
                return;
            }

            return;
        }