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

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

View File

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

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

View File

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

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> <PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework> <TargetFramework>netcoreapp2.1</TargetFramework>