diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 9002407..abe9e9e 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -33,6 +33,8 @@ namespace Upsilon.Binder return BindAssignmentStatement((AssignmentExpressionSyntax) s); case SyntaxKind.BlockStatement: return BindBlockStatement((BlockStatementSyntax) s); + case SyntaxKind.IfStatement: + return BindIfStatement((IfStatementSyntax) s); } throw new NotImplementedException(s.Kind.ToString()); @@ -173,5 +175,13 @@ namespace Upsilon.Binder } return new BoundBlockStatement(arr.ToImmutable()); } + + private BoundStatement BindIfStatement(IfStatementSyntax e) + { + var condition = BindExpressionStatement(e.Condition); + var block = BindBlockStatement(e.Block); + return new BoundIfStatement((BoundExpressionStatement) condition, (BoundBlockStatement) block); + } + } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundIfStatement.cs b/Upsilon/Binder/BoundIfStatement.cs new file mode 100644 index 0000000..135187f --- /dev/null +++ b/Upsilon/Binder/BoundIfStatement.cs @@ -0,0 +1,16 @@ +namespace Upsilon.Binder +{ + public class BoundIfStatement : BoundStatement + { + public BoundIfStatement(BoundExpressionStatement condition, BoundBlockStatement block) + { + Condition = condition; + Block = block; + } + + public override BoundKind Kind => BoundKind.BoundIfStatement; + + public BoundExpressionStatement Condition { get; } + public BoundBlockStatement Block { get; } + } +} \ No newline at end of file diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index c7d05bc..53af829 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -12,6 +12,7 @@ namespace Upsilon.Binder // Statements BoundAssignmentStatement, BoundExpressionStatement, - BoundBlockStatement + BoundBlockStatement, + BoundIfStatement } } \ No newline at end of file diff --git a/Upsilon/Diagnostics.cs b/Upsilon/Diagnostics.cs index 8c55782..3af91ae 100644 --- a/Upsilon/Diagnostics.cs +++ b/Upsilon/Diagnostics.cs @@ -25,9 +25,9 @@ namespace Upsilon Log(DiagnosticLevel.Error, message, location); } - public void LogBadCharacter(TextSpan location) + public void LogBadCharacter(TextSpan location, SyntaxKind expectedToken) { - LogError($"Invalid character found", location); + LogError($"Invalid character found. Expected: '{expectedToken}'", location); } public void LogUnknownVariable(TextSpan span, string variable) diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index c345205..2fcf1b7 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -40,6 +40,9 @@ namespace Upsilon.Evaluator case BoundKind.BoundBlockStatement: EvaluateBoundBlockStatement((BoundBlockStatement) e); break; + case BoundKind.BoundIfStatement: + EvaluateBoundIfStatement((BoundIfStatement) e); + break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; @@ -127,5 +130,14 @@ namespace Upsilon.Evaluator EvaluateStatement(boundStatement); } } + + private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement) + { + var condition = EvaluateExpression(boundBlockStatement.Condition.Expression); + if ((bool) condition) + { + EvaluateBoundBlockStatement(boundBlockStatement.Block); + } + } } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs index ad96311..5fb6334 100644 --- a/Upsilon/Evaluator/Script.cs +++ b/Upsilon/Evaluator/Script.cs @@ -18,6 +18,8 @@ namespace Upsilon.Evaluator ScriptString = new SourceText(scriptString); Diagnostics = new Diagnostics(ScriptString); _parsed = Parser.Parser.Parse(scriptString, Diagnostics); + if (variables == null) + variables = new Dictionary(); Binder = new Binder.Binder(new BoundScope(variables, null), Diagnostics); Evaluator = new Evaluator(this, Diagnostics, variables); } diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs index 0741aa7..c4e4d8e 100644 --- a/Upsilon/Parser/Lexer.cs +++ b/Upsilon/Parser/Lexer.cs @@ -99,7 +99,7 @@ namespace Upsilon.Parser default: if (char.IsLetter(Current)) return LexIdentifierOrKeyword(); - _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1), SyntaxKind.Identifier); return new SyntaxToken(SyntaxKind.BadToken, _position, "", null); } } @@ -116,7 +116,7 @@ namespace Upsilon.Parser { if (hasDecimalPoint) { - _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1), SyntaxKind.Number); return new SyntaxToken(SyntaxKind.BadToken, _position, "", null); } hasDecimalPoint = true; diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 8682642..4a0ecc3 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Immutable; using Upsilon.Text; @@ -43,13 +44,14 @@ namespace Upsilon.Parser if (Current.Kind == kind) return NextToken(); - _diagnostics.LogBadCharacter(Current.Span); + _diagnostics.LogBadCharacter(Current.Span, kind); return new SyntaxToken(kind, Current.Span.Start, "", null); } public StatementSyntax ParseScriptSyntax() { var statement = ParseBlockStatement(); + MatchToken(SyntaxKind.EndOfFile); return statement; } @@ -63,6 +65,10 @@ namespace Upsilon.Parser { return ParseAssignmentExpression(); } + if (Current.Kind == SyntaxKind.IfKeyword) + { + return ParseIfStatement(); + } return ParseExpressionStatement(); } @@ -75,8 +81,17 @@ namespace Upsilon.Parser var next = ParseStatement(); statements.Add(next); } - var endToken = NextToken(); - return new BlockStatementSyntax(statements.ToImmutable(), endToken); + return new BlockStatementSyntax(statements.ToImmutable()); + } + + public StatementSyntax ParseIfStatement() + { + var ifToken = MatchToken(SyntaxKind.IfKeyword); + var condition = ParseExpressionStatement(); + var thenToken = MatchToken(SyntaxKind.ThenKeyword); + var block = ParseBlockStatement(); + var endToken = MatchToken(SyntaxKind.EndKeyword); + return new IfStatementSyntax(ifToken, condition, thenToken, (BlockStatementSyntax) block, endToken); } public ExpressionStatementSyntax ParseExpressionStatement() @@ -147,7 +162,7 @@ namespace Upsilon.Parser var token = MatchToken(SyntaxKind.Identifier); return new VariableExpressionSyntax((IdentifierToken) token); default: - _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1), SyntaxKind.Identifier); NextToken(); return new BadExpressionSyntax(); } diff --git a/Upsilon/Parser/StatementSyntax/BlockStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/BlockStatementSyntax.cs index f4f30c6..8c3c052 100644 --- a/Upsilon/Parser/StatementSyntax/BlockStatementSyntax.cs +++ b/Upsilon/Parser/StatementSyntax/BlockStatementSyntax.cs @@ -7,17 +7,16 @@ namespace Upsilon.Parser { public sealed class BlockStatementSyntax : StatementSyntax { - public BlockStatementSyntax(ImmutableArray statements, SyntaxToken endToken) + public BlockStatementSyntax(ImmutableArray statements) { Statements = statements; - EndToken = endToken; var first = statements.FirstOrDefault(); - if (first != null) - Span = new TextSpan(first.Span.Start, endToken.Span.End); + var last = statements.LastOrDefault(); + if (first != null && last != null) + Span = new TextSpan(first.Span.Start, last.Span.End); } public ImmutableArray Statements { get; } - public SyntaxToken EndToken { get; } public override SyntaxKind Kind => SyntaxKind.BlockStatement; public override IEnumerable ChildNodes() { diff --git a/Upsilon/Parser/StatementSyntax/IfStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/IfStatementSyntax.cs new file mode 100644 index 0000000..5c10b1b --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/IfStatementSyntax.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Upsilon.Text; + +namespace Upsilon.Parser +{ + public sealed class IfStatementSyntax : StatementSyntax + { + public IfStatementSyntax(SyntaxToken ifToken, ExpressionStatementSyntax condition, SyntaxToken thenToken, + BlockStatementSyntax block, SyntaxToken endToken) + { + IfToken = ifToken; + Condition = condition; + ThenToken = thenToken; + Block = block; + EndToken = endToken; + + Span = new TextSpan(ifToken.Span.Start, endToken.Span.End); + } + + + public SyntaxToken IfToken { get; } + public ExpressionStatementSyntax Condition { get; } + public SyntaxToken ThenToken { get; } + public BlockStatementSyntax Block { get; } + public SyntaxToken EndToken { get; } + + public override SyntaxKind Kind => SyntaxKind.IfStatement; + public override IEnumerable ChildNodes() + { + yield return IfToken; + yield return Condition; + yield return ThenToken; + yield return Block; + yield return EndToken; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs index fcb983c..2f17713 100644 --- a/Upsilon/Parser/SyntaxKeyWords.cs +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -20,6 +20,10 @@ namespace Upsilon.Parser return SyntaxKind.LocalKeyword; case "end": return SyntaxKind.EndKeyword; + case "if": + return SyntaxKind.IfKeyword; + case "then": + return SyntaxKind.ThenKeyword; default: return SyntaxKind.Identifier; } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index 6d33808..84f9550 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -28,6 +28,8 @@ namespace Upsilon.Parser OrKeyword, LocalKeyword, EndKeyword, + IfKeyword, + ThenKeyword, Identifier, @@ -45,6 +47,7 @@ namespace Upsilon.Parser // statements ExpressionStatement, - BlockStatement + BlockStatement, + IfStatement } } \ No newline at end of file diff --git a/Upsilon/Upsilon.csproj b/Upsilon/Upsilon.csproj index 98f8dec..fd418ca 100644 --- a/Upsilon/Upsilon.csproj +++ b/Upsilon/Upsilon.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 diff --git a/UpsilonTests/IfTests.cs b/UpsilonTests/IfTests.cs new file mode 100644 index 0000000..0b66cd4 --- /dev/null +++ b/UpsilonTests/IfTests.cs @@ -0,0 +1,18 @@ +using Upsilon.Evaluator; +using Xunit; + +namespace UpsilonTests +{ + public class IfTests + { + [Fact] + public void BasicIfTest() + { + var input = "if true then val = true end"; + var script = new Script(input); + Assert.Empty(script.Diagnostics.Messages); + var actual = script.Evaluate(); + Assert.True(actual); + } + } +} \ No newline at end of file diff --git a/Yc/Program.cs b/Yc/Program.cs index 4917efc..8c38ab9 100644 --- a/Yc/Program.cs +++ b/Yc/Program.cs @@ -47,8 +47,9 @@ namespace Yc } else { + Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine(evaluate); - //variables = parsed.Variables; + Console.ResetColor(); } } }