Properly handle scopes

This commit is contained in:
Deukhoofd 2018-11-14 16:39:52 +01:00
parent 82e13a85e2
commit 7e1edbe3f1
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
8 changed files with 138 additions and 26 deletions

View File

@ -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());

View File

@ -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;
}

View File

@ -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; }
}
}

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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()

View File

@ -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);
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>