From 699377cdfcaa6c6a37b2cf7da3b17b8704bbcf7d Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 11 Nov 2018 10:26:52 +0100 Subject: [PATCH] Work on Diagnostics --- Upsilon/Diagnostics.cs | 86 +++++++++++++++++++ Upsilon/Evaluator/Evaluator.cs | 54 +++++++----- Upsilon/Evaluator/Script.cs | 18 ++-- Upsilon/Evaluator/VariableScope.cs | 2 +- .../ExpressionSyntax/BadExpressionSyntax.cs | 13 +++ Upsilon/Parser/Lexer.cs | 16 ++-- Upsilon/Parser/Parser.cs | 16 ++-- Upsilon/Parser/SyntaxKind.cs | 5 +- Upsilon/Text/SourceText.cs | 30 +++++++ Yc/Program.cs | 49 ++++++++++- 10 files changed, 244 insertions(+), 45 deletions(-) create mode 100644 Upsilon/Diagnostics.cs create mode 100644 Upsilon/Parser/ExpressionSyntax/BadExpressionSyntax.cs create mode 100644 Upsilon/Text/SourceText.cs diff --git a/Upsilon/Diagnostics.cs b/Upsilon/Diagnostics.cs new file mode 100644 index 0000000..5e7f405 --- /dev/null +++ b/Upsilon/Diagnostics.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using Upsilon.Text; + +namespace Upsilon +{ + public class Diagnostics + { + public SourceText ScriptString { get; } + public readonly List Messages = new List(); + + public Diagnostics(SourceText scriptString) + { + ScriptString = scriptString; + } + + public void Log(DiagnosticLevel level, string message, TextSpan location) + { + Messages.Add(new DiagnosticsMessage(this, level, message, location)); + } + + public void LogError(string message, TextSpan location) + { + Log(DiagnosticLevel.Error, message, location); + } + + public void LogBadCharacter(TextSpan location) + { + LogError($"Invalid character found", location); + } + + public void LogUnknownVariable(TextSpan span, string variable) + { + LogError($"Unknown variable '{variable}'", span); + } + + public void LogNullReferenceError(TextSpan span) + { + LogError($"Null Reference Encountered", span); + } + } + + public class DiagnosticsMessage + { + public Diagnostics Diagnostics { get; } + private readonly DiagnosticLevel _diagnosticLevel; + public string Message { get; } + public TextSpan Span { get; } + + public DiagnosticsMessage(Diagnostics diagnostics, DiagnosticLevel diagnosticLevel, string message, TextSpan span) + { + _diagnosticLevel = diagnosticLevel; + Diagnostics = diagnostics; + Message = message; + Span = span; + } + + public override string ToString() + { + return $"{Message} at {Span.Start}\n{Diagnostics.ScriptString.GetSpan(Span)}"; + } + + public string BeforeError(int i = 5) + { + return Diagnostics.ScriptString.GetSpan(Span.Start - i, i); + } + + public string AtError() + { + return Diagnostics.ScriptString.GetSpan(Span); + } + + public string AfterError(int i = 5) + { + return Diagnostics.ScriptString.GetSpan(Span.Start + 1, i); + } + + } + + public enum DiagnosticLevel + { + Information, + Warning, + Error, + } + +} \ No newline at end of file diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index 20443d4..c85a56d 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -5,11 +5,13 @@ namespace Upsilon.Evaluator { public class Evaluator { + private readonly Diagnostics _diagnostics; public Script Script { get; } public VariableScope Scope { get; } - public Evaluator(Script script, VariableScope scope) + public Evaluator(Script script, VariableScope scope, Diagnostics diagnostics) { + _diagnostics = diagnostics; Script = script; Scope = scope; } @@ -65,26 +67,34 @@ namespace Upsilon.Evaluator { var left = EvaluateExpression(e.Left); var right = EvaluateExpression(e.Right); - switch (e.Operator.Kind) + try { - case SyntaxKind.Plus: - return (double)left + (double)right; - case SyntaxKind.Minus: - return (double)left - (double)right; - case SyntaxKind.Star: - return (double)left * (double)right; - case SyntaxKind.Slash: - return (double)left / (double)right; - case SyntaxKind.AndKeyword: - return (bool)left && (bool)right; - case SyntaxKind.OrKeyword: - return (bool)left || (bool)right; - case SyntaxKind.EqualsEquals: - return Equals(left, right); - case SyntaxKind.TildeEquals: - return !Equals(left, right); - default: - throw new Exception("Invalid Binary Operator: " + e.Operator.Kind); + switch (e.Operator.Kind) + { + case SyntaxKind.Plus: + return (double) left + (double) right; + case SyntaxKind.Minus: + return (double) left - (double) right; + case SyntaxKind.Star: + return (double) left * (double) right; + case SyntaxKind.Slash: + return (double) left / (double) right; + case SyntaxKind.AndKeyword: + return (bool) left && (bool) right; + case SyntaxKind.OrKeyword: + return (bool) left || (bool) right; + case SyntaxKind.EqualsEquals: + return Equals(left, right); + case SyntaxKind.TildeEquals: + return !Equals(left, right); + default: + throw new Exception("Invalid Binary Operator: " + e.Operator.Kind); + } + } + catch (NullReferenceException) + { + _diagnostics.LogNullReferenceError(e.Span); + return null; } } @@ -110,7 +120,9 @@ namespace Upsilon.Evaluator { return value; } - throw new Exception("Unknown variable: " + varName); + + _diagnostics.LogUnknownVariable(e.Span, varName); + return null; } } diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs index f1e333a..4cefd72 100644 --- a/Upsilon/Evaluator/Script.cs +++ b/Upsilon/Evaluator/Script.cs @@ -1,27 +1,31 @@ using System.Collections.Generic; using Upsilon.Parser; +using Upsilon.Text; namespace Upsilon.Evaluator { public class Script : VariableScope { - private string ScriptString { get; } + private SourceText ScriptString { get; } private Evaluator Evaluator { get; } public readonly ScriptSyntax Parsed; + public Diagnostics Diagnostics { get; } public Script(string scriptString) { - ScriptString = scriptString; - Evaluator = new Evaluator(this, this); - Parsed = Parser.Parser.Parse(scriptString); + ScriptString = new SourceText(scriptString); + Diagnostics = new Diagnostics(ScriptString); + Parsed = Parser.Parser.Parse(scriptString, Diagnostics); + Evaluator = new Evaluator(this, this, Diagnostics); } public Script(string scriptString, Dictionary variables = null) :base(variables: variables) { - ScriptString = scriptString; - Evaluator = new Evaluator(this, this); - Parsed = Parser.Parser.Parse(scriptString); + ScriptString = new SourceText(scriptString); + Diagnostics = new Diagnostics(ScriptString); + Evaluator = new Evaluator(this, this, Diagnostics); + Parsed = Parser.Parser.Parse(scriptString, Diagnostics); } public object Evaluate() diff --git a/Upsilon/Evaluator/VariableScope.cs b/Upsilon/Evaluator/VariableScope.cs index 88595f7..fe98f38 100644 --- a/Upsilon/Evaluator/VariableScope.cs +++ b/Upsilon/Evaluator/VariableScope.cs @@ -47,7 +47,7 @@ namespace Upsilon.Evaluator { return _parentScope.TryGetVariable(key, out result); } - throw new Exception("Variable not found: " + key); + return false; } } } \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/BadExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/BadExpressionSyntax.cs new file mode 100644 index 0000000..35ff20e --- /dev/null +++ b/Upsilon/Parser/ExpressionSyntax/BadExpressionSyntax.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class BadExpressionSyntax : ExpressionSyntax + { + public override SyntaxKind Kind => SyntaxKind.BadExpression; + public override IEnumerable ChildNodes() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/Lexer.cs b/Upsilon/Parser/Lexer.cs index 8f9c73b..1bdc677 100644 --- a/Upsilon/Parser/Lexer.cs +++ b/Upsilon/Parser/Lexer.cs @@ -1,22 +1,24 @@ -using System; using System.Collections.Immutable; using System.Text; +using Upsilon.Text; namespace Upsilon.Parser { public class Lexer { private readonly string _text; + private readonly Diagnostics _diagnostics; private int _position; - private Lexer(string text) + private Lexer(string text, Diagnostics diagnostics) { _text = text; + _diagnostics = diagnostics; } - public static ImmutableArray Lex(string text) + public static ImmutableArray Lex(string text, Diagnostics diagnostics) { - var lexer = new Lexer(text); + var lexer = new Lexer(text, diagnostics); return lexer.Lex(); } @@ -97,7 +99,8 @@ namespace Upsilon.Parser default: if (char.IsLetter(Current)) return LexIdentifierOrKeyword(); - throw new Exception("Unknown token character: " + Current); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + return new SyntaxToken(SyntaxKind.BadToken, _position, "", null); } } @@ -113,7 +116,8 @@ namespace Upsilon.Parser { if (hasDecimalPoint) { - throw new Exception("No second decimal allowed there"); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + return new SyntaxToken(SyntaxKind.BadToken, _position, "", null); } hasDecimalPoint = true; } diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index 2c6c561..40c5bb5 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -1,22 +1,25 @@ using System; using System.Collections.Immutable; +using Upsilon.Text; namespace Upsilon.Parser { public class Parser { private readonly ImmutableArray _tokens; + private readonly Diagnostics _diagnostics; private int _position; - private Parser(ImmutableArray tokens) + private Parser(ImmutableArray tokens, Diagnostics diagnostics) { _tokens = tokens; + _diagnostics = diagnostics; } - public static ScriptSyntax Parse(string text) + public static ScriptSyntax Parse(string text, Diagnostics diagnostics) { - var tokens = Lexer.Lex(text); - return new Parser(tokens).ParseScriptSyntax(); + var tokens = Lexer.Lex(text, diagnostics); + return new Parser(tokens, diagnostics).ParseScriptSyntax(); } private SyntaxToken Current => Get(0); @@ -41,6 +44,7 @@ namespace Upsilon.Parser if (Current.Kind == kind) return NextToken(); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); return new SyntaxToken(kind, Current.Span.Start, "", null); } @@ -121,7 +125,9 @@ namespace Upsilon.Parser var token = MatchToken(SyntaxKind.Identifier); return new VariableExpressionSyntax((IdentifierToken) token); default: - throw new Exception("Unknown primary expression type: " + Current.Kind); + _diagnostics.LogBadCharacter(new TextSpan(_position, 1)); + NextToken(); + return new BadExpressionSyntax(); } } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index a3bb0f3..7a145c3 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -2,10 +2,12 @@ namespace Upsilon.Parser { public enum SyntaxKind { - // tokens + // misc EndOfFile, WhiteSpace, + BadToken, + // tokens Number, Plus, Minus, @@ -35,6 +37,7 @@ namespace Upsilon.Parser ParenthesizedExpression, AssignmentExpression, VariableExpression, + BadExpression, // script unit ScriptUnit, diff --git a/Upsilon/Text/SourceText.cs b/Upsilon/Text/SourceText.cs new file mode 100644 index 0000000..a72aae1 --- /dev/null +++ b/Upsilon/Text/SourceText.cs @@ -0,0 +1,30 @@ +using System; + +namespace Upsilon.Text +{ + public class SourceText + { + private readonly string _text; + + public SourceText(string text) + { + _text = text; + } + + public string GetSpan(int start, int length) + { + if (start < 0) + { + length += start; + start = 0; + }; + if (start + length >= _text.Length) length = _text.Length - start; + return _text.Substring(start, length); + } + + public string GetSpan(TextSpan span) + { + return GetSpan(span.Start, span.Length); + } + } +} \ No newline at end of file diff --git a/Yc/Program.cs b/Yc/Program.cs index 2f958f3..a706b4b 100644 --- a/Yc/Program.cs +++ b/Yc/Program.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using Upsilon; using Upsilon.Evaluator; -using Upsilon.Utilities; namespace Yc { @@ -21,10 +21,51 @@ namespace Yc } var parsed = new Script(input, variables); - //Console.WriteLine(parsed.Parsed.Print()); - Console.WriteLine(parsed.Evaluate()); - variables = parsed.Variables; + if (parsed.Diagnostics.Messages.Count > 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Errors were found during parsing"); + foreach (var diagnosticsMessage in parsed.Diagnostics.Messages) + { + LogError(diagnosticsMessage); + } + Console.ResetColor(); + continue; + } + + var evaluate = parsed.Evaluate(); + if (parsed.Diagnostics.Messages.Count > 0) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Errors were found during evaluating"); + foreach (var diagnosticsMessage in parsed.Diagnostics.Messages) + { + LogError(diagnosticsMessage); + } + Console.ResetColor(); + } + else + { + Console.WriteLine(evaluate); + variables = parsed.Variables; + } } } + + public static void LogError(DiagnosticsMessage message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(message.Message); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.Write(message.BeforeError()); + + Console.ForegroundColor = ConsoleColor.Red; + Console.Write(message.AtError()); + + Console.ForegroundColor = ConsoleColor.Gray; + Console.Write(message.AfterError()); + Console.WriteLine(); + } } } \ No newline at end of file