From 834d65f38e969f8c7e7978add2809e821f2ac7a5 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Thu, 21 Feb 2019 19:53:14 +0100 Subject: [PATCH] Implemented very basic stacktrace --- .../ScriptFunction/ScriptFunction.cs | 9 +++-- .../ScriptMethodInfoFunction.cs | 25 ++++++++----- .../ScriptFunction/ScriptRuntimeFunction.cs | 37 ++++++++++++------- .../BoundExpressions/BoundBinaryExpression.cs | 20 ++++------ .../BoundFunctionCallExpression.cs | 2 +- .../BoundFunctionExpression.cs | 2 +- .../BoundFunctionAssignmentStatement.cs | 1 + .../BoundStatements/BoundYieldStatement.cs | 7 ++-- Upsilon/Evaluator/EvaluationState.cs | 3 +- Upsilon/Evaluator/Stacktrace.cs | 26 +++++++++++++ Upsilon/Exceptions/EvaluationException.cs | 16 +++++++- Upsilon/Exceptions/ScriptRuntimeException.cs | 30 --------------- 12 files changed, 100 insertions(+), 78 deletions(-) create mode 100644 Upsilon/Evaluator/Stacktrace.cs delete mode 100644 Upsilon/Exceptions/ScriptRuntimeException.cs diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs index defd6cb..27d4fe9 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs @@ -6,8 +6,11 @@ namespace Upsilon.BaseTypes.ScriptFunction { public abstract class ScriptFunction : ScriptType { - protected ScriptFunction(bool isCoroutine) + public string Name { get; protected set; } + + protected ScriptFunction(string name, bool isCoroutine) { + Name = name; IsCoroutine = isCoroutine; } @@ -24,7 +27,7 @@ namespace Upsilon.BaseTypes.ScriptFunction public bool IsCoroutine { get; } - public abstract ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span); - public abstract IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span); + public abstract ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, TextSpan span, EvaluationState state); + public abstract IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, TextSpan span, EvaluationState state); } } \ No newline at end of file diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs index 916b979..a411201 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs @@ -14,7 +14,7 @@ namespace Upsilon.BaseTypes.ScriptFunction public class ScriptMethodInfoFunction : ScriptFunction { public ScriptMethodInfoFunction(UserDataMethod method, object o, bool directTypeManipulation, - bool isCoroutine, bool passScriptReference = false, bool passScopeReference = false) : base(isCoroutine) + bool isCoroutine, bool passScriptReference = false, bool passScopeReference = false) : base(method.Name, isCoroutine) { Method = method; _object = o; @@ -46,33 +46,38 @@ namespace Upsilon.BaseTypes.ScriptFunction return Method.GetMethods().First().Parameters; } - public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) + public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, TextSpan span, EvaluationState state) { - var result = Execute(diagnostics, variables, script, scope, span); + state.Stacktrace.Push(this); + var result = Execute(variables, state.Script, scope, span); + state.Stacktrace.Pop(); if (_directTypeManipulation) return (ScriptType)result; return result.ToScriptType(); } - public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, - TextSpan span) + public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, + TextSpan span , EvaluationState state) { - var result = Execute(diagnostics, variables, script, scope, span); + state.Stacktrace.Push(this); + var result = Execute(variables, state.Script, scope, span); if (result is IEnumerator enumerator) { while (enumerator.MoveNext()) { yield return enumerator.Current; } + state.Stacktrace.Pop(); yield break; } yield return result; + state.Stacktrace.Pop(); } - private object Execute(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) + private object Execute(IEnumerable variables, Script script, EvaluationScope scope, TextSpan span) { - var objects = new List(); + var objects = new List(); if (_passScriptReference) objects.Add(script); if (_passScopeReference) @@ -132,9 +137,9 @@ namespace Upsilon.BaseTypes.ScriptFunction // if we haven't found a method, we throw an exception if (method == null) { - throw new ScriptRuntimeException(script.FileName, + throw new EvaluationException(script.FileName, $"Can't find method {Method.Name} with parameter types {string.Join(", ", convertedTypes.Select(x => x.Name))} on type {_object.GetType().Name}", - span, diagnostics.ScriptString.GetSpan(span)); + span); } // get the method parameters var parameters = method.GetParameters(); diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs index 28bcbde..b77cba4 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs @@ -16,37 +16,46 @@ namespace Upsilon.BaseTypes.ScriptFunction { public List Options { get; } - public ScriptRuntimeFunction(List options, bool isCoroutine) : base(isCoroutine) + public ScriptRuntimeFunction(string name, List options, bool isCoroutine) : base(name, isCoroutine) { Options = options; } - public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, - EvaluationScope scope, TextSpan span) + public void SetName(string name) { - var option = GetValidOption(variables); - if (option == null) - { - throw new EvaluationException(script.FileName, - $"No valid function found", span); - } - return option.Run(diagnostics, variables, script, scope, span); + Name = name; } - public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, - TextSpan span) + public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, TextSpan span, EvaluationState state) { var option = GetValidOption(variables); if (option == null) { - throw new EvaluationException(script.FileName, + throw new EvaluationException(state.Script.FileName, $"No valid function found", span); } - var coroutine = option.RunCoroutine(diagnostics, variables, script, scope, span); + state.Stacktrace.Push(this); + var result = option.Run(diagnostics, variables, state.Script, scope, span); + state.Stacktrace.Pop(); + return result; + } + + public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, EvaluationScope scope, + TextSpan span, EvaluationState state) + { + var option = GetValidOption(variables); + if (option == null) + { + throw new EvaluationException(state.Script.FileName, + $"No valid function found", span); + } + state.Stacktrace.Push(this); + var coroutine = option.RunCoroutine(diagnostics, variables, state.Script, scope, span); while (coroutine.MoveNext()) { yield return coroutine.Current; } + state.Stacktrace.Pop(); } public ScriptRuntimeFunctionOption GetValidOption(object[] variables) diff --git a/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs b/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs index c34cb1a..f035813 100644 --- a/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundBinaryExpression.cs @@ -149,36 +149,32 @@ namespace Upsilon.Binder return ((ScriptNumber) left) < ((ScriptNumber) right); } - throw new ScriptRuntimeException(state.Script.FileName, - $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span, - diagnostics.ScriptString.GetSpan(Span)); + throw new EvaluationException(state.Script.FileName, + $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span); case BoundBinaryOperator.OperatorKind.LessEquals: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber) left) <= ((ScriptNumber) right); } - throw new ScriptRuntimeException(state.Script.FileName, - $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span, - diagnostics.ScriptString.GetSpan(Span)); + throw new EvaluationException(state.Script.FileName, + $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span); case BoundBinaryOperator.OperatorKind.Greater: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber) left) > ((ScriptNumber) right); } - throw new ScriptRuntimeException(state.Script.FileName, - $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span, - diagnostics.ScriptString.GetSpan(Span)); + throw new EvaluationException(state.Script.FileName, + $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span); case BoundBinaryOperator.OperatorKind.GreaterEquals: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber) left) >= ((ScriptNumber) right); } - throw new ScriptRuntimeException(state.Script.FileName, - $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span, - diagnostics.ScriptString.GetSpan(Span)); + throw new EvaluationException(state.Script.FileName, + $"Can't find operator '{Operator.Kind}' for types '{left.Type}' and '{right.Type}'", Span); default: throw new Exception("Invalid Binary Operator: " + Operator.Kind); } diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs index 3a2e252..7d09e33 100644 --- a/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionCallExpression.cs @@ -48,7 +48,7 @@ namespace Upsilon.Binder ls.Add(evaluate); } - var val = function.Run(diagnostics, ls.ToArray(), state.Script, scope, Span); + var val = function.Run(diagnostics, ls.ToArray(), scope, Span, state); return val; } } diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs index fc19d67..df9e77a 100644 --- a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs @@ -40,7 +40,7 @@ namespace Upsilon.Binder internal override ScriptType Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state) { var option = new ScriptRuntimeFunction.ScriptRuntimeFunctionOption(Parameters, Block, scope); - var func = new ScriptRuntimeFunction(new List() {option}, + var func = new ScriptRuntimeFunction("", new List() {option}, IsCoroutine); return func; diff --git a/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs index c4050a3..e89dfd1 100644 --- a/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs @@ -28,6 +28,7 @@ namespace Upsilon.Binder internal override void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state) { var func = (ScriptRuntimeFunction)Func.Evaluate(scope, diagnostics, ref state); + func.SetName(Variable.Name); if (Variable.Local) { if (scope.Variables.TryGetValue(Variable.Name, out var f) && f is ScriptRuntimeFunction scriptRuntimeFunction) diff --git a/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs b/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs index 006dfe0..c0dd9ab 100644 --- a/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs @@ -27,9 +27,8 @@ namespace Upsilon.Binder internal override void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state) { - throw new ScriptRuntimeException(state.Script.FileName, - "Yielding in a function that's not executed as a coroutine is not possible.", Span, - diagnostics.ScriptString.GetLine(Span.StartLine)); + throw new EvaluationException(state.Script.FileName, + "Yielding in a function that's not executed as a coroutine is not possible.", Span); } internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) @@ -51,7 +50,7 @@ namespace Upsilon.Binder var evaluate = t.Evaluate(scope, diagnostics, ref state); ls.Add(evaluate); } - var coroutine = function.RunCoroutine(diagnostics, ls.ToArray(), state.Script, scope, Span); + var coroutine = function.RunCoroutine(diagnostics, ls.ToArray(), scope, Span, state); while (coroutine.MoveNext()) { yield return coroutine.Current; diff --git a/Upsilon/Evaluator/EvaluationState.cs b/Upsilon/Evaluator/EvaluationState.cs index 195b2ad..70ef488 100644 --- a/Upsilon/Evaluator/EvaluationState.cs +++ b/Upsilon/Evaluator/EvaluationState.cs @@ -2,12 +2,13 @@ using Upsilon.BaseTypes; namespace Upsilon.Evaluator { - internal class EvaluationState + public class EvaluationState { public Script Script; public bool Returned; public bool HasBroken; public ScriptType ReturnValue; + public Stacktrace Stacktrace = new Stacktrace(); public ScriptType LastValue; } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Stacktrace.cs b/Upsilon/Evaluator/Stacktrace.cs new file mode 100644 index 0000000..7aa23b9 --- /dev/null +++ b/Upsilon/Evaluator/Stacktrace.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using Upsilon.BaseTypes.ScriptFunction; + +namespace Upsilon.Evaluator +{ + public class Stacktrace + { + private readonly Stack _stack = new Stack(); + + public void Push(ScriptFunction statement) + { + _stack.Push(statement); + } + + public ScriptFunction Pop() + { + return _stack.Pop(); + } + + public IEnumerable GetTopStack(int i) + { + return _stack.Take(i); + } + } +} \ No newline at end of file diff --git a/Upsilon/Exceptions/EvaluationException.cs b/Upsilon/Exceptions/EvaluationException.cs index 0d69d4a..e20ec2e 100644 --- a/Upsilon/Exceptions/EvaluationException.cs +++ b/Upsilon/Exceptions/EvaluationException.cs @@ -1,16 +1,20 @@ using System; +using System.Text; +using Upsilon.Evaluator; using Upsilon.Text; namespace Upsilon.Exceptions { public class EvaluationException : Exception { + private readonly Stacktrace _stacktrace; public string FileName { get; } public string ErrorMessage { get; } public TextSpan Span { get; } - public EvaluationException(string fileName, string message, TextSpan span) + public EvaluationException(string fileName, string message, TextSpan span, Stacktrace stacktrace = null) { + _stacktrace = stacktrace; FileName = fileName; ErrorMessage = message; Span = span; @@ -18,7 +22,15 @@ namespace Upsilon.Exceptions public override string ToString() { - return $"[{FileName}] ({Span.StartLine},{Span.StartPosition}) {ErrorMessage}"; + var err = new StringBuilder($"[{FileName}] ({Span.StartLine},{Span.StartPosition}) {ErrorMessage}"); + if (_stacktrace != null) + { + foreach (var statement in _stacktrace.GetTopStack(10)) + { + err.Append($"\n{statement.Name}"); + } + } + return err.ToString(); } public override string Message => ToString(); diff --git a/Upsilon/Exceptions/ScriptRuntimeException.cs b/Upsilon/Exceptions/ScriptRuntimeException.cs deleted file mode 100644 index 9f326ba..0000000 --- a/Upsilon/Exceptions/ScriptRuntimeException.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using Upsilon.Text; - -namespace Upsilon.Exceptions -{ - public class ScriptRuntimeException : Exception - { - public string FileName { get; } - public string ErrorMessage { get; } - public int Line { get; } - public int Character { get; } - public string ErrorLine { get; } - - public ScriptRuntimeException(string fileName, string errorMessage, TextSpan position, string errorLine) - { - FileName = fileName; - ErrorMessage = errorMessage; - Line = position.StartLine; - Character = position.StartPosition; - ErrorLine = errorLine; - } - - public override string ToString() - { - return $"[{FileName}] {ErrorMessage} at ({Line}, {Character})\n{ErrorLine}"; - } - - public override string Message => ToString(); - } -} \ No newline at end of file