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);
|
return new LuaString(a.Value + b.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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:
|
||||||
|
@ -217,7 +219,15 @@ 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))
|
||||||
{
|
{
|
||||||
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)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
VariableExpression,
|
||||||
BoundFunctionCallExpression,
|
BoundFunctionCallExpression,
|
||||||
BoundTableExpression,
|
BoundTableExpression,
|
||||||
|
BoundIndexExpression,
|
||||||
|
|
||||||
// Statements
|
// Statements
|
||||||
BoundAssignmentStatement,
|
BoundAssignmentStatement,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.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,7 +117,8 @@ namespace Upsilon.Parser
|
||||||
}
|
}
|
||||||
hasDecimalPoint = true;
|
hasDecimalPoint = true;
|
||||||
}
|
}
|
||||||
numStr.Append(Next);
|
if (Next != '_')
|
||||||
|
numStr.Append(Next);
|
||||||
_position++;
|
_position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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