From 7e1edbe3f15c4196febe004d9114afb6b6c95704 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Wed, 14 Nov 2018 16:39:52 +0100 Subject: [PATCH] Properly handle scopes --- Upsilon/Binder/Binder.cs | 17 ++++----- Upsilon/Binder/BoundScope.cs | 6 +-- Upsilon/Binder/VariableSymbol.cs | 4 +- Upsilon/Evaluator/EvaluationScope.cs | 55 ++++++++++++++++++++++++++++ Upsilon/Evaluator/Evaluator.cs | 34 ++++++++++++----- Upsilon/Evaluator/Script.cs | 2 +- UpsilonTests/ScopeTests.cs | 44 ++++++++++++++++++++++ UpsilonTests/UpsilonTests.csproj | 2 +- 8 files changed, 138 insertions(+), 26 deletions(-) create mode 100644 Upsilon/Evaluator/EvaluationScope.cs create mode 100644 UpsilonTests/ScopeTests.cs diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 7ea651d..33e125f 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -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(); + 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()); diff --git a/Upsilon/Binder/BoundScope.cs b/Upsilon/Binder/BoundScope.cs index 835149c..9e2a54d 100644 --- a/Upsilon/Binder/BoundScope.cs +++ b/Upsilon/Binder/BoundScope.cs @@ -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; } diff --git a/Upsilon/Binder/VariableSymbol.cs b/Upsilon/Binder/VariableSymbol.cs index 900753a..907697c 100644 --- a/Upsilon/Binder/VariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbol.cs @@ -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; } } } \ No newline at end of file diff --git a/Upsilon/Evaluator/EvaluationScope.cs b/Upsilon/Evaluator/EvaluationScope.cs new file mode 100644 index 0000000..7cf4341 --- /dev/null +++ b/Upsilon/Evaluator/EvaluationScope.cs @@ -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 _variables; + + internal EvaluationScope(EvaluationScope parentScope) + { + _parentScope = parentScope; + _variables = new Dictionary(); + } + internal EvaluationScope(Dictionary 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; + } + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 8b44294..c9d52ba 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -10,19 +10,18 @@ namespace Upsilon.Evaluator { private readonly Diagnostics _diagnostics; private LuaType _value; - private Script Script { get; } - private readonly Dictionary _variables = new Dictionary(); + private readonly EvaluationScope _scope; - public Evaluator(Script script, Diagnostics diagnostics) + public Evaluator(Diagnostics diagnostics, Dictionary vars) { _diagnostics = diagnostics; - Script = script; + _scope = new EvaluationScope(vars); } - public Evaluator(Script script, Diagnostics diagnostics, Dictionary 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) diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs index c8b7abe..9aa730e 100644 --- a/Upsilon/Evaluator/Script.cs +++ b/Upsilon/Evaluator/Script.cs @@ -22,7 +22,7 @@ namespace Upsilon.Evaluator if (variables == null) variables = new Dictionary(); Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics); - Evaluator = new Evaluator(this, Diagnostics, variables); + Evaluator = new Evaluator( Diagnostics, variables); } public BoundScript Bind() diff --git a/UpsilonTests/ScopeTests.cs b/UpsilonTests/ScopeTests.cs new file mode 100644 index 0000000..963d213 --- /dev/null +++ b/UpsilonTests/ScopeTests.cs @@ -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(); + 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(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal((long)100, evaluate); + } + + } +} \ No newline at end of file diff --git a/UpsilonTests/UpsilonTests.csproj b/UpsilonTests/UpsilonTests.csproj index b78f421..4ca660a 100644 --- a/UpsilonTests/UpsilonTests.csproj +++ b/UpsilonTests/UpsilonTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1