From 4ab755d0d2a541bb6d9fb37e5c5c9ae7404bbbe1 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Wed, 21 Nov 2018 17:18:35 +0100 Subject: [PATCH] Support for assigning multiple variables from a table --- Upsilon/Binder/Binder.cs | 113 ++++++++++-------- Upsilon/Binder/BoundKind.cs | 3 +- .../BoundMultiAssignmentStatement.cs | 19 +++ Upsilon/Evaluator/Evaluator.cs | 29 +++++ Upsilon/Parser/Parser.cs | 37 +++++- ...Syntax.cs => AssignmentStatementSyntax.cs} | 4 +- .../MultiAssignmentStatementSyntax.cs | 32 +++++ Upsilon/Parser/SyntaxKind.cs | 1 + 8 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs rename Upsilon/Parser/StatementSyntax/{AssignmentExpressionSyntax.cs => AssignmentStatementSyntax.cs} (81%) create mode 100644 Upsilon/Parser/StatementSyntax/MultiAssignmentStatementSyntax.cs diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 30ce4c6..0c527c1 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -48,7 +48,7 @@ namespace Upsilon.Binder case SyntaxKind.ExpressionStatement: return BindExpressionStatement((ExpressionStatementSyntax) s); case SyntaxKind.AssignmentStatement: - return BindAssignmentStatement((AssignmentExpressionSyntax) s); + return BindAssignmentStatement((AssignmentStatementSyntax) s); case SyntaxKind.BlockStatement: return BindBlockStatement((BlockStatementSyntax) s); case SyntaxKind.IfStatement: @@ -59,6 +59,8 @@ namespace Upsilon.Binder return BindFunctionAssignmentStatement((FunctionAssignmentStatementSyntax) s); case SyntaxKind.TableAssignmentStatement: return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s); + case SyntaxKind.MultiAssignmentStatement: + return BindMultiAssignmentStatement((MultiAssignmentStatementSyntax) s); } throw new NotImplementedException(s.Kind.ToString()); @@ -220,7 +222,53 @@ namespace Upsilon.Binder 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) { @@ -229,59 +277,30 @@ namespace Upsilon.Binder var boundExpression = BindExpression(e.Expression); 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) - { - 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(boundVariable, boundExpression); } - return new BoundVariableAssignment(variable, boundExpression); - } - - if (e.Identifier.Kind == SyntaxKind.IndexExpression) - { - throw new Exception("this"); } return new BoundExpressionStatement(new BoundLiteralExpression(new LuaNull())); } + private BoundStatement BindMultiAssignmentStatement(MultiAssignmentStatementSyntax s) + { + var ls = new List(); + 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) { diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index 2228a90..21112fc 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -23,6 +23,7 @@ namespace Upsilon.Binder BoundReturnStatement, BoundFunctionAssignmentStatement, BoundTableAssigmentStatement, - BoundFullstopIndexExpression + BoundFullstopIndexExpression, + BoundMultiAssignmentStatement } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs new file mode 100644 index 0000000..04984c3 --- /dev/null +++ b/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Upsilon.Binder +{ + public class BoundMultiAssignmentStatement : BoundStatement + { + public ImmutableArray Variables { get; } + public BoundExpression Assignment { get; } + + public BoundMultiAssignmentStatement(ImmutableArray variables, BoundExpression assignment) + { + Variables = variables; + Assignment = assignment; + } + + public override BoundKind Kind => BoundKind.BoundMultiAssignmentStatement; + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 1096408..c087a50 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -84,6 +84,7 @@ namespace Upsilon.Evaluator case BoundKind.BoundPromise: case BoundKind.BoundReturnStatement: case BoundKind.BoundTableAssigmentStatement: + case BoundKind.BoundMultiAssignmentStatement: EvaluateStatement((BoundStatement) b); break; default: @@ -116,6 +117,9 @@ namespace Upsilon.Evaluator case BoundKind.BoundTableAssigmentStatement: EvaluateTableAssignmentStatement((BoundTableAssigmentStatement) e); break; + case BoundKind.BoundMultiAssignmentStatement: + EvaluateMultiAssignmentStatement((BoundMultiAssignmentStatement) e); + break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; @@ -275,6 +279,31 @@ namespace Upsilon.Evaluator _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) { if (Scope.TryGet(e.Variable, out var val)) diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 4b09347..4a04f83 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Upsilon.Text; @@ -66,6 +67,10 @@ namespace Upsilon.Parser { return ParseAssignmentExpression(); } + if (Current.Kind == SyntaxKind.Identifier && Next.Kind == SyntaxKind.Comma) + { + return ParseAssignmentExpression(); + } if (Current.Kind == SyntaxKind.IfKeyword) { return ParseIfStatement(SyntaxKind.IfKeyword); @@ -216,7 +221,7 @@ namespace Upsilon.Parser return expression; } - private AssignmentExpressionSyntax ParseAssignmentExpression() + private StatementSyntax ParseAssignmentExpression() { SyntaxToken localKeyword = null; if (Current.Kind == SyntaxKind.LocalKeyword) @@ -225,9 +230,28 @@ namespace Upsilon.Parser } 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(){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 expression = ParseExpression(); - return new AssignmentExpressionSyntax(localKeyword, identifier, assignmentToken, expression); + return new AssignmentStatementSyntax(localKeyword, identifier, assignmentToken, expression); } private StatementSyntax ParseTableAssignmentExpression(ExpressionSyntax tableExpression) @@ -286,8 +310,7 @@ namespace Upsilon.Parser expression = ParseString(); break; case SyntaxKind.Identifier: - var token = MatchToken(SyntaxKind.Identifier); - expression = new VariableExpressionSyntax((IdentifierToken) token); + expression = ParseVariableExpression(); break; case SyntaxKind.OpenBrace: expression = ParseTable(); @@ -305,6 +328,12 @@ namespace Upsilon.Parser return expression; } + private ExpressionSyntax ParseVariableExpression() + { + var token = (IdentifierToken)MatchToken(SyntaxKind.Identifier); + return new VariableExpressionSyntax(token); + } + private ExpressionSyntax ParseFunctionCallExpression(ExpressionSyntax expression) { var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); diff --git a/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs b/Upsilon/Parser/StatementSyntax/AssignmentStatementSyntax.cs similarity index 81% rename from Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs rename to Upsilon/Parser/StatementSyntax/AssignmentStatementSyntax.cs index 85a50d8..3446e39 100644 --- a/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs +++ b/Upsilon/Parser/StatementSyntax/AssignmentStatementSyntax.cs @@ -3,9 +3,9 @@ using Upsilon.Text; 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) { LocalToken = localToken; diff --git a/Upsilon/Parser/StatementSyntax/MultiAssignmentStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/MultiAssignmentStatementSyntax.cs new file mode 100644 index 0000000..d2a9ffb --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/MultiAssignmentStatementSyntax.cs @@ -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 Identifiers { get; } + public SyntaxToken AssignmentToken { get; } + public ExpressionSyntax Expression { get; } + + public MultiAssignmentStatementSyntax(SyntaxToken localKeyword, IEnumerable identifiers, + SyntaxToken assignmentToken, ExpressionSyntax expression) + { + LocalKeyword = localKeyword; + Identifiers = identifiers.ToImmutableArray(); + AssignmentToken = assignmentToken; + Expression = expression; + } + + public override SyntaxKind Kind => SyntaxKind.MultiAssignmentStatement; + public override IEnumerable ChildNodes() + { + yield return LocalKeyword; + foreach (var expressionSyntax in Identifiers) + yield return expressionSyntax; + yield return AssignmentToken; + yield return Expression; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index f16ace0..1cc2699 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -58,6 +58,7 @@ namespace Upsilon.Parser TableExpression, IndexExpression, FullStopIndexExpression, + MultiAssignmentStatement, // script unit ScriptUnit,