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); 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 namespace Upsilon.BaseTypes
{ {
public class LuaTable : LuaType public class LuaTable : LuaType, IIndexable
{ {
private Dictionary<string, VariableSymbol> _variableLookup; private readonly Dictionary<string, VariableSymbol> _variableLookup;
private Dictionary<VariableSymbol, LuaType> _map; private readonly Dictionary<VariableSymbol, LuaType> _map;
public int EvaluatorIdentifier { get; }
public LuaTable() public LuaTable(int scopeIdentifier)
{ {
EvaluatorIdentifier = scopeIdentifier;
_map = new Dictionary<VariableSymbol, LuaType>(); _map = new Dictionary<VariableSymbol, LuaType>();
_variableLookup = new Dictionary<string, VariableSymbol>(); _variableLookup = new Dictionary<string, VariableSymbol>();
} }
public LuaTable(Dictionary<VariableSymbol, LuaType> map) public LuaTable(Dictionary<VariableSymbol, LuaType> map, int scopeIdentifier)
{ {
_map = map; _map = map;
EvaluatorIdentifier = scopeIdentifier;
_variableLookup = map.ToDictionary(x => x.Key.Name, x => x.Key); _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); return BindFunctionCallExpression((FunctionCallExpressionSyntax) e);
case SyntaxKind.TableExpression: case SyntaxKind.TableExpression:
return BindTableExpression((TableExpressionSyntax) e); return BindTableExpression((TableExpressionSyntax) e);
case SyntaxKind.IndexExpression:
return BindIndexExpression((IndexExpressionSyntax) e);
case SyntaxKind.BadExpression: case SyntaxKind.BadExpression:
break; break;
case SyntaxKind.ScriptUnit: case SyntaxKind.ScriptUnit:
@ -216,8 +218,16 @@ namespace Upsilon.Binder
var isLocal = e.LocalToken != null; var isLocal = e.LocalToken != null;
if (!Scope.TryGetVariable(name, !isLocal, out var variable)) if (!Scope.TryGetVariable(name, !isLocal, out var variable))
{
if (boundExpression.Type == Type.Table)
{
var tableExpression = (BoundTableExpression) boundExpression;
variable = new TableVariableSymbol(name, tableExpression.ValueType, isLocal);
}
else
{ {
variable = new VariableSymbol(name, boundExpression.Type, isLocal); variable = new VariableSymbol(name, boundExpression.Type, isLocal);
}
if (isLocal) if (isLocal)
Scope.SetVariable(variable); Scope.SetVariable(variable);
else else
@ -375,5 +385,26 @@ namespace Upsilon.Binder
return new BoundTableExpression(keyType, valueType, dictionary, statements); 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, VariableExpression,
BoundFunctionCallExpression, BoundFunctionCallExpression,
BoundTableExpression, BoundTableExpression,
BoundIndexExpression,
// Statements // Statements
BoundAssignmentStatement, BoundAssignmentStatement,

View File

@ -28,4 +28,15 @@ namespace Upsilon.Binder
Parameters = parameters; 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; private readonly EvaluationScope _parentScope;
internal readonly Dictionary<VariableSymbol, LuaType> Variables; internal readonly Dictionary<VariableSymbol, LuaType> Variables;
internal EvaluationScope(EvaluationScope parentScope) internal EvaluationScope(EvaluationScope parentScope)
{ {
_parentScope = parentScope; _parentScope = parentScope;

View File

@ -15,17 +15,26 @@ namespace Upsilon.Evaluator
private LuaType _returnValue; private LuaType _returnValue;
internal EvaluationScope Scope { get; } internal EvaluationScope Scope { get; }
private bool HasReturned { get; set; } private bool HasReturned { get; set; }
public int Identifier { get; }
internal Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables) internal Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> variables)
{ {
_diagnostics = diagnostics; _diagnostics = diagnostics;
Scope = new EvaluationScope(variables); Scope = new EvaluationScope(variables);
Identifier = new Random().Next();
} }
private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope) private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope)
{ {
_diagnostics = diagnostics; _diagnostics = diagnostics;
Scope = new EvaluationScope(parentScope); 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) public LuaType Evaluate(BoundScript e)
@ -138,6 +147,8 @@ namespace Upsilon.Evaluator
return EvaluateBoundFunctionCallExpression((BoundFunctionCallExpression) e); return EvaluateBoundFunctionCallExpression((BoundFunctionCallExpression) e);
case BoundKind.BoundTableExpression: case BoundKind.BoundTableExpression:
return EvaluateTableExpression((BoundTableExpression) e); return EvaluateTableExpression((BoundTableExpression) e);
case BoundKind.BoundIndexExpression:
return EvaluateIndexExpression((BoundIndexExpression) e);
default: default:
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -317,7 +328,25 @@ namespace Upsilon.Evaluator
} }
currentPos++; 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.Collections.Immutable;
using System.Text; using System.Text;
using Upsilon.Text; using Upsilon.Text;
@ -69,6 +70,10 @@ namespace Upsilon.Parser
return new SyntaxToken(SyntaxKind.OpenBrace, _position, "{", null); return new SyntaxToken(SyntaxKind.OpenBrace, _position, "{", null);
case '}': case '}':
return new SyntaxToken(SyntaxKind.CloseBrace, _position, "}", null); return new SyntaxToken(SyntaxKind.CloseBrace, _position, "}", null);
case '[':
return new SyntaxToken(SyntaxKind.OpenBracket, _position, "[", null);
case ']':
return new SyntaxToken(SyntaxKind.CloseBracket, _position, "]", null);
case ',': case ',':
return new SyntaxToken(SyntaxKind.Comma, _position, ",", null); return new SyntaxToken(SyntaxKind.Comma, _position, ",", null);
case '"': case '"':
@ -112,6 +117,7 @@ namespace Upsilon.Parser
} }
hasDecimalPoint = true; hasDecimalPoint = true;
} }
if (Next != '_')
numStr.Append(Next); numStr.Append(Next);
_position++; _position++;
} }

View File

@ -228,6 +228,8 @@ namespace Upsilon.Parser
case SyntaxKind.Identifier: case SyntaxKind.Identifier:
if (Next.Kind == SyntaxKind.OpenParenthesis) if (Next.Kind == SyntaxKind.OpenParenthesis)
return ParseFunctionCallExpression(); return ParseFunctionCallExpression();
if (Next.Kind == SyntaxKind.OpenBracket)
return ParseIndexExpression();
var token = MatchToken(SyntaxKind.Identifier); var token = MatchToken(SyntaxKind.Identifier);
return new VariableExpressionSyntax((IdentifierToken) token); return new VariableExpressionSyntax((IdentifierToken) token);
case SyntaxKind.OpenBrace: case SyntaxKind.OpenBrace:
@ -260,6 +262,15 @@ namespace Upsilon.Parser
parameters.ToImmutable(), closeParenthesis); 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() private ExpressionSyntax ParseParenthesizedExpression()
{ {
var l = MatchToken(SyntaxKind.OpenParenthesis); var l = MatchToken(SyntaxKind.OpenParenthesis);

View File

@ -23,6 +23,8 @@ namespace Upsilon.Parser
String, String,
OpenBrace, OpenBrace,
CloseBrace, CloseBrace,
OpenBracket,
CloseBracket,
// key words // key words
TrueKeyword, TrueKeyword,
@ -52,6 +54,7 @@ namespace Upsilon.Parser
FunctionCallExpression, FunctionCallExpression,
BadExpression, BadExpression,
TableExpression, TableExpression,
IndexExpression,
// script unit // script unit
ScriptUnit, 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);
}
}
}