diff --git a/src/MoonSharp.Interpreter/Tree/Lexer/Lexer.cs b/src/MoonSharp.Interpreter/Tree/Lexer/Lexer.cs index de47d322..7082f621 100755 --- a/src/MoonSharp.Interpreter/Tree/Lexer/Lexer.cs +++ b/src/MoonSharp.Interpreter/Tree/Lexer/Lexer.cs @@ -175,14 +175,14 @@ private Token ReadToken() { char next = CursorCharNext(); if (next == '.') - return PotentiallyDoubleCharOperator('.', TokenType.Op_Concat, TokenType.VarArgs, fromLine, fromCol); + return PotentiallyDoubleCharOperator(TokenType.Op_Concat, '.', TokenType.VarArgs, "...", '=', TokenType.Op_Assignment, "..=", fromLine, fromCol); else if (LexerUtils.CharIsDigit(next)) return ReadNumberToken(fromLine, fromCol, true); else return CreateToken(TokenType.Dot, fromLine, fromCol, "."); } case '+': - return CreateSingleCharToken(TokenType.Op_Add, fromLine, fromCol); + return PotentiallyDoubleCharOperator('=', TokenType.Op_Add, TokenType.Op_Assignment, fromLine, fromCol); case '-': { char next = CursorCharNext(); @@ -190,19 +190,24 @@ private Token ReadToken() { return ReadComment(fromLine, fromCol); } + else if (next == '=') + { + CursorCharNext(); + return CreateToken(TokenType.Op_Assignment, fromLine, fromCol, "-="); + } else { return CreateToken(TokenType.Op_MinusOrSub, fromLine, fromCol, "-"); } } case '*': - return CreateSingleCharToken(TokenType.Op_Mul, fromLine, fromCol); + return PotentiallyDoubleCharOperator('=', TokenType.Op_Mul, TokenType.Op_Assignment, fromLine, fromCol); case '/': - return CreateSingleCharToken(TokenType.Op_Div, fromLine, fromCol); + return PotentiallyDoubleCharOperator('=', TokenType.Op_Div, TokenType.Op_Assignment, fromLine, fromCol); case '%': - return CreateSingleCharToken(TokenType.Op_Mod, fromLine, fromCol); + return PotentiallyDoubleCharOperator('=', TokenType.Op_Mod, TokenType.Op_Assignment, fromLine, fromCol); case '^': - return CreateSingleCharToken(TokenType.Op_Pwr, fromLine, fromCol); + return PotentiallyDoubleCharOperator('=', TokenType.Op_Pwr, TokenType.Op_Assignment, fromLine, fromCol); case '$': return PotentiallyDoubleCharOperator('{', TokenType.Op_Dollar, TokenType.Brk_Open_Curly_Shared, fromLine, fromCol); case '#': @@ -540,8 +545,25 @@ private Token PotentiallyDoubleCharOperator(char expectedSecondChar, TokenType s else return CreateToken(singleCharToken, fromLine, fromCol, op); } + private Token PotentiallyDoubleCharOperator(TokenType singleCharToken, char expectedSecondChar, TokenType doubleCharToken, string doubleCharText, char alternateExpectedSecondChar, TokenType alternateDoubleCharToken, string alternateDoubleCharText, int fromLine, int fromCol) + { + string op = CursorChar().ToString(); + CursorCharNext(); + if (CursorChar() == expectedSecondChar) + { + CursorCharNext(); + return CreateToken(doubleCharToken, fromLine, fromCol, doubleCharText); + } + else if (CursorChar() == alternateExpectedSecondChar) + { + CursorCharNext(); + return CreateToken(alternateDoubleCharToken, fromLine, fromCol, alternateDoubleCharText); + } + else + return CreateToken(singleCharToken, fromLine, fromCol, op); + } private Token CreateNameToken(string name, int fromLine, int fromCol) { @@ -557,7 +579,6 @@ private Token CreateNameToken(string name, int fromLine, int fromCol) } } - private Token CreateToken(TokenType tokenType, int fromLine, int fromCol, string text = null) { Token t = new Token(tokenType, m_SourceId, fromLine, fromCol, m_Line, m_Col, m_PrevLineTo, m_PrevColTo) @@ -583,9 +604,5 @@ private string ReadNameToken() return name.ToString(); } - - - - } } diff --git a/src/MoonSharp.Interpreter/Tree/Statement.cs b/src/MoonSharp.Interpreter/Tree/Statement.cs index 1ccadeca..2c354c40 100644 --- a/src/MoonSharp.Interpreter/Tree/Statement.cs +++ b/src/MoonSharp.Interpreter/Tree/Statement.cs @@ -39,7 +39,7 @@ protected static Statement CreateStatement(ScriptLoadingContext lcontext, out bo case TokenType.Function: return new FunctionDefinitionStatement(lcontext, false, null); case TokenType.Local: - Token localToken = lcontext.Lexer.Current; + Token localToken = tkn; lcontext.Lexer.Next(); if (lcontext.Lexer.Current.Type == TokenType.Function) return new FunctionDefinitionStatement(lcontext, true, localToken); @@ -52,14 +52,12 @@ protected static Statement CreateStatement(ScriptLoadingContext lcontext, out bo return new BreakStatement(lcontext); default: { - Token l = lcontext.Lexer.Current; Expression exp = Expression.PrimaryExp(lcontext); - FunctionCallExpression fnexp = exp as FunctionCallExpression; - if (fnexp != null) + if (exp is FunctionCallExpression fnexp) return new FunctionCallStatement(lcontext, fnexp); else - return new AssignmentStatement(lcontext, exp, l); + return new AssignmentStatement(lcontext, exp, tkn); } } } diff --git a/src/MoonSharp.Interpreter/Tree/Statements/AssignmentStatement.cs b/src/MoonSharp.Interpreter/Tree/Statements/AssignmentStatement.cs index a82fe67a..68d8c155 100644 --- a/src/MoonSharp.Interpreter/Tree/Statements/AssignmentStatement.cs +++ b/src/MoonSharp.Interpreter/Tree/Statements/AssignmentStatement.cs @@ -32,6 +32,10 @@ public AssignmentStatement(ScriptLoadingContext lcontext, Token startToken) lcontext.Lexer.Next(); } + if (first.Type == TokenType.Local && lcontext.Lexer.Current.Type == TokenType.Op_Assignment && lcontext.Lexer.Current.Text != "=") { + throw new SyntaxErrorException(lcontext.Lexer.Current, $"Expected '=' after local, got '{lcontext.Lexer.Current.Text}'."); + } + if (lcontext.Lexer.Current.Type == TokenType.Op_Assignment) { CheckTokenType(lcontext, TokenType.Op_Assignment); @@ -61,21 +65,54 @@ public AssignmentStatement(ScriptLoadingContext lcontext, Expression firstExpres { m_LValues.Add(CheckVar(lcontext, firstExpression)); + List leftExpressions = new() {firstExpression}; + while (lcontext.Lexer.Current.Type == TokenType.Comma) { lcontext.Lexer.Next(); - Expression e = Expression.PrimaryExp(lcontext); - m_LValues.Add(CheckVar(lcontext, e)); + Expression exp = Expression.PrimaryExp(lcontext); + m_LValues.Add(CheckVar(lcontext, exp)); + leftExpressions.Add(exp); } + Token assignmentType = lcontext.Lexer.Current; + CheckTokenType(lcontext, TokenType.Op_Assignment); m_RValues = Expression.ExprList(lcontext); + // Replace e.g. "a += b" with "a = a + b" + if (assignmentType.Text != "=") + { + TokenType operationTokenType = assignmentType.Text switch + { + "+=" => TokenType.Op_Add, + "-=" => TokenType.Op_MinusOrSub, + "*=" => TokenType.Op_Mul, + "/=" => TokenType.Op_Div, + "%=" => TokenType.Op_Mod, + "^=" => TokenType.Op_Pwr, + "..=" => TokenType.Op_Concat, + _ => throw new InternalErrorException($"Assignment operator not recognised: {assignmentType.Text}"), + }; + assignmentType.Text = "="; + + for (int valueIndex = 0; valueIndex < m_RValues.Count; valueIndex++) + { + if (valueIndex < leftExpressions.Count) + { + object operatorChain = BinaryOperatorExpression.BeginOperatorChain(); + BinaryOperatorExpression.AddExpressionToChain(operatorChain, leftExpressions[valueIndex]); + BinaryOperatorExpression.AddOperatorToChain(operatorChain, new Token(operationTokenType, first.SourceId, first.FromLine, first.FromCol, first.ToLine, first.ToCol, first.PrevLine, first.PrevCol)); + BinaryOperatorExpression.AddExpressionToChain(operatorChain, m_RValues[valueIndex]); + m_RValues[valueIndex] = BinaryOperatorExpression.CommitOperatorChain(operatorChain, lcontext); + } + } + } + Token last = lcontext.Lexer.Current; m_Ref = first.GetSourceRefUpTo(last); lcontext.Source.Refs.Add(m_Ref); - } private IVariable CheckVar(ScriptLoadingContext lcontext, Expression firstExpression) @@ -88,7 +125,6 @@ private IVariable CheckVar(ScriptLoadingContext lcontext, Expression firstExpres return v; } - public override void Compile(Execution.VM.ByteCode bc) { using (bc.EnterSource(m_Ref)) @@ -106,6 +142,5 @@ public override void Compile(Execution.VM.ByteCode bc) bc.Emit_Pop(m_RValues.Count); } } - } }