Properly handle scopes
This commit is contained in:
parent
82e13a85e2
commit
7e1edbe3f1
|
@ -94,24 +94,19 @@ namespace Upsilon.Binder
|
|||
private BoundExpression BindLiteralExpression(LiteralExpressionSyntax e)
|
||||
{
|
||||
var value = e.Value;
|
||||
var type = Type.Nil;
|
||||
LuaType outValue = null;
|
||||
switch (value)
|
||||
{
|
||||
case double d:
|
||||
type = Type.Number;
|
||||
outValue = new NumberDouble(d);
|
||||
break;
|
||||
case long l:
|
||||
type = Type.Number;
|
||||
outValue = new NumberLong(l);
|
||||
break;
|
||||
case bool b:
|
||||
type = Type.Boolean;
|
||||
outValue = new LuaBoolean(b);
|
||||
break;
|
||||
case null:
|
||||
type = Type.Nil;
|
||||
outValue = new LuaNull();
|
||||
break;
|
||||
default:
|
||||
|
@ -129,7 +124,7 @@ namespace Upsilon.Binder
|
|||
private BoundExpression BindVariableExpression(VariableExpressionSyntax e)
|
||||
{
|
||||
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);
|
||||
return new BoundLiteralExpression(new LuaNull());
|
||||
|
@ -149,10 +144,11 @@ namespace Upsilon.Binder
|
|||
var name = e.Identifier.Name;
|
||||
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);
|
||||
if (e.LocalToken != null)
|
||||
variable = new VariableSymbol(name, boundExpression.Type, isLocal);
|
||||
if (isLocal)
|
||||
_scope.SetVariable(variable);
|
||||
else
|
||||
_scope.SetGlobalVariable(variable);
|
||||
|
@ -181,9 +177,10 @@ namespace Upsilon.Binder
|
|||
private BoundStatement BindBlockStatement(BlockStatementSyntax e)
|
||||
{
|
||||
var arr = ImmutableArray.CreateBuilder<BoundStatement>();
|
||||
var innerBinder = new Binder(_scope, _diagnostics);
|
||||
foreach (var statementSyntax in e.Statements)
|
||||
{
|
||||
var bound = BindStatement(statementSyntax);
|
||||
var bound = innerBinder.BindStatement(statementSyntax);
|
||||
arr.Add(bound);
|
||||
}
|
||||
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))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (_parentScope != null)
|
||||
if (_parentScope != null && allowUpperScopes)
|
||||
{
|
||||
return _parentScope.TryGetVariable(key, out result);
|
||||
return _parentScope.TryGetVariable(key, true, out result);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ namespace Upsilon.Binder
|
|||
{
|
||||
public class VariableSymbol
|
||||
{
|
||||
public VariableSymbol(string name, Type type)
|
||||
public VariableSymbol(string name, Type type, bool local)
|
||||
{
|
||||
Type = type;
|
||||
Local = local;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public Type Type { get; set; }
|
||||
public bool Local { 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 LuaType _value;
|
||||
private Script Script { get; }
|
||||
private readonly Dictionary<VariableSymbol, LuaType> _variables = new Dictionary<VariableSymbol, LuaType>();
|
||||
private readonly EvaluationScope _scope;
|
||||
|
||||
public Evaluator(Script script, Diagnostics diagnostics)
|
||||
public Evaluator(Diagnostics diagnostics, Dictionary<VariableSymbol, LuaType> vars)
|
||||
{
|
||||
_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;
|
||||
Script = script;
|
||||
_variables = vars;
|
||||
_scope = new EvaluationScope(parentScope);
|
||||
}
|
||||
|
||||
public LuaType Evaluate(BoundScript e)
|
||||
|
@ -115,21 +114,36 @@ namespace Upsilon.Evaluator
|
|||
private void EvaluateAssignmentStatement(BoundVariableAssignment e)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var innerEvaluator = new Evaluator(_diagnostics, _scope);
|
||||
foreach (var boundStatement in boundBlockStatement.Statements)
|
||||
{
|
||||
EvaluateStatement(boundStatement);
|
||||
innerEvaluator.EvaluateStatement(boundStatement);
|
||||
}
|
||||
|
||||
if (innerEvaluator._value != null)
|
||||
_value = innerEvaluator._value;
|
||||
}
|
||||
|
||||
private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement)
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Upsilon.Evaluator
|
|||
if (variables == null)
|
||||
variables = new Dictionary<VariableSymbol, LuaType>();
|
||||
Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics);
|
||||
Evaluator = new Evaluator(this, Diagnostics, variables);
|
||||
Evaluator = new Evaluator( Diagnostics, variables);
|
||||
}
|
||||
|
||||
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>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
|
|
Loading…
Reference in New Issue