// 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); }
// 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); }
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); }
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); }