From d2c14d213c0b85ca3ae4761c083c122532b7925e Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Fri, 23 Nov 2018 14:38:45 +0100 Subject: [PATCH] Add Numeric For Loops --- Upsilon/BaseTypes/Number/NumberLong.cs | 2 +- Upsilon/Binder/Binder.cs | 18 ++++++ Upsilon/Binder/BoundKind.cs | 3 +- .../BoundNumericForStatement.cs | 23 +++++++ Upsilon/Evaluator/Evaluator.cs | 33 ++++++++++ Upsilon/Parser/Parser.cs | 40 ++++++++++++ .../NumericForStatementSyntax.cs | 53 ++++++++++++++++ Upsilon/Parser/SyntaxKeyWords.cs | 6 ++ Upsilon/Parser/SyntaxKind.cs | 4 ++ .../GeneralTests/BasicMathExpressions.cs | 4 +- UpsilonTests/GeneralTests/ForLoopTests.cs | 63 +++++++++++++++++++ UpsilonTests/GeneralTests/FunctionTests.cs | 4 +- UpsilonTests/GeneralTests/IfTests.cs | 4 +- UpsilonTests/GeneralTests/MathPrecedence.cs | 4 +- UpsilonTests/GeneralTests/ScopeTests.cs | 3 +- UpsilonTests/GeneralTests/StringTests.cs | 2 +- UpsilonTests/GeneralTests/TableTests.cs | 2 +- .../GeneralTests/UserDataDictionaryTests.cs | 2 +- .../GeneralTests/UserDataListTests.cs | 2 +- .../GeneralTests/UserDataOperatorTests.cs | 3 +- UpsilonTests/GeneralTests/UserDataTests.cs | 3 +- 21 files changed, 256 insertions(+), 22 deletions(-) create mode 100644 Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs create mode 100644 Upsilon/Parser/StatementSyntax/NumericForStatementSyntax.cs create mode 100644 UpsilonTests/GeneralTests/ForLoopTests.cs diff --git a/Upsilon/BaseTypes/Number/NumberLong.cs b/Upsilon/BaseTypes/Number/NumberLong.cs index 1da827a..8ec4118 100644 --- a/Upsilon/BaseTypes/Number/NumberLong.cs +++ b/Upsilon/BaseTypes/Number/NumberLong.cs @@ -5,7 +5,7 @@ namespace Upsilon.BaseTypes.Number { internal class NumberLong : Number { - public long Value { get; } + public long Value { get; set; } protected override bool IsFloat { get; } = false; public NumberLong(long val) diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index cbe5e9c..8d03728 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -68,6 +68,8 @@ namespace Upsilon.Binder return BindTableAssignmentStatement((TableAssigmentStatementSyntax) s); case SyntaxKind.MultiAssignmentStatement: return BindMultiAssignmentStatement((MultiAssignmentStatementSyntax) s); + case SyntaxKind.NumericForStatement: + return BindNumericForStatement((NumericForStatementSyntax) s); } throw new NotImplementedException(s.Kind.ToString()); @@ -515,5 +517,21 @@ namespace Upsilon.Binder var value = BindExpression(e.Expression); return new BoundTableAssigmentStatement(indexableExpression, value); } + + private BoundStatement BindNumericForStatement(NumericForStatementSyntax e) + { + var variableName = e.Identifier.Name; + Scope = new BoundScope(Scope); + var variable = new VariableSymbol(variableName, Type.Number, true); + Scope.SetVariable(variable); + var boundStart = BindExpression(e.StartExpression); + var boundStop = BindExpression(e.StopExpression); + BoundExpression boundStep = null; + if (e.StepExpression != null) + boundStep = BindExpression(e.StepExpression); + var block = BindBlockStatement((BlockStatementSyntax) e.Block); + Scope = Scope.ParentScope; + return new BoundNumericForStatement(variable, boundStart, boundStop, boundStep, (BoundBlockStatement) block); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index 21112fc..a160b8d 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -24,6 +24,7 @@ namespace Upsilon.Binder BoundFunctionAssignmentStatement, BoundTableAssigmentStatement, BoundFullstopIndexExpression, - BoundMultiAssignmentStatement + BoundMultiAssignmentStatement, + BoundNumericForStatement } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs b/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs new file mode 100644 index 0000000..0220c3b --- /dev/null +++ b/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs @@ -0,0 +1,23 @@ +namespace Upsilon.Binder +{ + public class BoundNumericForStatement : BoundStatement + { + public VariableSymbol Variable { get; } + public BoundExpression BoundStart { get; } + public BoundExpression BoundStop { get; } + public BoundExpression BoundStep { get; } + public BoundBlockStatement Block { get; } + + public BoundNumericForStatement(VariableSymbol variable, BoundExpression boundStart, + BoundExpression boundStop, BoundExpression boundStep, BoundBlockStatement block) + { + Variable = variable; + BoundStart = boundStart; + BoundStop = boundStop; + BoundStep = boundStep; + Block = block; + } + + public override BoundKind Kind => BoundKind.BoundNumericForStatement; + } +} \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index b464c55..4ab5872 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -93,6 +93,7 @@ namespace Upsilon.Evaluator case BoundKind.BoundReturnStatement: case BoundKind.BoundTableAssigmentStatement: case BoundKind.BoundMultiAssignmentStatement: + case BoundKind.BoundNumericForStatement: EvaluateStatement((BoundStatement) b); break; default: @@ -128,6 +129,9 @@ namespace Upsilon.Evaluator case BoundKind.BoundMultiAssignmentStatement: EvaluateMultiAssignmentStatement((BoundMultiAssignmentStatement) e); break; + case BoundKind.BoundNumericForStatement: + EvaluateNumericForStatement((BoundNumericForStatement) e); + break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; @@ -485,5 +489,34 @@ namespace Upsilon.Evaluator } indexable.Set(_diagnostics, e.Span, index.ToString().ToLuaType(), value); } + + private void EvaluateNumericForStatement(BoundNumericForStatement e) + { + var innerEvaluator = new Evaluator(_diagnostics, Scope); + var startVal = (NumberLong)innerEvaluator.EvaluateExpression(e.BoundStart); + innerEvaluator.Scope.Set(e.Variable, startVal); + var stopVal = (NumberLong)innerEvaluator.EvaluateExpression(e.BoundStop); + long step = 1; + if (e.BoundStep != null) + { + var stepVal = (NumberLong)innerEvaluator.EvaluateExpression(e.BoundStep); + step = stepVal.Value; + } + + if (step > 0) + { + for (; startVal.Value <= stopVal.Value; startVal.Value = startVal.Value + step) + { + innerEvaluator.EvaluateBoundBlockStatement(e.Block); + } + } + else if (step < 0) + { + for (; startVal.Value >= stopVal.Value; startVal.Value = startVal.Value + step) + { + innerEvaluator.EvaluateBoundBlockStatement(e.Block); + } + } + } } } \ No newline at end of file diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 4a04f83..551bf47 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -87,6 +87,10 @@ namespace Upsilon.Parser { return ParseFunctionAssignmentStatement(); } + if (Current.Kind == SyntaxKind.ForKeyword) + { + return ParseForStatement(); + } return ParseExpressionStatement(); } @@ -131,6 +135,42 @@ namespace Upsilon.Parser } } + private StatementSyntax ParseForStatement() + { + var forToken = MatchToken(SyntaxKind.ForKeyword); + if (Next.Kind == SyntaxKind.Equals) + { + return ParseNumericForStatement(forToken); + } + return ParseGenericForStatement(forToken); + } + + private StatementSyntax ParseNumericForStatement(SyntaxToken forToken) + { + var identifier = (IdentifierToken)MatchToken(SyntaxKind.Identifier); + var equals = MatchToken(SyntaxKind.Equals); + var v1 = ParseExpression(); + var comma1 = MatchToken(SyntaxKind.Comma); + var v2 = ParseExpression(); + SyntaxToken comma2 = null; + ExpressionSyntax v3 = null; + if (Current.Kind == SyntaxKind.Comma) + { + comma2 = MatchToken(SyntaxKind.Comma); + v3 = ParseExpression(); + } + var doToken = MatchToken(SyntaxKind.DoKeyword); + var block = ParseBlockStatement(new []{SyntaxKind.EndKeyword}); + var endToken = MatchToken(SyntaxKind.EndKeyword); + return new NumericForStatementSyntax(forToken, identifier, equals, v1, comma1, v2, comma2, v3, doToken, + block, endToken); + } + + private StatementSyntax ParseGenericForStatement(SyntaxToken forToken) + { + throw new NotImplementedException(); + } + private ExpressionSyntax ParseFunctionExpression() { var functionToken = MatchToken(SyntaxKind.FunctionKeyword); diff --git a/Upsilon/Parser/StatementSyntax/NumericForStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/NumericForStatementSyntax.cs new file mode 100644 index 0000000..8ae2161 --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/NumericForStatementSyntax.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class NumericForStatementSyntax : StatementSyntax + { + public SyntaxToken ForToken { get; } + public IdentifierToken Identifier { get; } + public SyntaxToken EqualsToken { get; } + public ExpressionSyntax StartExpression { get; } + public SyntaxToken Comma1 { get; } + public ExpressionSyntax StopExpression { get; } + public SyntaxToken Comma2 { get; } + public ExpressionSyntax StepExpression { get; } + public SyntaxToken DoToken { get; } + public StatementSyntax Block { get; } + public SyntaxToken EndToken { get; } + + public NumericForStatementSyntax(SyntaxToken forToken, IdentifierToken identifier, SyntaxToken equalsToken, + ExpressionSyntax startExpression, SyntaxToken comma1, ExpressionSyntax stopExpression, SyntaxToken comma2, + ExpressionSyntax stepExpression, SyntaxToken doToken, StatementSyntax block, SyntaxToken endToken) + { + ForToken = forToken; + Identifier = identifier; + EqualsToken = equalsToken; + StartExpression = startExpression; + Comma1 = comma1; + StopExpression = stopExpression; + Comma2 = comma2; + StepExpression = stepExpression; + DoToken = doToken; + Block = block; + EndToken = endToken; + } + + public override SyntaxKind Kind => SyntaxKind.NumericForStatement; + public override IEnumerable ChildNodes() + { + yield return ForToken; + yield return Identifier; + yield return EqualsToken; + yield return StartExpression; + yield return Comma1; + yield return StopExpression; + if (Comma2 != null) + yield return Comma2; + if (StepExpression != null) + yield return StepExpression; + yield return DoToken; + yield return Block; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs index f9203e1..ccc2607 100644 --- a/Upsilon/Parser/SyntaxKeyWords.cs +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -34,6 +34,12 @@ namespace Upsilon.Parser return SyntaxKind.FunctionKeyword; case "return": return SyntaxKind.ReturnKeyword; + case "for": + return SyntaxKind.ForKeyword; + case "in": + return SyntaxKind.InKeyword; + case "do": + return SyntaxKind.DoKeyword; default: return SyntaxKind.Identifier; } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index 1cc2699..8a5104f 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -43,6 +43,9 @@ namespace Upsilon.Parser NilKeyword, FunctionKeyword, ReturnKeyword, + ForKeyword, + InKeyword, + DoKeyword, Identifier, @@ -73,5 +76,6 @@ namespace Upsilon.Parser ReturnStatement, FunctionAssignmentStatement, TableAssignmentStatement, + NumericForStatement } } \ No newline at end of file diff --git a/UpsilonTests/GeneralTests/BasicMathExpressions.cs b/UpsilonTests/GeneralTests/BasicMathExpressions.cs index 98eb8a7..4413d1e 100644 --- a/UpsilonTests/GeneralTests/BasicMathExpressions.cs +++ b/UpsilonTests/GeneralTests/BasicMathExpressions.cs @@ -1,9 +1,7 @@ -using System; -using System.Diagnostics; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class BasicMathExpressions : TestClass { diff --git a/UpsilonTests/GeneralTests/ForLoopTests.cs b/UpsilonTests/GeneralTests/ForLoopTests.cs new file mode 100644 index 0000000..84652f8 --- /dev/null +++ b/UpsilonTests/GeneralTests/ForLoopTests.cs @@ -0,0 +1,63 @@ +using Upsilon.Evaluator; +using Xunit; + +namespace UpsilonTests.GeneralTests +{ + public class ForLoopTests : TestClass + { + public ForLoopTests(StaticScriptFixture fix) : base(fix) + { + } + + [Fact] + public void BasicNumericForLoopTest() + { + const string input = @" +a = 0 +for i=0,5 do + a = a + i +end +return a +"; + var script = new Script(input, BoundScope, StaticScope); + Assert.Empty(script.Diagnostics.Messages); + var result = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(15, result); + } + + [Fact] + public void NumericForLoopWithDifferentStepTest() + { + const string input = @" +a = 0 +for i=0,10,2 do + a = a + i +end +return a +"; + var script = new Script(input, BoundScope, StaticScope); + Assert.Empty(script.Diagnostics.Messages); + var result = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(30, result); + } + + [Fact] + public void NumericForLoopWithNegativeStepTest() + { + const string input = @" +a = 0 +for i=5,0,-1 do + a = a + i +end +return a"; + var script = new Script(input, BoundScope, StaticScope); + Assert.Empty(script.Diagnostics.Messages); + var result = script.Evaluate(); + Assert.Empty(script.Diagnostics.Messages); + Assert.Equal(15, result); + } + + } +} \ No newline at end of file diff --git a/UpsilonTests/GeneralTests/FunctionTests.cs b/UpsilonTests/GeneralTests/FunctionTests.cs index f933a5e..8bea5c8 100644 --- a/UpsilonTests/GeneralTests/FunctionTests.cs +++ b/UpsilonTests/GeneralTests/FunctionTests.cs @@ -1,9 +1,7 @@ -using Upsilon.BaseTypes; -using Upsilon.BaseTypes.Number; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class FunctionTests : TestClass { diff --git a/UpsilonTests/GeneralTests/IfTests.cs b/UpsilonTests/GeneralTests/IfTests.cs index 5fca7d6..9ba73d2 100644 --- a/UpsilonTests/GeneralTests/IfTests.cs +++ b/UpsilonTests/GeneralTests/IfTests.cs @@ -1,9 +1,7 @@ -using Upsilon.BaseTypes; -using Upsilon.BaseTypes.Number; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class IfTests : TestClass { diff --git a/UpsilonTests/GeneralTests/MathPrecedence.cs b/UpsilonTests/GeneralTests/MathPrecedence.cs index c0f82b9..4f9746b 100644 --- a/UpsilonTests/GeneralTests/MathPrecedence.cs +++ b/UpsilonTests/GeneralTests/MathPrecedence.cs @@ -1,9 +1,7 @@ -using Upsilon.BaseTypes.Number; using Upsilon.Evaluator; -using Upsilon.Parser; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class MathPrecedence : TestClass { diff --git a/UpsilonTests/GeneralTests/ScopeTests.cs b/UpsilonTests/GeneralTests/ScopeTests.cs index 4153897..206f5d2 100644 --- a/UpsilonTests/GeneralTests/ScopeTests.cs +++ b/UpsilonTests/GeneralTests/ScopeTests.cs @@ -1,8 +1,7 @@ -using Upsilon.BaseTypes.Number; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class ScopeTests : TestClass { diff --git a/UpsilonTests/GeneralTests/StringTests.cs b/UpsilonTests/GeneralTests/StringTests.cs index 4f94108..aadfc68 100644 --- a/UpsilonTests/GeneralTests/StringTests.cs +++ b/UpsilonTests/GeneralTests/StringTests.cs @@ -1,7 +1,7 @@ using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class StringTests : TestClass { diff --git a/UpsilonTests/GeneralTests/TableTests.cs b/UpsilonTests/GeneralTests/TableTests.cs index e4e4041..416f773 100644 --- a/UpsilonTests/GeneralTests/TableTests.cs +++ b/UpsilonTests/GeneralTests/TableTests.cs @@ -1,7 +1,7 @@ using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class TableTests : TestClass { diff --git a/UpsilonTests/GeneralTests/UserDataDictionaryTests.cs b/UpsilonTests/GeneralTests/UserDataDictionaryTests.cs index a635dc0..97e1515 100644 --- a/UpsilonTests/GeneralTests/UserDataDictionaryTests.cs +++ b/UpsilonTests/GeneralTests/UserDataDictionaryTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class UserDataDictionaryTests : TestClass { diff --git a/UpsilonTests/GeneralTests/UserDataListTests.cs b/UpsilonTests/GeneralTests/UserDataListTests.cs index f6e9d92..a0ab702 100644 --- a/UpsilonTests/GeneralTests/UserDataListTests.cs +++ b/UpsilonTests/GeneralTests/UserDataListTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Upsilon.Evaluator; using Xunit; -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class UserDataListTests : TestClass { diff --git a/UpsilonTests/GeneralTests/UserDataOperatorTests.cs b/UpsilonTests/GeneralTests/UserDataOperatorTests.cs index 16fe481..29f6a79 100644 --- a/UpsilonTests/GeneralTests/UserDataOperatorTests.cs +++ b/UpsilonTests/GeneralTests/UserDataOperatorTests.cs @@ -2,10 +2,11 @@ using System; using Upsilon.BaseTypes.UserData; using Upsilon.Evaluator; using Xunit; + // ReSharper disable UnusedMember.Local // ReSharper disable ClassNeverInstantiated.Global -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class UserDataOperatorTests : TestClass, IClassFixture { diff --git a/UpsilonTests/GeneralTests/UserDataTests.cs b/UpsilonTests/GeneralTests/UserDataTests.cs index 3418205..25ebcef 100644 --- a/UpsilonTests/GeneralTests/UserDataTests.cs +++ b/UpsilonTests/GeneralTests/UserDataTests.cs @@ -2,9 +2,10 @@ using System; using Upsilon.BaseTypes.UserData; using Upsilon.Evaluator; using Xunit; + // ReSharper disable UnusedMember.Local -namespace UpsilonTests +namespace UpsilonTests.GeneralTests { public class UserDataTests : TestClass, IClassFixture {