コード例 #1
0
ファイル: Compiler.cs プロジェクト: wzzwzz687510/YarnSpinner
        // for setting variables, has two forms
        // << SET variable TO/= expression >>
        // << SET expression >>
        // the second form does need to match the structure:
        // variable (+= -= *= /= %=) expression
        public override int VisitSet_statement(YarnSpinnerParser.Set_statementContext context)
        {
            // if it is the first form
            // a regular << SET $varName TO expression >>
            if (context.variable() != null)
            {
                // add the expression (whatever it resolves to)
                Visit(context.expression());

                // now store the variable and clean up the stack
                string variableName = context.variable().GetText();
                compiler.Emit(ByteCode.StoreVariable, variableName);
                compiler.Emit(ByteCode.Pop);
            }
            // it is the second form
            else
            {
                // checking the expression is of the correct form
                var expression = context.expression();
                // TODO: is there really no more elegant way of doing this?!
                if (expression is YarnSpinnerParser.ExpMultDivModEqualsContext ||
                    expression is YarnSpinnerParser.ExpPlusMinusEqualsContext)
                {
                    // run the expression, it handles it from here
                    Visit(expression);
                }
                else
                {
                    // throw an error
                    throw Yarn.ParseException.Make(context, "Invalid expression inside assignment statement");
                }
            }

            return(0);
        }
コード例 #2
0
        // A set command: explicitly setting a value to an expression <<set $foo
        // to 1>>
        public override int VisitSet_statement([NotNull] YarnSpinnerParser.Set_statementContext context)
        {
            // Ensure that the correct result is on the stack by evaluating the
            // expression. If this assignment includes an operation (e.g. +=),
            // do that work here too.
            switch (context.op.Type)
            {
            case YarnSpinnerLexer.OPERATOR_ASSIGNMENT:
                this.Visit(context.expression());
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_ADDITION_EQUALS:
                this.GenerateCodeForOperation(Operator.Add, context.op, context.expression().Type, context.variable(), context.expression());
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION_EQUALS:
                this.GenerateCodeForOperation(Operator.Minus, context.op, context.expression().Type, context.variable(), context.expression());
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION_EQUALS:
                this.GenerateCodeForOperation(Operator.Multiply, context.op, context.expression().Type, context.variable(), context.expression());
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_DIVISION_EQUALS:
                this.GenerateCodeForOperation(Operator.Divide, context.op, context.expression().Type, context.variable(), context.expression());
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MODULUS_EQUALS:
                this.GenerateCodeForOperation(Operator.Modulo, context.op, context.expression().Type, context.variable(), context.expression());
                break;
            }

            // now store the variable and clean up the stack
            string variableName = context.variable().GetText();

            this.compiler.Emit(OpCode.StoreVariable, context.Start, new Operand(variableName));
            this.compiler.Emit(OpCode.Pop, context.Start);
            return(0);
        }
コード例 #3
0
        public override Yarn.IType VisitSet_statement([NotNull] YarnSpinnerParser.Set_statementContext context)
        {
            var variableContext   = context.variable();
            var expressionContext = context.expression();

            if (expressionContext == null || variableContext == null)
            {
                return(BuiltinTypes.Undefined);
            }

            var variableType = base.Visit(variableContext);

            if (variableType != BuiltinTypes.Undefined)
            {
                // giving the expression a hint just in case it is needed to help resolve any ambiguity on the expression
                // currently this is only useful in situations where we have a function as the rvalue of a known lvalue
                expressionContext.Hint = variableType;
            }

            var expressionType = base.Visit(expressionContext);

            var variableName = variableContext.GetText();

            ParserRuleContext[] terms = { variableContext, expressionContext };

            Operator @operator;

            switch (context.op.Type)
            {
            case YarnSpinnerLexer.OPERATOR_ASSIGNMENT:
                // Straight assignment supports any assignment, as long
                // as it's consistent; we already know the type of the
                // expression, so let's check to see if it's assignable
                // to the type of the variable
                if (variableType != BuiltinTypes.Undefined && TypeUtil.IsSubType(variableType, expressionType) == false)
                {
                    string message = $"{variableName} ({variableType?.Name ?? "undefined"}) cannot be assigned a {expressionType?.Name ?? "undefined"}";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                }
                else if (variableType == BuiltinTypes.Undefined && expressionType != BuiltinTypes.Undefined)
                {
                    // This variable was undefined, but we have a
                    // defined type for the value it was set to. Create
                    // an implicit declaration for the variable!

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

                    // Attempt to get a default value for the given type. If
                    // we can't get one, we can't create the definition.
                    var canCreateDefaultValue = TryGetDefaultValueForType(expressionType, out var defaultValue);

                    if (!canCreateDefaultValue)
                    {
                        diagnostics.Add(new Diagnostic(sourceFileName, variableContext, string.Format(CantDetermineVariableTypeError, variableName)));
                        break;
                    }

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

            case YarnSpinnerLexer.OPERATOR_MATHS_ADDITION_EQUALS:
                // += supports strings and numbers
                @operator      = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_ADDITION];
                expressionType = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION_EQUALS:
                // -=, *=, /=, %= supports only numbers
                @operator      = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION];
                expressionType = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION_EQUALS:
                @operator      = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION];
                expressionType = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_DIVISION_EQUALS:
                @operator      = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_DIVISION];
                expressionType = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MODULUS_EQUALS:
                @operator      = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_MODULUS];
                expressionType = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            default:
                throw new InvalidOperationException($"Internal error: {nameof(VisitSet_statement)} got unexpected operand {context.op.Text}");
            }

            if (variableType == BuiltinTypes.Undefined && expressionType == BuiltinTypes.Undefined)
            {
                this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, $"Type of expression \"{context.GetTextWithWhitespace()}\" can't be determined without more context. Please declare one or more terms.", Diagnostic.DiagnosticSeverity.Error));
            }

            // at this point we have either fully resolved the type of the expression or been unable to do so
            // we return the type of the expression regardless and rely on either elements to catch the issue
            return(expressionType);
        }
