Work on Diagnostics

This commit is contained in:
Deukhoofd 2018-11-11 10:26:52 +01:00
parent ab61a01573
commit 699377cdfc
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
10 changed files with 244 additions and 45 deletions

86
Upsilon/Diagnostics.cs Normal file
View File

@ -0,0 +1,86 @@
using System.Collections.Generic;
using Upsilon.Text;
namespace Upsilon
{
public class Diagnostics
{
public SourceText ScriptString { get; }
public readonly List<DiagnosticsMessage> Messages = new List<DiagnosticsMessage>();
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,
}
}

View File

@ -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,6 +67,8 @@ namespace Upsilon.Evaluator
{
var left = EvaluateExpression(e.Left);
var right = EvaluateExpression(e.Right);
try
{
switch (e.Operator.Kind)
{
case SyntaxKind.Plus:
@ -87,6 +91,12 @@ namespace Upsilon.Evaluator
throw new Exception("Invalid Binary Operator: " + e.Operator.Kind);
}
}
catch (NullReferenceException)
{
_diagnostics.LogNullReferenceError(e.Span);
return null;
}
}
private object EvaluateAssignmentExpression(AssignmentExpressionSyntax e)
{
@ -110,7 +120,9 @@ namespace Upsilon.Evaluator
{
return value;
}
throw new Exception("Unknown variable: " + varName);
_diagnostics.LogUnknownVariable(e.Span, varName);
return null;
}
}

View File

@ -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<string, object> 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()

View File

@ -47,7 +47,7 @@ namespace Upsilon.Evaluator
{
return _parentScope.TryGetVariable(key, out result);
}
throw new Exception("Variable not found: " + key);
return false;
}
}
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Upsilon.Parser
{
public class BadExpressionSyntax : ExpressionSyntax
{
public override SyntaxKind Kind => SyntaxKind.BadExpression;
public override IEnumerable<SyntaxNode> ChildNodes()
{
throw new System.NotImplementedException();
}
}
}

View File

@ -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<SyntaxToken> Lex(string text)
public static ImmutableArray<SyntaxToken> 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;
}

View File

@ -1,22 +1,25 @@
using System;
using System.Collections.Immutable;
using Upsilon.Text;
namespace Upsilon.Parser
{
public class Parser
{
private readonly ImmutableArray<SyntaxToken> _tokens;
private readonly Diagnostics _diagnostics;
private int _position;
private Parser(ImmutableArray<SyntaxToken> tokens)
private Parser(ImmutableArray<SyntaxToken> 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();
}
}

View File

@ -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,

View File

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

View File

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