From 67b4e5950678a4125a8988ce39785b9441f51a38 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Wed, 16 Feb 2022 08:36:19 +0100 Subject: [PATCH] Properly expose stacktraces and related data to user code (#3271) --- CHANGELOG.md | 3 + .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 98 ++++++++++--------- .../Base/0.0.0-dev/src/Meta/Enso_Project.enso | 2 +- .../0.0.0-dev/src/Runtime/Extensions.enso | 93 ++++++++++++++++++ .../Base/0.0.0-dev/src/System/File.enso | 10 ++ docs/syntax/imports.md | 16 +++ .../generic/GetExecutableNameNode.java | 36 +++++++ .../generic/GetSourceLocationNode.java | 36 +++++++ .../generic/HasSourceLocationNode.java | 21 ++++ .../builtin/runtime/GetStackTraceNode.java | 25 +++++ .../org/enso/interpreter/runtime/Context.java | 13 +++ .../interpreter/runtime/builtin/Builtins.java | 2 + .../interpreter/runtime/builtin/Polyglot.java | 6 ++ .../interpreter/runtime/data/EnsoFile.java | 4 + .../interpreter/runtime/data/EnsoSource.java | 45 +++++++++ .../runtime/data/EnsoSourceSection.java | 67 +++++++++++++ .../runtime/src/main/resources/Builtins.enso | 31 ++++++ test/Tests/src/Main.enso | 3 + test/Tests/src/Runtime/Stack_Traces_Spec.enso | 20 ++++ 19 files changed, 482 insertions(+), 49 deletions(-) create mode 100644 distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetExecutableNameNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetSourceLocationNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/HasSourceLocationNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSource.java create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSourceSection.java create mode 100644 test/Tests/src/Runtime/Stack_Traces_Spec.enso diff --git a/CHANGELOG.md b/CHANGELOG.md index a0dbe65788cf..2bd22a6cf83c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ - [Implemented `Range.find`, `Table.rename_columns` and `Table.use_first_row_as_names` operations][3249] - [Implemented `Text.at` and `Text.is_digit` methods][3269] +- [Implemented `Runtime.get_stack_trace` together with some utilities to process + stack traces and code locations][3271] - [Implemented `Vector.flatten`][3259] [debug-shortcuts]: @@ -59,6 +61,7 @@ [3249]: https://github.com/enso-org/enso/pull/3249 [3264]: https://github.com/enso-org/enso/pull/3264 [3269]: https://github.com/enso-org/enso/pull/3269 +[3271]: https://github.com/enso-org/enso/pull/3271 [3259]: https://github.com/enso-org/enso/pull/3259 #### Enso Compiler diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 56d35b565f5e..cb81af7bbb51 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -1,55 +1,57 @@ -import Standard.Base.Data.Any.Extensions -import Standard.Base.Data.Array.Extensions -import Standard.Base.Data.Interval -import Standard.Base.Data.Json -import Standard.Base.Data.List -import Standard.Base.Data.Locale -import Standard.Base.Data.Map -import Standard.Base.Data.Maybe -import Standard.Base.Data.Noise -import Standard.Base.Data.Number.Extensions -import Standard.Base.Data.Ordering -import Standard.Base.Data.Ordering.Sort_Order -import Standard.Base.Data.Pair -import Standard.Base.Data.Range -import Standard.Base.Data.Text.Extensions -import Standard.Base.Data.Vector -import Standard.Base.Error.Common -import Standard.Base.Error.Extensions -import Standard.Base.Math -import Standard.Base.Meta -import Standard.Base.Meta.Enso_Project -import Standard.Base.Polyglot.Java -import Standard.Base.System.File -import Standard.Base.Data.Text.Regex.Mode as Regex_Mode +import project.Data.Any.Extensions +import project.Data.Array.Extensions +import project.Data.Interval +import project.Data.Json +import project.Data.List +import project.Data.Locale +import project.Data.Map +import project.Data.Maybe +import project.Data.Noise +import project.Data.Number.Extensions +import project.Data.Ordering +import project.Data.Ordering.Sort_Order +import project.Data.Pair +import project.Data.Range +import project.Data.Text.Extensions +import project.Data.Vector +import project.Error.Common +import project.Error.Extensions +import project.Math +import project.Meta +import project.Meta.Enso_Project +import project.Polyglot.Java +import project.Runtime.Extensions +import project.System.File +import project.Data.Text.Regex.Mode as Regex_Mode from Standard.Builtins import Nothing, Number, Integer, Any, True, False, Cons, Boolean, Arithmetic_Error -export Standard.Base.Data.Interval -export Standard.Base.Data.Json -export Standard.Base.Data.Locale -export Standard.Base.Data.Map -export Standard.Base.Data.Maybe -export Standard.Base.Data.Ordering -export Standard.Base.Data.Ordering.Sort_Order -export Standard.Base.Data.Vector -export Standard.Base.Math -export Standard.Base.Meta -export Standard.Base.System.File -export Standard.Base.Data.Text.Regex.Mode as Regex_Mode +export project.Data.Interval +export project.Data.Json +export project.Data.Locale +export project.Data.Map +export project.Data.Maybe +export project.Data.Ordering +export project.Data.Ordering.Sort_Order +export project.Data.Vector +export project.Math +export project.Meta +export project.System.File +export project.Data.Text.Regex.Mode as Regex_Mode -from Standard.Base.Data.Any.Extensions export all -from Standard.Base.Data.Array.Extensions export all -from Standard.Base.Data.List export Nil, Cons -from Standard.Base.Data.Number.Extensions export all hiding Math, String, Double -from Standard.Base.Data.Noise export all hiding Noise -from Standard.Base.Data.Pair export Pair -from Standard.Base.Data.Range export Range -from Standard.Base.Data.Text.Extensions export Text, Split_Kind, Line_Ending_Style -from Standard.Base.Error.Common export all -from Standard.Base.Error.Extensions export all -from Standard.Base.Meta.Enso_Project export all -from Standard.Base.Polyglot.Java export all +from project.Data.Any.Extensions export all +from project.Data.Array.Extensions export all +from project.Data.List export Nil, Cons +from project.Data.Number.Extensions export all hiding Math, String, Double +from project.Data.Noise export all hiding Noise +from project.Data.Pair export Pair +from project.Data.Range export Range +from project.Data.Text.Extensions export Text, Split_Kind, Line_Ending_Style +from project.Error.Common export all +from project.Error.Extensions export all +from project.Meta.Enso_Project export all +from project.Polyglot.Java export all +from project.Runtime.Extensions export all from Standard.Builtins export all hiding Meta, Less, Equal, Greater, Ordering diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso index 35b4aa2474f5..35137147230f 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta/Enso_Project.enso @@ -9,7 +9,7 @@ import Standard.Builtins Enso_Project.root Builtins.Project_Description.root : File.File -Builtins.Project_Description.root = File.File this.prim_root_file +Builtins.Project_Description.root = File.new this.prim_root_file.getPath ## Returns the root data directory of the project. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso new file mode 100644 index 000000000000..ba4a27ef7510 --- /dev/null +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime/Extensions.enso @@ -0,0 +1,93 @@ +from Standard.Base import all + +## ADVANCED + UNSTABLE + Represents a source location in Enso code. Contains information about the + source file and code position within it. +type Source_Location + ## PRIVATE + type Source_Location prim_location + + ## UNSTABLE + Pretty prints the location. + to_text : Text + to_text = + '(Source_Location ' + this.formatted_coordinates + ')' + + ## UNSTABLE + + Returns the 1-based line index of the start of this code range. + start_line : Integer + start_line = this.prim_location.getStartLine + + ## UNSTABLE + + Returns the 1-based line index of the end of this code range. + end_line : Integer + end_line = this.prim_location.getEndLine + + ## UNSTABLE + + Returns the 1-based column index of the start of this code range. + start_column : Integer + start_column = this.prim_location.getStartColumn + + ## UNSTABLE + + Returns the 1-based column index of the end of this code range. + end_column : Integer + end_column = this.prim_location.getEndColumn + + ## UNSTABLE + + Returns a pretty-printed location (file and line info). + formatted_coordinates : Text + formatted_coordinates = + start_line = this.start_line + end_line = this.end_line + indices = case start_line == end_line of + True -> + row = start_line.to_text + start = this.start_column.to_text + end = this.end_column.to_text + row + ":" + start + "-" + end + False -> + start_line + '-' + end_line + cwd = File.current_directory + file = this.file.absolute + formatted_file = case file.is_child_of cwd of + True -> cwd.relativize file . path + _ -> file.path + formatted_file + ":" + indices + + ## UNSTABLE + + Return the source file corresponding to this location. + file : File.File + file = File.new this.prim_location.getSource.getPath + +## ADVANCED + UNSTABLE + + Represents a single stack frame in an Enso stack trace. +type Stack_Trace_Element + ## PRIVATE + type Stack_Trace_Element name source_location + +## ADVANCED + UNSTABLE + + Returns the execution stack trace of its call site. The ordering of the + resulting vector is such that the top stack frame is the first element. +Runtime.get_stack_trace : Vector.Vector Stack_Trace_Element +Runtime.get_stack_trace = + prim_stack = this.primitive_get_stack_trace + stack_with_prims = Vector.Vector prim_stack + stack = stack_with_prims.map el-> + loc = case Polyglot.has_source_location el of + True -> Source_Location (Polyglot.get_source_location el) + False -> Nothing + name = Polyglot.get_executable_name el + Stack_Trace_Element name loc + # drop this frame and the one from `Runtime.primitive_get_stack_trace` + stack.drop_start 2 diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso index e19d3e4128b8..dce90ccac7d4 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso @@ -598,6 +598,16 @@ type File matcher.matches (Path.of pathStr) filtered + ## UNSTABLE + + Checks if `this` is a child path of `other`. + is_child_of other = this.prim_file.startsWith other.prim_file + + ## UNSTABLE + + Transforms `child` to a relative path with respect to `this`. + relativize child = File (this.prim_file.relativize child.prim_file) + ## An output stream, allowing for interactive writing of contents into an open file. type Output_Stream diff --git a/docs/syntax/imports.md b/docs/syntax/imports.md index 93df7759ca7f..bdf4b6c545ad 100644 --- a/docs/syntax/imports.md +++ b/docs/syntax/imports.md @@ -14,6 +14,7 @@ code from modules. +- [Qualified Names](#qualified-names) - [Import Syntax](#import-syntax) - [Qualified Imports](#qualified-imports) - [Unqualified Imports](#unqualified-imports) @@ -24,6 +25,21 @@ code from modules. +## Qualified Names + +Both imports and exports require the use of qualified module names. A qualified +name consists of the library namespace (usually organization under which its +published) and the library name, followed by module names mirroring the source +tree of the library. For example the file `src/Stuff/Things/Util.enso` inside +the library `My_Lib` published by the user `wdanilo` would have the following +qualified name: `wdanilo.My_Lib.Stuff.Things.Util`. To facilitate library +renaming (or deciding on the publishing organization later in the development +cycle, or working on a project that won't be published) it is possible to use +the keyword `project` instead of namespace and project name, to import a file in +the same project. Therefore, the file `src/Varia/Tools/Manager.enso` in `My_Lib` +published (or not) by `wdanilo` may use `project.Stuff.Things.Util` to refer to +the previously mentioned file. + ## Import Syntax There are two main ways of importing a module into the current scope. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetExecutableNameNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetExecutableNameNode.java new file mode 100644 index 000000000000..cad4ded4c575 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetExecutableNameNode.java @@ -0,0 +1,36 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Polyglot", + name = "get_executable_name", + description = "Returns the executable name of a polyglot object.") +public class GetExecutableNameNode extends Node { + private @Child InteropLibrary functionsLibrary = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private @Child InteropLibrary stringsLibrary = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + Text execute(Object _this, Object function) { + try { + return Text.create(stringsLibrary.asString(functionsLibrary.getExecutableName(function))); + } catch (UnsupportedMessageException e) { + err.enter(); + Builtins builtins = Context.get(this).getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError(builtins.function(), function, "function"), this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetSourceLocationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetSourceLocationNode.java new file mode 100644 index 000000000000..538712d974ae --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/GetSourceLocationNode.java @@ -0,0 +1,36 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +import org.enso.interpreter.Language; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.Context; +import org.enso.interpreter.runtime.builtin.Builtins; +import org.enso.interpreter.runtime.data.EnsoSourceSection; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Polyglot", + name = "get_source_location", + description = "Returns the source location of a polyglot object.") +public class GetSourceLocationNode extends Node { + private @Child InteropLibrary library = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + Object execute(Object _this, Object value) { + try { + return Context.get(this) + .getEnvironment() + .asGuestValue(new EnsoSourceSection(library.getSourceLocation(value))); + } catch (UnsupportedMessageException e) { + err.enter(); + Builtins builtins = Context.get(this).getBuiltins(); + throw new PanicException( + builtins.error().makeTypeError(builtins.function(), value, "function"), this); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/HasSourceLocationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/HasSourceLocationNode.java new file mode 100644 index 000000000000..7b0175c263ae --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/interop/generic/HasSourceLocationNode.java @@ -0,0 +1,21 @@ +package org.enso.interpreter.node.expression.builtin.interop.generic; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.profiles.BranchProfile; +import org.enso.interpreter.Constants; +import org.enso.interpreter.dsl.BuiltinMethod; + +@BuiltinMethod( + type = "Polyglot", + name = "has_source_location", + description = "Checks if an object has a source location.") +public class HasSourceLocationNode extends Node { + private @Child InteropLibrary library = + InteropLibrary.getFactory().createDispatched(Constants.CacheSizes.BUILTIN_INTEROP_DISPATCH); + private final BranchProfile err = BranchProfile.create(); + + boolean execute(Object _this, Object value) { + return library.hasSourceLocation(value); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java new file mode 100644 index 000000000000..c39c11063e6b --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/GetStackTraceNode.java @@ -0,0 +1,25 @@ +package org.enso.interpreter.node.expression.builtin.runtime; + +import com.oracle.truffle.api.TruffleStackTrace; +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.error.PanicException; + +@BuiltinMethod( + type = "Runtime", + name = "primitive_get_stack_trace", + description = "Gets the current execution stacktrace.") +public class GetStackTraceNode extends Node { + Array execute(Object _this) { + var exception = new PanicException(null, this); + TruffleStackTrace.fillIn(exception); + var elements = TruffleStackTrace.getStackTrace(exception); + var ret = new Array(elements.size()); + for (int i = 0; i < elements.size(); i++) { + var element = elements.get(i); + ret.getItems()[i] = element.getGuestObject(); + } + return ret; + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java index 9a21aafe4a6c..026453fac8c9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Context.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage.Env; import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.api.nodes.Node; import org.enso.compiler.Compiler; import org.enso.compiler.PackageRepository; import org.enso.compiler.data.CompilerConfig; @@ -37,6 +38,9 @@ */ public class Context { + private static final TruffleLanguage.ContextReference REFERENCE = + TruffleLanguage.ContextReference.create(Language.class); + private final Language language; private final Env environment; private @CompilationFinal Compiler compiler; @@ -138,6 +142,15 @@ public void initialize() { pkg -> packageRepository.registerMainProjectPackage(pkg.libraryName(), pkg)); } + /** + * @param node the location of context access. Pass {@code null} if not in a node. + * @return the proper context instance for the current {@link + * com.oracle.truffle.api.TruffleContext}. + */ + public static Context get(Node node) { + return REFERENCE.get(node); + } + /** Performs eventual cleanup before the context is disposed of. */ public void shutdown() { threadManager.shutdown(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 94e3f33cebb9..169aebe6d09d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -24,6 +24,7 @@ import org.enso.interpreter.node.expression.builtin.io.PrintlnMethodGen; import org.enso.interpreter.node.expression.builtin.io.ReadlnMethodGen; import org.enso.interpreter.node.expression.builtin.runtime.GCMethodGen; +import org.enso.interpreter.node.expression.builtin.runtime.GetStackTraceMethodGen; import org.enso.interpreter.node.expression.builtin.runtime.NoInlineMethodGen; import org.enso.interpreter.node.expression.builtin.runtime.NoInlineWithArgMethodGen; import org.enso.interpreter.node.expression.builtin.state.GetStateMethodGen; @@ -157,6 +158,7 @@ public Builtins(Context context) { scope.registerMethod( runtime, "no_inline_with_arg", NoInlineWithArgMethodGen.makeFunction(language)); scope.registerMethod(runtime, "gc", GCMethodGen.makeFunction(language)); + scope.registerMethod(runtime, "primitive_get_stack_trace", GetStackTraceMethodGen.makeFunction(language)); scope.registerMethod(panic, "throw", ThrowPanicMethodGen.makeFunction(language)); scope.registerMethod(panic, "recover", RecoverPanicMethodGen.makeFunction(language)); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java index 2b11bc1303ba..633033d6578d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Polyglot.java @@ -31,6 +31,12 @@ private void createPolyglot(Language language, ModuleScope scope) { scope.registerMethod(polyglot, "get_array_size", GetArraySizeMethodGen.makeFunction(language)); scope.registerMethod( polyglot, "is_language_installed", IsLanguageInstalledMethodGen.makeFunction(language)); + scope.registerMethod( + polyglot, "get_executable_name", GetExecutableNameMethodGen.makeFunction(language)); + scope.registerMethod( + polyglot, "get_source_location", GetSourceLocationMethodGen.makeFunction(language)); + scope.registerMethod( + polyglot, "has_source_location", HasSourceLocationMethodGen.makeFunction(language)); } /** @return the atom constructor for polyglot */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java index 0f7207b51e28..b83d77d973c1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoFile.java @@ -89,6 +89,10 @@ public void delete() throws IOException { truffleFile.delete(); } + public boolean startsWith(EnsoFile parent) { + return truffleFile.startsWith(parent.truffleFile); + } + @Override public String toString() { return "(File " + truffleFile.getPath() + ")"; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSource.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSource.java new file mode 100644 index 000000000000..34a7e31b3af8 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSource.java @@ -0,0 +1,45 @@ +package org.enso.interpreter.runtime.data; + +import com.oracle.truffle.api.source.Source; +import com.oracle.truffle.api.source.SourceSection; + +/** Wrapper for exposing sources to Enso. Delegates to original methods with no behavior changes. */ +public class EnsoSource { + private final Source source; + + public EnsoSource(Source source) { + this.source = source; + } + + public String getLanguage() { + return source.getLanguage(); + } + + public String getName() { + return source.getName(); + } + + public String getPath() { + return source.getPath(); + } + + public boolean isInternal() { + return source.isInternal(); + } + + public CharSequence getCharacters() { + return source.getCharacters(); + } + + public int getLength() { + return source.getLength(); + } + + public CharSequence getCharacters(int lineNumber) { + return source.getCharacters(lineNumber); + } + + public int getLineCount() { + return source.getLineCount(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSourceSection.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSourceSection.java new file mode 100644 index 000000000000..f35bc2a081dc --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoSourceSection.java @@ -0,0 +1,67 @@ +package org.enso.interpreter.runtime.data; + +import com.oracle.truffle.api.source.SourceSection; + +/** + * Wrapper for exposing source sections in Enso. Delegates to the original methods with no behaviour + * changes. + */ +public class EnsoSourceSection { + private final SourceSection sourceSection; + + public EnsoSourceSection(SourceSection sourceSection) { + this.sourceSection = sourceSection; + } + + public int getStartLine() { + return sourceSection.getStartLine(); + } + + public int getEndLine() { + return sourceSection.getEndLine(); + } + + public int getEndColumn() { + return sourceSection.getEndColumn(); + } + + public int getCharIndex() { + return sourceSection.getCharIndex(); + } + + public int getCharLength() { + return sourceSection.getCharLength(); + } + + public int getCharEndIndex() { + return sourceSection.getCharEndIndex(); + } + + public CharSequence getCharacters() { + return sourceSection.getCharacters(); + } + + public int getStartColumn() { + return sourceSection.getStartColumn(); + } + + public boolean isAvailable() { + return sourceSection.isAvailable(); + } + + public boolean hasLines() { + return sourceSection.hasLines(); + } + + public boolean hasColumns() { + return sourceSection.hasColumns(); + } + + public boolean hasCharIndex() { + return sourceSection.hasCharIndex(); + } + + public EnsoSource getSource() { + return new EnsoSource(sourceSection.getSource()); + } +} diff --git a/engine/runtime/src/main/resources/Builtins.enso b/engine/runtime/src/main/resources/Builtins.enso index 646192604324..8f2bfcfa5916 100644 --- a/engine/runtime/src/main/resources/Builtins.enso +++ b/engine/runtime/src/main/resources/Builtins.enso @@ -493,6 +493,28 @@ type Polyglot invoke : Any -> Text -> Vector -> Any invoke target name arguments = @Builtin_Method "Polyglot.invoke" + ## ADVANCED + UNSTABLE + + Checks if `value` defines a source location. + + Source locations are typically exposed by functions, classes, sometimes + also other objects to specify their allocation sites. + has_source_location : Any -> Boolean + has_source_location value = @Builtin_Method "Polyglot.has_source_location" + + ## ADVANCED + UNSTABLE + + Gets the source location of `value`. + + Source locations are typically exposed by functions, classes, sometimes + also other objects to specify their allocation sites. + This method will throw a polyglot exception if + `Polyglot.has_source_location value` returns `False`. + get_source_location : Any -> Source_Location + get_source_location value = @Builtin_Method "Polyglot.get_source_location" + ## Utilities for working with Java polyglot objects. type Java @@ -1794,6 +1816,15 @@ type Runtime no_inline_with_arg : Any -> Any no_inline_with_arg function arg = @Builtin_Method "Runtime.no_inline_with_arg" + ## PRIVATE + + Returns a raw representation of the current execution stack trace. + You probably want `Runtime.get_stack_trace` instead. + primitive_get_stack_trace : Array + primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace" + + + ## The runtime's integrated monadic state management. type State diff --git a/test/Tests/src/Main.enso b/test/Tests/src/Main.enso index ac0009eed46c..4366578d677e 100644 --- a/test/Tests/src/Main.enso +++ b/test/Tests/src/Main.enso @@ -41,6 +41,8 @@ import project.Network.Http.Request_Spec as Http_Request_Spec import project.Network.Http_Spec import project.Network.Uri_Spec +import project.Runtime.Stack_Traces_Spec + import project.System.File_Spec import project.System.Process_Spec @@ -81,6 +83,7 @@ main = Test.Suite.run_main <| Regex_Spec.spec Runtime_Spec.spec Span_Spec.spec + Stack_Traces_Spec.spec Text_Spec.spec Time_Spec.spec Uri_Spec.spec diff --git a/test/Tests/src/Runtime/Stack_Traces_Spec.enso b/test/Tests/src/Runtime/Stack_Traces_Spec.enso new file mode 100644 index 000000000000..3cfb19d00eec --- /dev/null +++ b/test/Tests/src/Runtime/Stack_Traces_Spec.enso @@ -0,0 +1,20 @@ +from Standard.Base import all +import Standard.Test + +type My_Type + +bar = Runtime.get_stack_trace +baz = here.bar +Number.foo = here.baz +foo x = x.foo +My_Type.foo = here.foo 123 + +spec = Test.group "Stack traces" <| + Test.specify "should capture traces correctly" <| + modname = Meta.Constructor (Meta.meta here . constructor) . name + stack = My_Type.foo + names = [modname + ".bar", modname + ".baz", "Number.foo", modname + ".foo", "My_Type.foo"] + stack.take_start 5 . map .name . should_equal names + file = Enso_Project.root / 'src' / 'Runtime' / 'Stack_Traces_Spec.enso' + stack.take_start 5 . map (.source_location >> .file) . each (_.should_equal file) +