Support for assigning multiple variables from a table
This commit is contained in:
		| @@ -48,7 +48,7 @@ namespace Upsilon.Binder | |||||||
|                 case SyntaxKind.ExpressionStatement: |                 case SyntaxKind.ExpressionStatement: | ||||||
|                     return BindExpressionStatement((ExpressionStatementSyntax) s); |                     return BindExpressionStatement((ExpressionStatementSyntax) s); | ||||||
|                 case SyntaxKind.AssignmentStatement: |                 case SyntaxKind.AssignmentStatement: | ||||||
|                     return BindAssignmentStatement((AssignmentExpressionSyntax) s); |                     return BindAssignmentStatement((AssignmentStatementSyntax) s); | ||||||
|                 case SyntaxKind.BlockStatement: |                 case SyntaxKind.BlockStatement: | ||||||
|                     return BindBlockStatement((BlockStatementSyntax) s); |                     return BindBlockStatement((BlockStatementSyntax) s); | ||||||
|                 case SyntaxKind.IfStatement: |                 case SyntaxKind.IfStatement: | ||||||
| @@ -59,6 +59,8 @@ namespace Upsilon.Binder | |||||||
|                     return BindFunctionAssignmentStatement((FunctionAssignmentStatementSyntax) s); |                     return BindFunctionAssignmentStatement((FunctionAssignmentStatementSyntax) s); | ||||||
|                 case SyntaxKind.TableAssignmentStatement: |                 case SyntaxKind.TableAssignmentStatement: | ||||||
|                     return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s); |                     return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s); | ||||||
|  |                 case SyntaxKind.MultiAssignmentStatement: | ||||||
|  |                     return BindMultiAssignmentStatement((MultiAssignmentStatementSyntax) s); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             throw new NotImplementedException(s.Kind.ToString()); |             throw new NotImplementedException(s.Kind.ToString()); | ||||||
| @@ -220,7 +222,53 @@ namespace Upsilon.Binder | |||||||
|             return new BoundExpressionStatement(exp); |             return new BoundExpressionStatement(exp); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private BoundStatement BindAssignmentStatement(AssignmentExpressionSyntax e) |         private VariableSymbol TryBindVariable(string name, bool isLocal, BoundExpression assignment) | ||||||
|  |         { | ||||||
|  |             if (!Scope.TryGetVariable(name, !isLocal, out var variable)) | ||||||
|  |             { | ||||||
|  |                 if (assignment.Type == Type.Table) | ||||||
|  |                 { | ||||||
|  |                     var tableExpression = (BoundTableExpression) assignment; | ||||||
|  |                     variable = new TableVariableSymbol(name, tableExpression.ValueType, isLocal); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     variable = new VariableSymbol(name, assignment.Type, isLocal); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (isLocal) | ||||||
|  |                     Scope.SetVariable(variable); | ||||||
|  |                 else | ||||||
|  |                     Scope.SetGlobalVariable(variable); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 // don't allow assigning different typed variables to a variable, unless either of them is nil, allow assigning nil to all variables | ||||||
|  |                 if (assignment.Type != variable.Type) | ||||||
|  |                 { | ||||||
|  |                     if (variable.Type == Type.Nil || assignment.Type == Type.Nil) | ||||||
|  |                     { | ||||||
|  |                         variable.Type = assignment.Type; | ||||||
|  |                     } | ||||||
|  |                     else if (variable.Type == Type.Unknown) | ||||||
|  |                     { | ||||||
|  |                         variable.Type = assignment.Type; | ||||||
|  |                     } | ||||||
|  |                     else if (assignment.Type == Type.Unknown && assignment is BoundVariableExpression v) | ||||||
|  |                     { | ||||||
|  |                         v.Variable.Type = variable.Type; | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |                         _diagnostics.LogCannotConvert(assignment.Type, variable.Type, assignment.Span); | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return variable; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private BoundStatement BindAssignmentStatement(AssignmentStatementSyntax e) | ||||||
|         { |         { | ||||||
|             if (e.Identifier.Kind == SyntaxKind.VariableExpression) |             if (e.Identifier.Kind == SyntaxKind.VariableExpression) | ||||||
|             { |             { | ||||||
| @@ -229,59 +277,30 @@ namespace Upsilon.Binder | |||||||
|                 var boundExpression = BindExpression(e.Expression); |                 var boundExpression = BindExpression(e.Expression); | ||||||
|  |  | ||||||
|                 var isLocal = e.LocalToken != null; |                 var isLocal = e.LocalToken != null; | ||||||
|                 if (!Scope.TryGetVariable(name, !isLocal, out var variable)) |                 var boundVariable = TryBindVariable(name, isLocal, boundExpression); | ||||||
|  |                 if (boundVariable != null) | ||||||
|                 { |                 { | ||||||
|                     if (boundExpression.Type == Type.Table) |                     return new BoundVariableAssignment(boundVariable, boundExpression); | ||||||
|                     { |  | ||||||
|                         var tableExpression = (BoundTableExpression) boundExpression; |  | ||||||
|                         variable = new TableVariableSymbol(name, tableExpression.ValueType, isLocal); |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                     { |  | ||||||
|                         variable = new VariableSymbol(name, boundExpression.Type, isLocal); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (isLocal) |  | ||||||
|                         Scope.SetVariable(variable); |  | ||||||
|                     else |  | ||||||
|                         Scope.SetGlobalVariable(variable); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     // don't allow assigning different typed variables to a variable, unless either of them is nil, allow assigning nil to all variables |  | ||||||
|                     if (boundExpression.Type != variable.Type) |  | ||||||
|                     { |  | ||||||
|                         if (variable.Type == Type.Nil || boundExpression.Type == Type.Nil) |  | ||||||
|                         { |  | ||||||
|                             variable.Type = boundExpression.Type; |  | ||||||
|                         } |  | ||||||
|                         else if (variable.Type == Type.Unknown) |  | ||||||
|                         { |  | ||||||
|                             variable.Type = boundExpression.Type; |  | ||||||
|                         } |  | ||||||
|                         else if (boundExpression.Type == Type.Unknown && boundExpression is BoundVariableExpression v) |  | ||||||
|                         { |  | ||||||
|                             v.Variable.Type = variable.Type; |  | ||||||
|                         } |  | ||||||
|                         else |  | ||||||
|                         { |  | ||||||
|                             _diagnostics.LogCannotConvert(boundExpression.Type, variable.Type, e.Span); |  | ||||||
|                             return new BoundExpressionStatement(boundExpression); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return new BoundVariableAssignment(variable, boundExpression); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (e.Identifier.Kind == SyntaxKind.IndexExpression) |  | ||||||
|             { |  | ||||||
|                 throw new Exception("this"); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return new BoundExpressionStatement(new BoundLiteralExpression(new LuaNull())); |             return new BoundExpressionStatement(new BoundLiteralExpression(new LuaNull())); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private BoundStatement BindMultiAssignmentStatement(MultiAssignmentStatementSyntax s) | ||||||
|  |         { | ||||||
|  |             var ls = new List<VariableSymbol>(); | ||||||
|  |             var assignment = BindExpression(s.Expression); | ||||||
|  |             var isLocal = s.LocalKeyword != null; | ||||||
|  |  | ||||||
|  |             foreach (var identifierToken in s.Identifiers) | ||||||
|  |             { | ||||||
|  |                 var boundVariable = TryBindVariable(identifierToken.Name, isLocal, assignment); | ||||||
|  |                 ls.Add(boundVariable); | ||||||
|  |             } | ||||||
|  |             return new BoundMultiAssignmentStatement(ls.ToImmutableArray(), assignment); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private BoundStatement BindBlockStatement(BlockStatementSyntax e) |         private BoundStatement BindBlockStatement(BlockStatementSyntax e) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ namespace Upsilon.Binder | |||||||
|         BoundReturnStatement, |         BoundReturnStatement, | ||||||
|         BoundFunctionAssignmentStatement, |         BoundFunctionAssignmentStatement, | ||||||
|         BoundTableAssigmentStatement, |         BoundTableAssigmentStatement, | ||||||
|         BoundFullstopIndexExpression |         BoundFullstopIndexExpression, | ||||||
|  |         BoundMultiAssignmentStatement | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Immutable; | ||||||
|  |  | ||||||
|  | namespace Upsilon.Binder | ||||||
|  | { | ||||||
|  |     public class BoundMultiAssignmentStatement : BoundStatement | ||||||
|  |     { | ||||||
|  |         public ImmutableArray<VariableSymbol> Variables { get; } | ||||||
|  |         public BoundExpression Assignment { get; } | ||||||
|  |  | ||||||
|  |         public BoundMultiAssignmentStatement(ImmutableArray<VariableSymbol> variables, BoundExpression assignment) | ||||||
|  |         { | ||||||
|  |             Variables = variables; | ||||||
|  |             Assignment = assignment; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override BoundKind Kind => BoundKind.BoundMultiAssignmentStatement; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -84,6 +84,7 @@ namespace Upsilon.Evaluator | |||||||
|                 case BoundKind.BoundPromise: |                 case BoundKind.BoundPromise: | ||||||
|                 case BoundKind.BoundReturnStatement: |                 case BoundKind.BoundReturnStatement: | ||||||
|                 case BoundKind.BoundTableAssigmentStatement: |                 case BoundKind.BoundTableAssigmentStatement: | ||||||
|  |                 case BoundKind.BoundMultiAssignmentStatement: | ||||||
|                     EvaluateStatement((BoundStatement) b); |                     EvaluateStatement((BoundStatement) b); | ||||||
|                     break; |                     break; | ||||||
|                 default: |                 default: | ||||||
| @@ -116,6 +117,9 @@ namespace Upsilon.Evaluator | |||||||
|                 case BoundKind.BoundTableAssigmentStatement: |                 case BoundKind.BoundTableAssigmentStatement: | ||||||
|                     EvaluateTableAssignmentStatement((BoundTableAssigmentStatement) e); |                     EvaluateTableAssignmentStatement((BoundTableAssigmentStatement) e); | ||||||
|                     break; |                     break; | ||||||
|  |                 case BoundKind.BoundMultiAssignmentStatement: | ||||||
|  |                     EvaluateMultiAssignmentStatement((BoundMultiAssignmentStatement) e); | ||||||
|  |                     break; | ||||||
|                 default: |                 default: | ||||||
|                     EvaluateExpressionStatement((BoundExpressionStatement) e); |                     EvaluateExpressionStatement((BoundExpressionStatement) e); | ||||||
|                     break; |                     break; | ||||||
| @@ -275,6 +279,31 @@ namespace Upsilon.Evaluator | |||||||
|             _lastValue = val; |             _lastValue = val; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private void EvaluateMultiAssignmentStatement(BoundMultiAssignmentStatement e) | ||||||
|  |         { | ||||||
|  |             var val = EvaluateExpression(e.Assignment); | ||||||
|  |             if (val.Type == Type.Table) | ||||||
|  |             { | ||||||
|  |                 var table = (LuaTable) val; | ||||||
|  |                 for (var i = 0; i < e.Variables.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     var variableSymbol = e.Variables[i]; | ||||||
|  |                     if (variableSymbol == null) | ||||||
|  |                         continue; | ||||||
|  |                     var value = table.Get(_diagnostics, e.Span, new NumberLong(i + 1), Scope); | ||||||
|  |                     if (variableSymbol.Local) | ||||||
|  |                         Scope.Set(variableSymbol, value); | ||||||
|  |                     else | ||||||
|  |                         Scope.SetGlobal(variableSymbol, value); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 _diagnostics.LogError($"Can't assign type '{val.Type}' to multiple variables.", e.Span); | ||||||
|  |             } | ||||||
|  |             _lastValue = val; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private LuaType EvaluateVariableExpression(BoundVariableExpression e) |         private LuaType EvaluateVariableExpression(BoundVariableExpression e) | ||||||
|         { |         { | ||||||
|             if (Scope.TryGet(e.Variable, out var val)) |             if (Scope.TryGet(e.Variable, out var val)) | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using Upsilon.Text; | using Upsilon.Text; | ||||||
| @@ -66,6 +67,10 @@ namespace Upsilon.Parser | |||||||
|             { |             { | ||||||
|                 return ParseAssignmentExpression(); |                 return ParseAssignmentExpression(); | ||||||
|             } |             } | ||||||
|  |             if (Current.Kind == SyntaxKind.Identifier && Next.Kind == SyntaxKind.Comma) | ||||||
|  |             { | ||||||
|  |                 return ParseAssignmentExpression(); | ||||||
|  |             } | ||||||
|             if (Current.Kind == SyntaxKind.IfKeyword) |             if (Current.Kind == SyntaxKind.IfKeyword) | ||||||
|             { |             { | ||||||
|                 return ParseIfStatement(SyntaxKind.IfKeyword); |                 return ParseIfStatement(SyntaxKind.IfKeyword); | ||||||
| @@ -216,7 +221,7 @@ namespace Upsilon.Parser | |||||||
|             return expression; |             return expression; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private AssignmentExpressionSyntax ParseAssignmentExpression() |         private StatementSyntax ParseAssignmentExpression() | ||||||
|         { |         { | ||||||
|             SyntaxToken localKeyword = null; |             SyntaxToken localKeyword = null; | ||||||
|             if (Current.Kind == SyntaxKind.LocalKeyword) |             if (Current.Kind == SyntaxKind.LocalKeyword) | ||||||
| @@ -225,9 +230,28 @@ namespace Upsilon.Parser | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             var identifier = ParseExpression(); |             var identifier = ParseExpression(); | ||||||
|  |             if (Current.Kind == SyntaxKind.Comma) | ||||||
|  |             { | ||||||
|  |                 if (identifier.Kind != SyntaxKind.VariableExpression) | ||||||
|  |                 { | ||||||
|  |                     _diagnostics.LogError("Only identifiers can be used for a multi assignment statement.", identifier.Span); | ||||||
|  |                     return new ExpressionStatementSyntax(new BadExpressionSyntax()); | ||||||
|  |                 } | ||||||
|  |                 var cast = (VariableExpressionSyntax)identifier; | ||||||
|  |                 var ls = new List<IdentifierToken>(){cast.Identifier}; | ||||||
|  |                 while (Current.Kind == SyntaxKind.Comma) | ||||||
|  |                 { | ||||||
|  |                     NextToken(); | ||||||
|  |                     ls.Add((IdentifierToken) MatchToken(SyntaxKind.Identifier)); | ||||||
|  |                 } | ||||||
|  |                 var assignmentTokenMulti = MatchToken(SyntaxKind.Equals); | ||||||
|  |                 var expressionMulti      = ParseExpression(); | ||||||
|  |                 return new MultiAssignmentStatementSyntax(localKeyword, ls, assignmentTokenMulti, expressionMulti); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             var assignmentToken = MatchToken(SyntaxKind.Equals); |             var assignmentToken = MatchToken(SyntaxKind.Equals); | ||||||
|             var expression = ParseExpression(); |             var expression = ParseExpression(); | ||||||
|             return new AssignmentExpressionSyntax(localKeyword, identifier, assignmentToken, expression); |             return new AssignmentStatementSyntax(localKeyword, identifier, assignmentToken, expression); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private StatementSyntax ParseTableAssignmentExpression(ExpressionSyntax tableExpression) |         private StatementSyntax ParseTableAssignmentExpression(ExpressionSyntax tableExpression) | ||||||
| @@ -286,8 +310,7 @@ namespace Upsilon.Parser | |||||||
|                     expression = ParseString(); |                     expression = ParseString(); | ||||||
|                     break; |                     break; | ||||||
|                 case SyntaxKind.Identifier: |                 case SyntaxKind.Identifier: | ||||||
|                     var token = MatchToken(SyntaxKind.Identifier); |                     expression = ParseVariableExpression(); | ||||||
|                     expression = new VariableExpressionSyntax((IdentifierToken) token); |  | ||||||
|                     break; |                     break; | ||||||
|                 case SyntaxKind.OpenBrace: |                 case SyntaxKind.OpenBrace: | ||||||
|                     expression = ParseTable(); |                     expression = ParseTable(); | ||||||
| @@ -305,6 +328,12 @@ namespace Upsilon.Parser | |||||||
|             return expression; |             return expression; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private ExpressionSyntax ParseVariableExpression() | ||||||
|  |         { | ||||||
|  |             var token = (IdentifierToken)MatchToken(SyntaxKind.Identifier); | ||||||
|  |             return new VariableExpressionSyntax(token); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private ExpressionSyntax ParseFunctionCallExpression(ExpressionSyntax expression) |         private ExpressionSyntax ParseFunctionCallExpression(ExpressionSyntax expression) | ||||||
|         { |         { | ||||||
|             var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); |             var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); | ||||||
|   | |||||||
| @@ -3,9 +3,9 @@ using Upsilon.Text; | |||||||
| 
 | 
 | ||||||
| namespace Upsilon.Parser | namespace Upsilon.Parser | ||||||
| { | { | ||||||
|     public sealed class AssignmentExpressionSyntax : StatementSyntax |     public sealed class AssignmentStatementSyntax : StatementSyntax | ||||||
|     { |     { | ||||||
|         public AssignmentExpressionSyntax(SyntaxToken localToken, ExpressionSyntax identifyExpression, SyntaxToken equalsToken, |         public AssignmentStatementSyntax(SyntaxToken localToken, ExpressionSyntax identifyExpression, SyntaxToken equalsToken, | ||||||
|             ExpressionSyntax expression) |             ExpressionSyntax expression) | ||||||
|         { |         { | ||||||
|             LocalToken = localToken; |             LocalToken = localToken; | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Immutable; | ||||||
|  |  | ||||||
|  | namespace Upsilon.Parser | ||||||
|  | { | ||||||
|  |     public class MultiAssignmentStatementSyntax : StatementSyntax | ||||||
|  |     { | ||||||
|  |         public SyntaxToken LocalKeyword { get; } | ||||||
|  |         public ImmutableArray<IdentifierToken> Identifiers { get; } | ||||||
|  |         public SyntaxToken AssignmentToken { get; } | ||||||
|  |         public ExpressionSyntax Expression { get; } | ||||||
|  |  | ||||||
|  |         public MultiAssignmentStatementSyntax(SyntaxToken localKeyword, IEnumerable<IdentifierToken> identifiers, | ||||||
|  |             SyntaxToken assignmentToken, ExpressionSyntax expression) | ||||||
|  |         { | ||||||
|  |             LocalKeyword = localKeyword; | ||||||
|  |             Identifiers = identifiers.ToImmutableArray(); | ||||||
|  |             AssignmentToken = assignmentToken; | ||||||
|  |             Expression = expression; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override SyntaxKind Kind => SyntaxKind.MultiAssignmentStatement; | ||||||
|  |         public override IEnumerable<SyntaxNode> ChildNodes() | ||||||
|  |         { | ||||||
|  |             yield return LocalKeyword; | ||||||
|  |             foreach (var expressionSyntax in Identifiers) | ||||||
|  |                 yield return expressionSyntax; | ||||||
|  |             yield return AssignmentToken; | ||||||
|  |             yield return Expression; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -58,6 +58,7 @@ namespace Upsilon.Parser | |||||||
|         TableExpression, |         TableExpression, | ||||||
|         IndexExpression, |         IndexExpression, | ||||||
|         FullStopIndexExpression, |         FullStopIndexExpression, | ||||||
|  |         MultiAssignmentStatement, | ||||||
|  |  | ||||||
|         // script unit |         // script unit | ||||||
|         ScriptUnit, |         ScriptUnit, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user