From 3d4e6380ea847e545877d25ba5175c50377809ec Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 18 Nov 2018 14:18:24 +0100 Subject: [PATCH] Functionality for indexing tables --- Upsilon/BaseTypes/IIndexable.cs | 8 ++ Upsilon/BaseTypes/LuaString.cs | 5 ++ Upsilon/BaseTypes/LuaTable.cs | 22 +++-- Upsilon/Binder/Binder.cs | 33 +++++++- .../BoundExpressions/BoundIndexExpression.cs | 21 +++++ Upsilon/Binder/BoundKind.cs | 1 + Upsilon/Binder/VariableSymbol.cs | 11 +++ Upsilon/Evaluator/EvaluationScope.cs | 1 + Upsilon/Evaluator/Evaluator.cs | 31 ++++++- .../ExpressionSyntax/IndexExpressionSyntax.cs | 30 +++++++ Upsilon/Parser/Lexer.cs | 8 +- Upsilon/Parser/Parser.cs | 11 +++ Upsilon/Parser/SyntaxKind.cs | 3 + UpsilonTests/TableTests.cs | 81 +++++++++++++++++++ 14 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 Upsilon/BaseTypes/IIndexable.cs create mode 100644 Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs create mode 100644 Upsilon/Parser/ExpressionSyntax/IndexExpressionSyntax.cs create mode 100644 UpsilonTests/TableTests.cs diff --git a/Upsilon/BaseTypes/IIndexable.cs b/Upsilon/BaseTypes/IIndexable.cs new file mode 100644 index 0000000..644f9b8 --- /dev/null +++ b/Upsilon/BaseTypes/IIndexable.cs @@ -0,0 +1,8 @@ +namespace Upsilon.BaseTypes +{ + public interface IIndexable + { + LuaType Get(string s, int scopeIdentifier); + int EvaluatorIdentifier { get; } + } +} \ No newline at end of file diff --git a/Upsilon/BaseTypes/LuaString.cs b/Upsilon/BaseTypes/LuaString.cs index 64e07a7..32da32d 100644 --- a/Upsilon/BaseTypes/LuaString.cs +++ b/Upsilon/BaseTypes/LuaString.cs @@ -71,5 +71,10 @@ namespace Upsilon.BaseTypes { return new LuaString(a.Value + b.Value); } + + public override string ToString() + { + return Value; + } } } \ No newline at end of file diff --git a/Upsilon/BaseTypes/LuaTable.cs b/Upsilon/BaseTypes/LuaTable.cs index cdb6afb..dbb340e 100644 --- a/Upsilon/BaseTypes/LuaTable.cs +++ b/Upsilon/BaseTypes/LuaTable.cs @@ -4,20 +4,23 @@ using Upsilon.Binder; namespace Upsilon.BaseTypes { - public class LuaTable : LuaType + public class LuaTable : LuaType, IIndexable { - private Dictionary _variableLookup; - private Dictionary _map; + private readonly Dictionary _variableLookup; + private readonly Dictionary _map; + public int EvaluatorIdentifier { get; } - public LuaTable() + public LuaTable(int scopeIdentifier) { + EvaluatorIdentifier = scopeIdentifier; _map = new Dictionary(); _variableLookup = new Dictionary(); } - public LuaTable(Dictionary map) + public LuaTable(Dictionary map, int scopeIdentifier) { _map = map; + EvaluatorIdentifier = scopeIdentifier; _variableLookup = map.ToDictionary(x => x.Key.Name, x => x.Key); } @@ -29,5 +32,14 @@ namespace Upsilon.BaseTypes } + public LuaType Get(string s, int scopeIdentifier) + { + if (!_variableLookup.TryGetValue(s, out var variableSymbol)) + return new LuaNull(); + if (EvaluatorIdentifier != scopeIdentifier && variableSymbol.Local) + return new LuaNull(); + return _map[variableSymbol]; + } + } } \ No newline at end of file diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 2d4103e..65597b6 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -80,6 +80,8 @@ namespace Upsilon.Binder return BindFunctionCallExpression((FunctionCallExpressionSyntax) e); case SyntaxKind.TableExpression: return BindTableExpression((TableExpressionSyntax) e); + case SyntaxKind.IndexExpression: + return BindIndexExpression((IndexExpressionSyntax) e); case SyntaxKind.BadExpression: break; case SyntaxKind.ScriptUnit: @@ -217,7 +219,15 @@ namespace Upsilon.Binder var isLocal = e.LocalToken != null; if (!Scope.TryGetVariable(name, !isLocal, out var variable)) { - variable = new VariableSymbol(name, boundExpression.Type, isLocal); + 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 @@ -375,5 +385,26 @@ namespace Upsilon.Binder return new BoundTableExpression(keyType, valueType, dictionary, statements); } + private BoundExpression BindIndexExpression(IndexExpressionSyntax e) + { + var name = e.Identifier.Name; + if (!Scope.TryGetVariable(name, true, out var variable)) + { + _diagnostics.LogUnknownVariable(e.Identifier.Span, name); + return new BoundLiteralExpression(new LuaNull()); + } + + if (variable.Type != Type.Table) + { + //TODO better error message + _diagnostics.LogUnknownVariable(e.Identifier.Span, name); + return new BoundLiteralExpression(new LuaNull()); + } + + var tableVariable = (TableVariableSymbol) variable; + var outType = tableVariable.OutType; + var index = BindExpression(e.Index); + return new BoundIndexExpression(tableVariable, index, outType); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs b/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs new file mode 100644 index 0000000..3655265 --- /dev/null +++ b/Upsilon/Binder/BoundExpressions/BoundIndexExpression.cs @@ -0,0 +1,21 @@ +using Upsilon.BaseTypes; +using Upsilon.Parser; + +namespace Upsilon.Binder +{ + public class BoundIndexExpression : BoundExpression + { + public BoundIndexExpression(TableVariableSymbol identifier, BoundExpression index, Type type) + { + Identifier = identifier; + Index = index; + Type = type; + } + + public TableVariableSymbol Identifier { get; } + public BoundExpression Index { get; } + + public override BoundKind Kind => BoundKind.BoundIndexExpression; + public override Type Type { get; } + } +} \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index cc803c4..0ae0d1c 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -10,6 +10,7 @@ namespace Upsilon.Binder VariableExpression, BoundFunctionCallExpression, BoundTableExpression, + BoundIndexExpression, // Statements BoundAssignmentStatement, diff --git a/Upsilon/Binder/VariableSymbol.cs b/Upsilon/Binder/VariableSymbol.cs index 947c4a3..eaa545d 100644 --- a/Upsilon/Binder/VariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbol.cs @@ -28,4 +28,15 @@ namespace Upsilon.Binder Parameters = parameters; } } + + public class TableVariableSymbol : VariableSymbol + { + public Type OutType { get; } + + public TableVariableSymbol(string name, Type outType, bool local) + :base (name, Type.Table, local) + { + OutType = outType; + } + } } \ No newline at end of file diff --git a/Upsilon/Evaluator/EvaluationScope.cs b/Upsilon/Evaluator/EvaluationScope.cs index 100f6fe..a10a2d2 100644 --- a/Upsilon/Evaluator/EvaluationScope.cs +++ b/Upsilon/Evaluator/EvaluationScope.cs @@ -9,6 +9,7 @@ namespace Upsilon.Evaluator private readonly EvaluationScope _parentScope; internal readonly Dictionary Variables; + internal EvaluationScope(EvaluationScope parentScope) { _parentScope = parentScope; diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 656d82e..5411bc1 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -15,17 +15,26 @@ 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) @@ -138,6 +147,8 @@ namespace Upsilon.Evaluator return EvaluateBoundFunctionCallExpression((BoundFunctionCallExpression) e); case BoundKind.BoundTableExpression: return EvaluateTableExpression((BoundTableExpression) e); + case BoundKind.BoundIndexExpression: + return EvaluateIndexExpression((BoundIndexExpression) e); default: throw new NotImplementedException(); } @@ -317,7 +328,25 @@ namespace Upsilon.Evaluator } currentPos++; } - return new LuaTable(dic); + return new LuaTable(dic, innerEvaluator.Identifier); + } + + private LuaType EvaluateIndexExpression(BoundIndexExpression e) + { + if (!Scope.TryGet(e.Identifier, out var val)) + { + throw new Exception($"Cannot find variable: '{e.Identifier.Name}'"); + } + + if (!(val is IIndexable indexable)) + { + throw new Exception("Variable is not indexable."); + } + + var innerEvaluator = new Evaluator(_diagnostics, Scope, indexable.EvaluatorIdentifier); + var indexer = EvaluateExpression(e.Index); + + return indexable.Get(indexer.ToString(), Identifier); } } } \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/IndexExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/IndexExpressionSyntax.cs new file mode 100644 index 0000000..2f8dab2 --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/IndexExpressionSyntax.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class IndexExpressionSyntax : ExpressionSyntax + { + public IdentifierToken Identifier { get; } + public SyntaxToken OpenBracket { get; } + public ExpressionSyntax Index { get; } + public SyntaxToken CloseBracket { get; } + + public IndexExpressionSyntax(IdentifierToken identifier, SyntaxToken openBracket, ExpressionSyntax index, + SyntaxToken closeBracket) + { + Identifier = identifier; + OpenBracket = openBracket; + Index = index; + CloseBracket = closeBracket; + } + + public override SyntaxKind Kind => SyntaxKind.IndexExpression; + public override IEnumerable ChildNodes() + { + yield return Identifier; + yield return OpenBracket; + yield return Index; + yield return CloseBracket; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs index 213a299..7498e9d 100644 --- a/Upsilon/Parser/Lexer.cs +++ b/Upsilon/Parser/Lexer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Immutable; using System.Text; using Upsilon.Text; @@ -69,6 +70,10 @@ namespace Upsilon.Parser return new SyntaxToken(SyntaxKind.OpenBrace, _position, "{", null); case '}': return new SyntaxToken(SyntaxKind.CloseBrace, _position, "}", null); + case '[': + return new SyntaxToken(SyntaxKind.OpenBracket, _position, "[", null); + case ']': + return new SyntaxToken(SyntaxKind.CloseBracket, _position, "]", null); case ',': return new SyntaxToken(SyntaxKind.Comma, _position, ",", null); case '"': @@ -112,7 +117,8 @@ namespace Upsilon.Parser } hasDecimalPoint = true; } - numStr.Append(Next); + if (Next != '_') + numStr.Append(Next); _position++; } diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 3759794..de8f21e 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -228,6 +228,8 @@ namespace Upsilon.Parser case SyntaxKind.Identifier: if (Next.Kind == SyntaxKind.OpenParenthesis) return ParseFunctionCallExpression(); + if (Next.Kind == SyntaxKind.OpenBracket) + return ParseIndexExpression(); var token = MatchToken(SyntaxKind.Identifier); return new VariableExpressionSyntax((IdentifierToken) token); case SyntaxKind.OpenBrace: @@ -260,6 +262,15 @@ namespace Upsilon.Parser parameters.ToImmutable(), closeParenthesis); } + private ExpressionSyntax ParseIndexExpression() + { + var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); + var openBracket = MatchToken(SyntaxKind.OpenBracket); + var index = ParseExpression(); + var closeBracket = MatchToken(SyntaxKind.CloseBracket); + return new IndexExpressionSyntax(identifier, openBracket, index, closeBracket); + } + private ExpressionSyntax ParseParenthesizedExpression() { var l = MatchToken(SyntaxKind.OpenParenthesis); diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index a83891e..48ed33d 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -23,6 +23,8 @@ namespace Upsilon.Parser String, OpenBrace, CloseBrace, + OpenBracket, + CloseBracket, // key words TrueKeyword, @@ -52,6 +54,7 @@ namespace Upsilon.Parser FunctionCallExpression, BadExpression, TableExpression, + IndexExpression, // script unit ScriptUnit, diff --git a/UpsilonTests/TableTests.cs b/UpsilonTests/TableTests.cs new file mode 100644 index 0000000..6c76c1d --- /dev/null +++ b/UpsilonTests/TableTests.cs @@ -0,0 +1,81 @@ +using Upsilon.Evaluator; +using Xunit; + +namespace UpsilonTests +{ + public class TableTests + { + [Fact] + public void BasicNumberTable() + { + const string input = @" +table = { + 100, 200, 300 +} +return table[2] +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var evaluated = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(200, evaluated); + } + + [Fact] + public void BasicStringTable() + { + const string input = @" +table = { + test = 100, + val = 400, + another = 10_000, +} +return table[""another""] +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var evaluated = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(10_000, evaluated); + } + + [Fact] + public void HidesLocalKeys() + { + const string input = @" +table = { + local test = 100, + val = 400, + another = 10_000, +} +return table[""test""] +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var evaluated = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Null(evaluated); + } + + [Fact] + public void FunctionsInTable() + { + const string input = @" +table = { + function() test + return 100 + end + val = 400, + another = 10_000, +} +return table[""test""]() +"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var evaluated = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(100, evaluated); + } + + } +} \ No newline at end of file