Functionality for indexing tables

This commit is contained in:
Deukhoofd 2018-11-18 14:18:24 +01:00
parent 5a52c235c5
commit 3d4e6380ea
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
14 changed files with 258 additions and 8 deletions

View File

@ -0,0 +1,8 @@
namespace Upsilon.BaseTypes
{
public interface IIndexable
{
LuaType Get(string s, int scopeIdentifier);
int EvaluatorIdentifier { get; }
}
}

View File

@ -71,5 +71,10 @@ namespace Upsilon.BaseTypes
{
return new LuaString(a.Value + b.Value);
}
public override string ToString()
{
return Value;
}
}
}

View File

@ -4,20 +4,23 @@ using Upsilon.Binder;
namespace Upsilon.BaseTypes
{
public class LuaTable : LuaType
public class LuaTable : LuaType, IIndexable
{
private Dictionary<string, VariableSymbol> _variableLookup;
private Dictionary<VariableSymbol, LuaType> _map;
private readonly Dictionary<string, VariableSymbol> _variableLookup;
private readonly Dictionary<VariableSymbol, LuaType> _map;
public int EvaluatorIdentifier { get; }
public LuaTable()
public LuaTable(int scopeIdentifier)
{
EvaluatorIdentifier = scopeIdentifier;
_map = new Dictionary<VariableSymbol, LuaType>();
_variableLookup = new Dictionary<string, VariableSymbol>();
}
public LuaTable(Dictionary<VariableSymbol, LuaType> map)
public LuaTable(Dictionary<VariableSymbol, LuaType> 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];
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -10,6 +10,7 @@ namespace Upsilon.Binder
VariableExpression,
BoundFunctionCallExpression,
BoundTableExpression,
BoundIndexExpression,
// Statements
BoundAssignmentStatement,

View File

@ -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;
}
}
}

View File

@ -9,6 +9,7 @@ namespace Upsilon.Evaluator
private readonly EvaluationScope _parentScope;
internal readonly Dictionary<VariableSymbol, LuaType> Variables;
internal EvaluationScope(EvaluationScope parentScope)
{
_parentScope = parentScope;

View File

@ -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<VariableSymbol, LuaType> 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);
}
}
}

View File

@ -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<SyntaxNode> ChildNodes()
{
yield return Identifier;
yield return OpenBracket;
yield return Index;
yield return CloseBracket;
}
}
}

View File

@ -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++;
}

View File

@ -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);

View File

@ -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,

View File

@ -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<long>();
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<long>();
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<long>();
Assert.Empty(script.Diagnostics.Messages);
Assert.Equal(100, evaluated);
}
}
}