コード例 #4
0
 /// <summary>
 /// Visit a parse tree produced by <see cref="YarnSpinnerParser.set_statement"/>.
 /// <para>
 /// The default implementation returns the result of calling <see cref="AbstractParseTreeVisitor{Result}.VisitChildren(IRuleNode)"/>
 /// on <paramref name="context"/>.
 /// </para>
 /// </summary>
 /// <param name="context">The parse tree.</param>
 /// <return>The visitor result.</return>
 public virtual Result VisitSet_statement([NotNull] YarnSpinnerParser.Set_statementContext context)
 {
     return(VisitChildren(context));
 }
コード例 #5
0
 /// <summary>
 /// Exit a parse tree produced by <see cref="YarnSpinnerParser.set_statement"/>.
 /// <para>The default implementation does nothing.</para>
 /// </summary>
 /// <param name="context">The parse tree.</param>
 public virtual void ExitSet_statement([NotNull] YarnSpinnerParser.Set_statementContext context)
 {
 }
コード例 #6
0
        public override Yarn.IType VisitSet_statement([NotNull] YarnSpinnerParser.Set_statementContext context)
        {
            var variableContext   = context.variable();
            var expressionContext = context.expression();

            if (expressionContext == null || variableContext == null)
            {
                return(BuiltinTypes.Undefined);
            }

            var expressionType = base.Visit(expressionContext);
            var variableType   = base.Visit(variableContext);

            var variableName = variableContext.GetText();

            ParserRuleContext[] terms = { variableContext, expressionContext };

            Yarn.IType type;

            Operator @operator;

            switch (context.op.Type)
            {
            case YarnSpinnerLexer.OPERATOR_ASSIGNMENT:
                // Straight assignment supports any assignment, as long
                // as it's consistent; we already know the type of the
                // expression, so let's check to see if it's assignable
                // to the type of the variable
                if (variableType != BuiltinTypes.Undefined && TypeUtil.IsSubType(variableType, expressionType) == false)
                {
                    string message = $"{variableName} ({variableType?.Name ?? "undefined"}) cannot be assigned a {expressionType?.Name ?? "undefined"}";
                    this.diagnostics.Add(new Diagnostic(this.sourceFileName, context, message));
                }
                else if (variableType == BuiltinTypes.Undefined && expressionType != BuiltinTypes.Undefined)
                {
                    // This variable was undefined, but we have a
                    // defined type for the value it was set to. Create
                    // an implicit declaration for the variable!

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

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

            case YarnSpinnerLexer.OPERATOR_MATHS_ADDITION_EQUALS:
                // += supports strings and numbers
                @operator = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_ADDITION];
                type      = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION_EQUALS:
                // -=, *=, /=, %= supports only numbers
                @operator = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_SUBTRACTION];
                type      = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION_EQUALS:
                @operator = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_MULTIPLICATION];
                type      = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_DIVISION_EQUALS:
                @operator = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_DIVISION];
                type      = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            case YarnSpinnerLexer.OPERATOR_MATHS_MODULUS_EQUALS:
                @operator = CodeGenerationVisitor.TokensToOperators[YarnSpinnerLexer.OPERATOR_MATHS_MODULUS];
                type      = CheckOperation(context, terms, @operator, context.op.Text);
                break;

            default:
                throw new InvalidOperationException($"Internal error: {nameof(VisitSet_statement)} got unexpected operand {context.op.Text}");
            }

            if (expressionType == BuiltinTypes.Undefined)
            {
                // We don't know what this is set to, so we'll have to
                // assume it's ok. Return the variable type, if known.
                return(variableType);
            }

            return(expressionType);
        }