Functionality for indexing tables
This commit is contained in:
parent
5a52c235c5
commit
3d4e6380ea
|
@ -0,0 +1,8 @@
|
|||
namespace Upsilon.BaseTypes
|
||||
{
|
||||
public interface IIndexable
|
||||
{
|
||||
LuaType Get(string s, int scopeIdentifier);
|
||||
int EvaluatorIdentifier { get; }
|
||||
}
|
||||
}
|
|
@ -71,5 +71,10 @@ namespace Upsilon.BaseTypes
|
|||
{
|
||||
return new LuaString(a.Value + b.Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace Upsilon.Binder
|
|||
VariableExpression,
|
||||
BoundFunctionCallExpression,
|
||||
BoundTableExpression,
|
||||
BoundIndexExpression,
|
||||
|
||||
// Statements
|
||||
BoundAssignmentStatement,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ namespace Upsilon.Evaluator
|
|||
private readonly EvaluationScope _parentScope;
|
||||
internal readonly Dictionary<VariableSymbol, LuaType> Variables;
|
||||
|
||||
|
||||
internal EvaluationScope(EvaluationScope parentScope)
|
||||
{
|
||||
_parentScope = parentScope;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue