Properly handle scopes
This commit is contained in:
parent
82e13a85e2
commit
7e1edbe3f1
|
@ -94,24 +94,19 @@ namespace Upsilon.Binder
|
||||||
private BoundExpression BindLiteralExpression(LiteralExpressionSyntax e)
|
private BoundExpression BindLiteralExpression(LiteralExpressionSyntax e)
|
||||||
{
|
{
|
||||||
var value = e.Value;
|
var value = e.Value;
|
||||||
var type = Type.Nil;
|
|
||||||
LuaType outValue = null;
|
LuaType outValue = null;
|
||||||
switch (value)
|
switch (value)
|
||||||
{
|
{
|
||||||
case double d:
|
case double d:
|
||||||
type = Type.Number;
|
|
||||||
outValue = new NumberDouble(d);
|
outValue = new NumberDouble(d);
|
||||||
break;
|
break;
|
||||||
case long l:
|
case long l:
|
||||||
type = Type.Number;
|
|
||||||
outValue = new NumberLong(l);
|
outValue = new NumberLong(l);
|
||||||
break;
|
break;
|
||||||
case bool b:
|
case bool b:
|
||||||
type = Type.Boolean;
|
|
||||||
outValue = new LuaBoolean(b);
|
outValue = new LuaBoolean(b);
|
||||||
break;
|
break;
|
||||||
case null:
|
case null:
|
||||||
type = Type.Nil;
|
|
||||||
outValue = new LuaNull();
|
outValue = new LuaNull();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -129,7 +124,7 @@ namespace Upsilon.Binder
|
||||||
private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
|
private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
|
||||||
{
|
{
|
||||||
var name = e.Identifier.Name;
|
var name = e.Identifier.Name;
|
||||||
if (!_scope.TryGetVariable(name, out var variable))
|
if (!_scope.TryGetVariable(name, true, out var variable))
|
||||||
{
|
{
|
||||||
_diagnostics.LogUnknownVariable(e.Identifier.Span, name);
|
_diagnostics.LogUnknownVariable(e.Identifier.Span, name);
|
||||||
return new BoundLiteralExpression(new LuaNull());
|
return new BoundLiteralExpression(new LuaNull());
|
||||||
|
@ -149,10 +144,11 @@ namespace Upsilon.Binder
|
||||||
var name = e.Identifier.Name;
|
var name = e.Identifier.Name;
|
||||||
var boundExpression = BindExpression(e.Expression);
|
var boundExpression = BindExpression(e.Expression);
|
||||||
|
|
||||||
if (!_scope.TryGetVariable(name, out var variable))
|
var isLocal = e.LocalToken != null;
|
||||||
|
if (!_scope.TryGetVariable(name, !isLocal, out var variable))
|
||||||
{
|
{
|
||||||
variable = new VariableSymbol(name, boundExpression.Type);
|
variable = new VariableSymbol(name, boundExpression.Type, isLocal);
|
||||||
if (e.LocalToken != null)
|
if (isLocal)
|
||||||
_scope.SetVariable(variable);
|
_scope.SetVariable(variable);
|
||||||
else
|
else
|
||||||
_scope.SetGlobalVariable(variable);
|
_scope.SetGlobalVariable(variable);
|
||||||
|
@ -181,9 +177,10 @@ namespace Upsilon.Binder
|
||||||
private BoundStatement BindBlockStatement(BlockStatementSyntax e)
|
private BoundStatement BindBlockStatement(BlockStatementSyntax e)
|
||||||
{
|
{
|
||||||
var arr = ImmutableArray.CreateBuilder<BoundStatement>();
|
var arr = ImmutableArray.CreateBuilder<BoundStatement>();
|
||||||
|
var innerBinder = new Binder(_scope, _diagnostics);
|
||||||
foreach (var statementSyntax in e.Statements)
|
foreach (var statementSyntax in e.Statements)
|
||||||
{
|
{
|
||||||
var bound = BindStatement(statementSyntax);
|
var bound = innerBinder.BindStatement(statementSyntax);
|
||||||
arr.Add(bound);
|
arr.Add(bound);
|
||||||
}
|
}
|
||||||
return new BoundBlockStatement(arr.ToImmutable());
|
return new BoundBlockStatement(arr.ToImmutable());
|
||||||
|
|
|
@ -41,15 +41,15 @@ namespace Upsilon.Binder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool TryGetVariable(string key, out VariableSymbol result)
|
public bool TryGetVariable(string key, bool allowUpperScopes, out VariableSymbol result)
|
||||||
{
|
{
|
||||||
if (_variables.TryGetValue(key, out result))
|
if (_variables.TryGetValue(key, out result))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (_parentScope != null)
|
if (_parentScope != null && allowUpperScopes)
|
||||||
{
|
{
|
||||||
return _parentScope.TryGetVariable(key, out result);
|
return _parentScope.TryGetVariable(key, true, out result);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,15 @@ namespace Upsilon.Binder
|
||||||
{
|
{
|
||||||
public class VariableSymbol
|
public class VariableSymbol
|
||||||
{
|
{
|
||||||
public VariableSymbol(string name, Type type)
|
public VariableSymbol(string name, Type type, bool local)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
Local = local;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type Type { get; set; }
|
public Type Type { get; set; }
|
||||||
|
public bool Local { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Upsilon.BaseTypes;
|
||||||
|
using Upsilon.Binder;
|
||||||
|
|
||||||
|
namespace Upsilon.Evaluator
|
||||||
|
{
|
||||||
|
public class EvaluationScope
|
||||||
|
{
|
||||||
|
private readonly EvaluationScope _parentScope;
|
||||||
|
private readonly Dictionary<VariableSymbol, LuaType> _variables;
|
||||||
|
|
||||||
|
internal EvaluationScope(EvaluationScope parentScope)
|
||||||
|
{
|
||||||
|
_parentScope = parentScope;
|
||||||
|
_variables = new Dictionary<VariableSymbol, LuaType>();
|
||||||
|
}
|
||||||
|
internal EvaluationScope(Dictionary<VariableSymbol, LuaType> vars)
|
||||||
|
{
|
||||||
|
_variables = vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(VariableSymbol symbol, LuaType obj)
|
||||||
|
{
|
||||||
|
if (_variables.ContainsKey(symbol))
|
||||||
|
{
|
||||||
|
_variables[symbol] = obj;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_variables.Add(symbol, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetGlobal(VariableSymbol symbol, LuaType obj)
|
||||||
|
{
|
||||||
|
if (_parentScope != null)
|
||||||
|
_parentScope.SetGlobal(symbol, obj);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Set(symbol, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(VariableSymbol symbol, out LuaType obj)
|
||||||
|
{
|
||||||
|
if (_variables.TryGetValue(symbol, out obj))
|
||||||
|
return true;
|
||||||
|
if (_parentScope != null)
|
||||||
|
if (_parentScope.TryGet(symbol, out obj))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,19 +10,18 @@ namespace Upsilon.Evaluator
|
||||||
{
|
{
|
||||||
private readonly Diagnostics _diagnostics;
|
private readonly Diagnostics _diagnostics;
|
||||||
private LuaType _value;
|
private LuaType _value;
|
||||||
private Script Script { get; }
|
private readonly EvaluationScope _scope;
|
||||||
private readonly Dictionary<VariableSymbol, LuaType> _variables = new Dictionary<VariableSymbol, LuaType>();
|
|
||||||
|
|
||||||
public Evaluator(Script script, Diagnostics diagnostics)
|
public Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> vars)
|
||||||
{
|
{
|
||||||
_diagnostics = diagnostics;
|
_diagnostics = diagnostics;
|
||||||
Script = script;
|
_scope = new EvaluationScope(vars);
|
||||||
}
|
}
|
||||||
public Evaluator(Script script, Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> vars)
|
|
||||||
|
private Evaluator(Diagnostics diagnostics, EvaluationScope parentScope)
|
||||||
{
|
{
|
||||||
_diagnostics = diagnostics;
|
_diagnostics = diagnostics;
|
||||||
Script = script;
|
_scope = new EvaluationScope(parentScope);
|
||||||
_variables = vars;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaType Evaluate(BoundScript e)
|
public LuaType Evaluate(BoundScript e)
|
||||||
|
@ -115,21 +114,36 @@ namespace Upsilon.Evaluator
|
||||||
private void EvaluateAssignmentStatement(BoundVariableAssignment e)
|
private void EvaluateAssignmentStatement(BoundVariableAssignment e)
|
||||||
{
|
{
|
||||||
var val = EvaluateExpression(e.BoundExpression);
|
var val = EvaluateExpression(e.BoundExpression);
|
||||||
_variables[e.Variable] = val;
|
if (e.Variable.Local)
|
||||||
|
{
|
||||||
|
_scope.Set(e.Variable, val);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_scope.SetGlobal(e.Variable, val);
|
||||||
|
}
|
||||||
_value = val;
|
_value = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LuaType EvaluateVariableExpression(BoundVariableExpression e)
|
private LuaType EvaluateVariableExpression(BoundVariableExpression e)
|
||||||
{
|
{
|
||||||
return _variables[e.Variable];
|
if (_scope.TryGet(e.Variable, out var val))
|
||||||
|
{
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
throw new Exception($"Cannot find variable: '{e.Variable.Name}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EvaluateBoundBlockStatement(BoundBlockStatement boundBlockStatement)
|
private void EvaluateBoundBlockStatement(BoundBlockStatement boundBlockStatement)
|
||||||
{
|
{
|
||||||
|
var innerEvaluator = new Evaluator(_diagnostics, _scope);
|
||||||
foreach (var boundStatement in boundBlockStatement.Statements)
|
foreach (var boundStatement in boundBlockStatement.Statements)
|
||||||
{
|
{
|
||||||
EvaluateStatement(boundStatement);
|
innerEvaluator.EvaluateStatement(boundStatement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (innerEvaluator._value != null)
|
||||||
|
_value = innerEvaluator._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement)
|
private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement)
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Upsilon.Evaluator
|
||||||
if (variables == null)
|
if (variables == null)
|
||||||
variables = new Dictionary<VariableSymbol, LuaType>();
|
variables = new Dictionary<VariableSymbol, LuaType>();
|
||||||
Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics);
|
Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics);
|
||||||
Evaluator = new Evaluator(this, Diagnostics, variables);
|
Evaluator = new Evaluator( Diagnostics, variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BoundScript Bind()
|
public BoundScript Bind()
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
using Upsilon.BaseTypes.Number;
|
||||||
|
using Upsilon.Evaluator;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace UpsilonTests
|
||||||
|
{
|
||||||
|
public class ScopeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void LocalInnerScopeDoesNotOverrideGlobal()
|
||||||
|
{
|
||||||
|
const string input = @"
|
||||||
|
a = 10
|
||||||
|
if true then
|
||||||
|
local a = 100
|
||||||
|
end
|
||||||
|
b = a
|
||||||
|
";
|
||||||
|
var script = new Script(input);
|
||||||
|
Assert.Empty(script.Diagnostics.Messages);
|
||||||
|
var evaluate = script.Evaluate<NumberLong>();
|
||||||
|
Assert.Empty(script.Diagnostics.Messages);
|
||||||
|
Assert.Equal((long)10, evaluate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InnerScopeDoesOverrideGlobal()
|
||||||
|
{
|
||||||
|
const string input = @"
|
||||||
|
a = 10
|
||||||
|
if true then
|
||||||
|
a = 100
|
||||||
|
end
|
||||||
|
b = a
|
||||||
|
";
|
||||||
|
var script = new Script(input);
|
||||||
|
Assert.Empty(script.Diagnostics.Messages);
|
||||||
|
var evaluate = script.Evaluate<NumberLong>();
|
||||||
|
Assert.Empty(script.Diagnostics.Messages);
|
||||||
|
Assert.Equal((long)100, evaluate);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
|
|
Loading…
Reference in New Issue