diff --git a/Upsilon/BaseTypes/LuaTable.cs b/Upsilon/BaseTypes/LuaTable.cs index 518be13..06d1fd3 100644 --- a/Upsilon/BaseTypes/LuaTable.cs +++ b/Upsilon/BaseTypes/LuaTable.cs @@ -1,4 +1,5 @@ using System.Linq; +using Upsilon.Binder; using Upsilon.Evaluator; namespace Upsilon.BaseTypes @@ -28,5 +29,10 @@ namespace Upsilon.BaseTypes } return new LuaNull(); } + + public void Set(string s, LuaType obj) + { + EvaluationScope.Set(new VariableSymbol(s, obj.Type, false), obj); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 19d1682..8a94205 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -56,6 +56,8 @@ namespace Upsilon.Binder return BindReturnStatement((ReturnStatementSyntax) s); case SyntaxKind.FunctionAssignmentStatement: return BindFunctionAssignmentStatement((FunctionAssignmentStatementSyntax) s); + case SyntaxKind.TableAssignmentStatement: + return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s); } throw new NotImplementedException(s.Kind.ToString()); @@ -217,52 +219,64 @@ namespace Upsilon.Binder private BoundStatement BindAssignmentStatement(AssignmentExpressionSyntax e) { - var name = e.Identifier.Name; - var boundExpression = BindExpression(e.Expression); + if (e.Identifier.Kind == SyntaxKind.VariableExpression) + { + var variableExpression = (VariableExpressionSyntax) e.Identifier; + var name = variableExpression.Identifier.Name; + var boundExpression = BindExpression(e.Expression); - var isLocal = e.LocalToken != null; - if (!Scope.TryGetVariable(name, !isLocal, out var variable)) - { - if (boundExpression.Type == Type.Table) + var isLocal = e.LocalToken != null; + if (!Scope.TryGetVariable(name, !isLocal, out var variable)) { - 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) + if (boundExpression.Type == Type.Table) { - 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; + var tableExpression = (BoundTableExpression) boundExpression; + variable = new TableVariableSymbol(name, tableExpression.ValueType, isLocal); } else { - _diagnostics.LogCannotConvert(boundExpression.Type, variable.Type, e.Span); - return new BoundExpressionStatement(boundExpression); + 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); } - return new BoundVariableAssignment(variable, boundExpression); + if (e.Identifier.Kind == SyntaxKind.IndexExpression) + { + throw new Exception("this"); + } + + return new BoundExpressionStatement(new BoundLiteralExpression(new LuaNull())); } @@ -419,6 +433,11 @@ namespace Upsilon.Binder var expression = BindExpression(e.Expression); var index = BindExpression(e.Index); + if (index.Type != Type.Number && index.Type != Type.String && index.Type != Type.Unknown) + { + _diagnostics.LogInvalidIndexExpression(expression.Type, index.Type, e.Span); + return new BoundLiteralExpression(new LuaNull()); + } switch (expression.Type) { case Type.Table: @@ -432,5 +451,12 @@ namespace Upsilon.Binder return new BoundLiteralExpression(new LuaNull()); } } + + private BoundStatement BindTableAssignmentStatement(TableAssigmentStatementSyntax e) + { + var tableIndexExpression = (BoundIndexExpression)BindExpression(e.TableExpression); + var value = BindExpression(e.Expression); + return new BoundTableAssigmentStatement(tableIndexExpression, value); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index 6865d82..14be700 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -21,6 +21,7 @@ namespace Upsilon.Binder BoundFunctionExpression, BoundPromise, BoundReturnStatement, - BoundFunctionAssignmentStatement + BoundFunctionAssignmentStatement, + BoundTableAssigmentStatement } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs new file mode 100644 index 0000000..025a2ee --- /dev/null +++ b/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs @@ -0,0 +1,16 @@ +namespace Upsilon.Binder +{ + public class BoundTableAssigmentStatement : BoundStatement + { + public BoundIndexExpression TableIndexExpression { get; } + public BoundExpression Value { get; } + + public BoundTableAssigmentStatement(BoundIndexExpression tableIndexExpression, BoundExpression value) + { + TableIndexExpression = tableIndexExpression; + Value = value; + } + + public override BoundKind Kind => BoundKind.BoundTableAssigmentStatement; + } +} \ No newline at end of file diff --git a/Upsilon/Diagnostics.cs b/Upsilon/Diagnostics.cs index bb27445..fd2674b 100644 --- a/Upsilon/Diagnostics.cs +++ b/Upsilon/Diagnostics.cs @@ -34,6 +34,10 @@ namespace Upsilon { LogError($"Invalid character found. Expected: '{expectedToken}'", location); } + public void LogBadCharacter(TextSpan location) + { + LogError($"Invalid character found.", location); + } public void LogUnknownVariable(TextSpan span, string variable) { diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index efa7a22..097cf5b 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -15,26 +15,17 @@ namespace Upsilon.Evaluator private LuaType _returnValue; internal EvaluationScope Scope { get; } private bool HasReturned { get; set; } - public int Identifier { get; } internal Evaluator(Diagnostics diagnostics, Dictionary variables) { _diagnostics = diagnostics; Scope = new EvaluationScope(variables); - Identifier = new Random().Next(); } private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope) { _diagnostics = diagnostics; Scope = new EvaluationScope(parentScope); - Identifier = new Random().Next(); - } - private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope, int identifier) - { - _diagnostics = diagnostics; - Scope = new EvaluationScope(parentScope); - Identifier = identifier; } public LuaType Evaluate(BoundScript e) @@ -90,6 +81,7 @@ namespace Upsilon.Evaluator case BoundKind.BoundFunctionAssignmentStatement: case BoundKind.BoundPromise: case BoundKind.BoundReturnStatement: + case BoundKind.BoundTableAssigmentStatement: EvaluateStatement((BoundStatement) b); break; default: @@ -119,6 +111,9 @@ namespace Upsilon.Evaluator case BoundKind.BoundFunctionAssignmentStatement: EvaluateBoundFunctionAssigmentStatement((BoundFunctionAssignmentStatement) e); break; + case BoundKind.BoundTableAssigmentStatement: + EvaluateTableAssignmentStatement((BoundTableAssigmentStatement) e); + break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; @@ -354,5 +349,19 @@ namespace Upsilon.Evaluator var indexer = evaluator.EvaluateExpression(e.Index); return indexable.Get(indexer.ToString(), scope); } + + private void EvaluateTableAssignmentStatement(BoundTableAssigmentStatement e) + { + var table = EvaluateExpression(e.TableIndexExpression.Identifier); + var index = EvaluateExpression(e.TableIndexExpression.Index); + var value = EvaluateExpression(e.Value); + if (table.Type != Type.Table) + { + throw new Exception("Not a table"); + } + + var t = (LuaTable) table; + t.Set(index.ToString(), value); + } } } \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 7d080e6..e0dc927 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -83,7 +83,6 @@ namespace Upsilon.Parser return ParseFunctionAssignmentStatement(); } - return ParseExpressionStatement(); } @@ -109,7 +108,7 @@ namespace Upsilon.Parser case SyntaxKind.ElseIfKeyword: var nextElseIf = new ElseIfStatementSyntax((IfStatementSyntax) ParseIfStatement(SyntaxKind.ElseIfKeyword)); - return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, + return new IfStatementSyntax(ifToken, (ExpressionStatementSyntax) condition, thenToken, (BlockStatementSyntax) block, nextElseIf); case SyntaxKind.ElseKeyword: { @@ -117,11 +116,11 @@ namespace Upsilon.Parser var elseBlock = ParseBlockStatement(new[]{SyntaxKind.EndKeyword}); var endEndToken = MatchToken(SyntaxKind.EndKeyword); var elseStatement = new ElseStatementSyntax(elseToken, (BlockStatementSyntax) elseBlock, endEndToken); - return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, elseStatement); + return new IfStatementSyntax(ifToken, (ExpressionStatementSyntax) condition, thenToken, (BlockStatementSyntax) block, elseStatement); } case SyntaxKind.EndKeyword: var endToken = MatchToken(SyntaxKind.EndKeyword); - return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, endToken); + return new IfStatementSyntax(ifToken, (ExpressionStatementSyntax) condition, thenToken, (BlockStatementSyntax) block, endToken); default: throw new ArgumentOutOfRangeException(); } @@ -172,9 +171,13 @@ namespace Upsilon.Parser return new FunctionAssignmentStatementSyntax(localToken, identifier, functionExpression); } - private ExpressionStatementSyntax ParseExpressionStatement() + private StatementSyntax ParseExpressionStatement() { var expression = ParseExpression(); + if (expression.Kind == SyntaxKind.IndexExpression && Current.Kind == SyntaxKind.Equals) + { + return ParseTableAssignmentExpression(expression); + } return new ExpressionStatementSyntax(expression); } @@ -213,12 +216,21 @@ namespace Upsilon.Parser { localKeyword = MatchToken(SyntaxKind.LocalKeyword); } - var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); + + var identifier = ParseExpression(); var assignmentToken = MatchToken(SyntaxKind.Equals); var expression = ParseExpression(); return new AssignmentExpressionSyntax(localKeyword, identifier, assignmentToken, expression); } + private StatementSyntax ParseTableAssignmentExpression(ExpressionSyntax tableExpression) + { + var assignmentToken = MatchToken(SyntaxKind.Equals); + var expression = ParseExpression(); + return new TableAssigmentStatementSyntax(tableExpression, assignmentToken, expression); + } + + private ExpressionSyntax ParseBinaryExpression(SyntaxKindPrecedence.Precedence parentPrecedence = SyntaxKindPrecedence.Precedence.None) { ExpressionSyntax left; @@ -278,7 +290,7 @@ namespace Upsilon.Parser expression = new LiteralExpressionSyntax(nilToken, null); break; default: - _diagnostics.LogBadCharacter(new TextSpan(_position, 1), SyntaxKind.Identifier); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); NextToken(); expression = new BadExpressionSyntax(); break; diff --git a/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs b/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs index de50fc5..85a50d8 100644 --- a/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs +++ b/Upsilon/Parser/StatementSyntax/AssignmentExpressionSyntax.cs @@ -5,11 +5,11 @@ namespace Upsilon.Parser { public sealed class AssignmentExpressionSyntax : StatementSyntax { - public AssignmentExpressionSyntax(SyntaxToken localToken, IdentifierToken identifier, SyntaxToken equalsToken, + public AssignmentExpressionSyntax(SyntaxToken localToken, ExpressionSyntax identifyExpression, SyntaxToken equalsToken, ExpressionSyntax expression) { LocalToken = localToken; - Identifier = identifier; + Identifier = identifyExpression; EqualsToken = equalsToken; Expression = expression; var start = LocalToken?.Span.Start ?? Identifier.Span.Start; @@ -19,7 +19,7 @@ namespace Upsilon.Parser public override SyntaxKind Kind => SyntaxKind.AssignmentStatement; public SyntaxToken LocalToken { get; } - public IdentifierToken Identifier { get; } + public ExpressionSyntax Identifier { get; } public SyntaxToken EqualsToken { get; } public ExpressionSyntax Expression { get; } diff --git a/Upsilon/Parser/StatementSyntax/TableAssigmentStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/TableAssigmentStatementSyntax.cs new file mode 100644 index 0000000..c93627d --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/TableAssigmentStatementSyntax.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class TableAssigmentStatementSyntax : StatementSyntax + { + public ExpressionSyntax TableExpression { get; } + public SyntaxToken AssignmentToken { get; } + public ExpressionSyntax Expression { get; } + + public TableAssigmentStatementSyntax(ExpressionSyntax tableExpression, SyntaxToken assignmentToken, + ExpressionSyntax expression) + { + TableExpression = tableExpression; + AssignmentToken = assignmentToken; + Expression = expression; + } + + public override SyntaxKind Kind => SyntaxKind.TableAssignmentStatement; + + public override IEnumerable ChildNodes() + { + yield return TableExpression; + 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 0dc78c7..2ed9f88 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -1,3 +1,4 @@ + namespace Upsilon.Parser { public enum SyntaxKind @@ -67,6 +68,7 @@ namespace Upsilon.Parser ElseStatement, FunctionExpression, ReturnStatement, - FunctionAssignmentStatement + FunctionAssignmentStatement, + TableAssignmentStatement } } \ No newline at end of file diff --git a/UpsilonTests/TableTests.cs b/UpsilonTests/TableTests.cs index 3b873ca..fc8d149 100644 --- a/UpsilonTests/TableTests.cs +++ b/UpsilonTests/TableTests.cs @@ -153,6 +153,21 @@ table = { end } return table[1]() +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var evaluated = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(400, evaluated); + } + + [Fact] + public void AssignToTable() + { + const string input = @" +table = {} +table[1] = 400 +return table[1] "; var script = new Script(input); Assert.Empty(script.Diagnostics.Messages);