Пример #1
0
        /// <summary>
        /// Initializes a new instance of the <see cref="Diagnostic"/> class.
        /// </summary>
        /// <param name="fileName"><inheritdoc cref="FileName"
        /// path="/summary/node()"/></param>
        /// <param name="context">The parse node at which the error
        /// occurred.</param>
        /// <param name="message"><inheritdoc cref="Message"
        /// path="/summary/node()"/></param>
        /// <param name="severity"><inheritdoc cref="Severity"
        /// path="/summary/node()"/></param>
        public Diagnostic(string fileName, ParserRuleContext context, string message, DiagnosticSeverity severity = DiagnosticSeverity.Error)
        {
            this.FileName = fileName;

            if (context != null)
            {
                this.Range = new Range(
                    context.Start.Line - 1,
                    context.Start.Column,
                    context.Stop.Line - 1,
                    context.Stop.Column + context.Stop.Text.Length);
            }
            this.Message  = message;
            this.Context  = context.GetTextWithWhitespace();
            this.Severity = severity;
        }
Пример #2
0
        // ok so what do we actually need to do in here?
        // we need to do a few different things
        // basically we need to go through the various types in the expression
        // if any are known we need to basically log that
        // then at the end if there are still unknowns we check if the operation itself forces a type
        // so if we have say Undefined = Undefined + Number then we know that only one operation supports + Number and that is Number + Number
        // so we can slot the type into the various parts
        private Yarn.IType CheckOperation(ParserRuleContext context, ParserRuleContext[] terms, Operator operationType, string operationDescription, params Yarn.IType[] permittedTypes)
        {
            var termTypes = new List <Yarn.IType>();

            var expressionType = BuiltinTypes.Undefined;

            foreach (var expression in terms)
            {
                // Visit this expression, and determine its type.
                Yarn.IType type = Visit(expression);

                if (type != BuiltinTypes.Undefined)
                {
                    termTypes.Add(type);
                    if (expressionType == BuiltinTypes.Undefined)
                    {
                        // This is the first concrete type we've seen. This
                        // will be our expression type.
                        expressionType = type;
                    }
                }
            }

            if (permittedTypes.Length == 1 && expressionType == BuiltinTypes.Undefined)
            {
                // If we aren't sure of the expression type from
                // parameters, but we only have one permitted one, then
                // assume that the expression type is the single permitted
                // type.
                expressionType = permittedTypes.First();
            }

            if (expressionType == BuiltinTypes.Undefined)
            {
                // We still don't know what type of expression this is, and
                // don't have a reasonable guess.

                // Last-ditch effort: is the operator that we were given
                // valid in exactly one type? In that case, we'll decide
                // it's that type.
                var typesImplementingMethod = types
                                              .Where(t => t.Methods != null)
                                              .Where(t => t.Methods.ContainsKey(operationType.ToString()));

                if (typesImplementingMethod.Count() == 1)
                {
                    // Only one type implements the operation we were
                    // given. Given no other information, we will assume
                    // that it is this type.
                    expressionType = typesImplementingMethod.First();
                }
                else if (typesImplementingMethod.Count() > 1)
                {
                    // Multiple types implement this operation.
                    IEnumerable <string> typeNames = typesImplementingMethod.Select(t => t.Name);

                    string message = $"Type of expression \"{context.GetTextWithWhitespace()}\" can't be determined without more context (the compiler thinks it could be {string.Join(", or ", typeNames)}). Use a type cast on at least one of the terms (e.g. the string(), number(), bool() functions)";

                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                    return(BuiltinTypes.Undefined);
                }
                else
                {
                    // No types implement this operation (??)
                    string message = $"Type of expression \"{context.GetTextWithWhitespace()}\" can't be determined without more context. Use a type cast on at least one of the terms (e.g. the string(), number(), bool() functions)";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                    return(BuiltinTypes.Undefined);
                }
            }

            // to reach this point we have either worked out the final type of the expression
            // or had to give up, and if we gave up we have nothing left to do
            // there are then two parts to this, first we need to declare the implict type of any variables (that appears to be working)
            // or the implicit type of any function.
            // annoyingly the function will already have an implicit definition created for it
            // we will have to strip that out and add in a new one with the new return type
            foreach (var term in terms)
            {
                if (term is YarnSpinnerParser.ExpValueContext)
                {
                    var value = ((YarnSpinnerParser.ExpValueContext)term).value();
                    if (value is YarnSpinnerParser.ValueFuncContext)
                    {
                        var id = ((YarnSpinnerParser.ValueFuncContext)value).function_call().FUNC_ID().GetText();

                        Declaration functionDeclaration = NewDeclarations.Where(d => d.Type is FunctionType).FirstOrDefault(d => d.Name == id);
                        if (functionDeclaration != null)
                        {
                            var func = functionDeclaration.Type as FunctionType;
                            if (func?.ReturnType == BuiltinTypes.Undefined)
                            {
                                NewDeclarations.Remove(functionDeclaration);
                                func.ReturnType = expressionType;
                                NewDeclarations.Add(functionDeclaration);
                            }
                        }
                        else
                        {
                            Visit(term);
                        }
                    }
                }
            }

            // Were any of the terms variables for which we don't currently
            // have a declaration for?

            // Start by building a list of all terms that are variables.
            // These are either variable values, or variable names . (The
            // difference between these two is that a ValueVarContext
            // occurs in syntax where the value of the variable is used
            // (like an expression), while a VariableContext occurs in
            // syntax where it's just a variable name (like a set
            // statements)

            // All VariableContexts in the terms of this expression (but
            // not in the children of those terms)
            var variableContexts = terms
                                   .Select(c => c.GetChild <YarnSpinnerParser.ValueVarContext>(0)?.variable())
                                   .Concat(terms.Select(c => c.GetChild <YarnSpinnerParser.VariableContext>(0)))
                                   .Concat(terms.OfType <YarnSpinnerParser.VariableContext>())
                                   .Concat(terms.OfType <YarnSpinnerParser.ValueVarContext>().Select(v => v.variable()))
                                   .Where(c => c != null);

            // Build the list of variable contexts that we don't have a
            // declaration for. We'll check for explicit declarations first.
            var undefinedVariableContexts = variableContexts
                                            .Where(v => Declarations.Any(d => d.Name == v.VAR_ID().GetText()) == false)
                                            .Distinct();

            if (undefinedVariableContexts.Count() > 0)
            {
                // We have references to variables that we don't have a an
                // explicit declaration for! Time to create implicit
                // references for them!

                // Get the position of this reference in the file
                int positionInFile = context.Start.Line;

                // The start line of the body is the line after the delimiter
                int nodePositionInFile = this.currentNodeContext.BODY_START().Symbol.Line + 1;

                foreach (var undefinedVariableContext in undefinedVariableContexts)
                {
                    // We can only create an implicit declaration for a variable
                    // if we have a default value for it, because all variables
                    // are required to have a value. If we can't, it's generally
                    // because we couldn't figure out a concrete type for the
                    // variable given the context.
                    var canGetDefaultValue = TryGetDefaultValueForType(expressionType, out var defaultValue);

                    // If we can't produce this, then we can't generate the
                    // declaration.
                    if (!canGetDefaultValue)
                    {
                        this.diagnostics.Add(new Diagnostic(sourceFileName, undefinedVariableContext, string.Format(CantDetermineVariableTypeError, undefinedVariableContext.VAR_ID().GetText())));
                        continue;
                    }

                    // Generate a declaration for this variable here.
                    var decl = new Declaration
                    {
                        Name           = undefinedVariableContext.VAR_ID().GetText(),
                        Description    = $"Implicitly declared in {System.IO.Path.GetFileName(sourceFileName)}, node {currentNodeName}",
                        Type           = expressionType,
                        DefaultValue   = defaultValue,
                        SourceFileName = sourceFileName,
                        SourceNodeName = currentNodeName,
                        Range          = new Range
                        {
                            Start =
                            {
                                Line      = undefinedVariableContext.Start.Line - 1,
                                Character = undefinedVariableContext.Start.Column,
                            },
                            End =
                            {
                                Line      = undefinedVariableContext.Stop.Line - 1,
                                Character = undefinedVariableContext.Stop.Column + undefinedVariableContext.Stop.Text.Length,
                            },
                        },
                        IsImplicit = true,
                    };
                    NewDeclarations.Add(decl);
                }
            }

            // All types must be same as the expression type (which is the
            // first defined type we encountered when going through the
            // terms)
            if (termTypes.All(t => t == expressionType) == false)
            {
                // Not all the term types we found were the expression
                // type.
                var    typeList = string.Join(", ", termTypes.Select(t => t.Name));
                string message  = $"All terms of {operationDescription} must be the same, not {typeList}";
                this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                return(BuiltinTypes.Undefined);
            }

            // We've now determined that this expression is of
            // expressionType. In case any of the terms had an undefined
            // type, we'll define it now.
            foreach (var term in terms)
            {
                if (term is YarnSpinnerParser.ExpressionContext expression)
                {
                    if (expression.Type == BuiltinTypes.Undefined)
                    {
                        expression.Type = expressionType;
                    }

                    if (expression.Type is FunctionType functionType && functionType.ReturnType == BuiltinTypes.Undefined)
                    {
                        functionType.ReturnType = expressionType;
                    }
                }
            }

            if (operationType != Operator.None)
            {
                // We need to validate that the type we've selected actually
                // implements this operation.
                var implementingType = TypeUtil.FindImplementingTypeForMethod(expressionType, operationType.ToString());

                if (implementingType == null)
                {
                    string message = $"{expressionType.Name} has no implementation defined for {operationDescription}";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                    return(BuiltinTypes.Undefined);
                }
            }

            // Is this expression is required to be one of the specified types?
            if (permittedTypes.Count() > 0)
            {
                // Is the type that we've arrived at compatible with one of
                // the permitted types?
                if (permittedTypes.Any(t => TypeUtil.IsSubType(t, expressionType)))
                {
                    // It's compatible! Great, return the type we've
                    // determined.
                    return(expressionType);
                }
                else
                {
                    // The expression type wasn't valid!
                    var permittedTypesList = string.Join(" or ", permittedTypes.Select(t => t?.Name ?? "undefined"));
                    var typeList           = string.Join(", ", termTypes.Select(t => t.Name));

                    string message = $"Terms of '{operationDescription}' must be {permittedTypesList}, not {typeList}";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                    return(BuiltinTypes.Undefined);
                }
            }
            else
            {
                // We weren't given a specific type. The expression type is
                // therefore only valid if it can use the provided
                // operator.

                // Find a type in 'expressionType's hierarchy that
                // implements this method.
                var implementingTypeForMethod = TypeUtil.FindImplementingTypeForMethod(expressionType, operationType.ToString());

                if (implementingTypeForMethod == null)
                {
                    // The type doesn't have a method for handling this
                    // operator, and neither do any of its supertypes. This
                    // expression is therefore invalid.

                    string message = $"Operator {operationDescription} cannot be used with {expressionType.Name} values";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));

                    return(BuiltinTypes.Undefined);
                }
                else
                {
                    return(expressionType);
                }
            }
        }