I would like to implement an error stack class/object. This will be helpful for addressing #76 (message for errors inside of stdlib). The reasons why this is useful are that:
pure (or maybe elemental too, I'll have to think more carefully about that) procedures, and then handle them at a higher levelpure proceduresPerhaps this should be part of #72 (standard assert and macros) or the existing stdlib_experimental_error.f90 or perhaps it should be its own module.
I'm waiting, in part, for #69 (string handling routines) to get to a point where there's some consensus, to take a crack at implementing that before proposing an implementation for this. String handling will be important for the manipulations required.
pure subroutine push(this, file_name, line, procedure_name)
!! caller calls this on line before call to callee
!! `file_name` can be set to a compile-time constant via CMake definition
!! or can use `__FILE__` macro, but this may overrun the line length
!! `line` can be passed `__LINE__` macro.
class(error_stack_t), intent(inout) :: this
character(len=*), intent(in) :: file_name
integer, intent(in) :: line
character(len=*), intent(in), optional :: procedure_name
end subroutine push
I like to let CMake ad a per-source-file definition, something like THIS_FILE that holds the relative path to the file in the source directory. It could fallback to __FILE__ when using manual makefiles. The benefit over __FILE__ is two fold:
Also, one or more arguments could be made optional. It would be wonderful if there was a way to query the name of the current scope in Fortran and if the programmer didn't need to make two separate calls, one to the callee and one push info onto the error stack. You could add file name, line number and error_stack arguments to all procedures and then the callee could push the data onto the stack as an alternative to the caller doing it.
Feedback, advice and suggestions welcome here.
pure subroutine pop(this)
!! Called before/on return to caller
!! only removes entries from the stack if no errors are signaling,
!! otherwise preserve the call stack to the error, but decrement the depth
!! so that when the error is caught the stack can be cleaned up appropriately
class(error_stack_t), intent(inout) :: this
end subroutine pop
pure subroutine signal(this, message, severity, error_type) ! Or raise?
!! Callee can signal or raise an exception and return control to caller
!! (or further up the call stack) for handling
class(error_stack_t), intent(inout) :: this
character(len=*), intent(in) :: message
integer, intent(in) :: severity
integer, intent(in), optional :: error_type
end subroutine signal
subroutine catch(this, pop) ! maybe impure elemental?
!! Do IO to tty or log as is necessary and handle calls to `error stop`
!! or other actions based on severity
class(error_stack_t), intent(inout) :: this
logical, optional :: pop !! pop stack up to this callers depth
end subroutine catch
The API needs some further thought, which is why I'd like feedback from others. In an ideal world, it would be awesome to have this act as a decorator and not have to call the push method before the caller calls the callee or not have to pass the file name and line number if the callee does the push.
Preprocessor macro expansion could automatically expand multiple actual arguments on calls to other stdlib procedure to pass in the stack object, file name and line number but seems a bit too magic for my liking.
Thanks for the proposal. Can you give an example how would this be used in practice? Does one have to call pop and push manually (or with a macro) in every function/subroutine in a project?
Shouldn't better debugging and stacktraces capability be rather built into compilers themselves?
Thanks for the proposal. Can you give an example how would this be used in practice?
I'll update the original post with an example
Does one have to call
popandpushmanually (or with a macro) in every function/subroutine in a project?
Unless someone can think of a better way to do this, then, "yes": Either the caller or callee must call pop and push starting at the depth where one wishes to catch and report errors and down into any procedures who will be raising/signalling errors.
Upon further consideration, the design with the lease typing that does not require an object oriented decorator is to pass 3 extra arguments to each procedure:
error_stack_t object__LINE__ macro as an integer literal actual argument (this will hold the line the procedure was called on)__FILE__ or a CMake defined relative path)Then the procedure can call push and pop on entry/exit and provide its own name as an optional argument. I'm going to update my original post to reflect this.
For other classes & objects that take an OOD/OOP approach you may be able to implement a decorator pattern where the classes being implemented use the error_stack_t class, probably via composition. I need to think on this particular aspect a bit more and look at other examples, as I do not have experience with decorator patterns in Fortran. The hard part is getting the file name and the line number where the procedure was called. With pre-processor macros this needs to happen in the same file/scope that the call is made.
Shouldn't better debugging and stacktraces capability be rather built into compilers themselves?
Absolutely! But I'm tired of waiting 馃槃. I should open a new issue over in the other J3 repo. In particular the following would be helpful in letting users write their own error handling, if we cannot get error handling into the language itself:
file_name(): Function to return the name of a source code file (effectively standardize the __FILE__ macro without it being a macro)line_number(): Function to return an integer of the line number of the file it was calledscope_name(): Function to return (even the mangled, namespaced) symbol definition. Or as an alternative you could have:function_name(): Function to return the name of the current functionsubroutine_name(): Function to return the name of the current subroutineOr, better still for error handling:
called_from_file(): Function to return filename where the current procedure was calledcalled_on_line(): Function to return the line number where the current procedure was calledShouldn't better debugging and stacktraces capability be rather built into compilers themselves?
Absolutely! But I'm tired of waiting
I am doing what I can. I could have been a lot further along, but I felt putting my time into stdlib and the J3 repo and getting us organized was worth getting a bit delayed.
In the meantime, here is how to get nice stacktraces from user code:
https://github.com/trilinos/Trilinos/blob/4ddae567dc74b55dfa2fb7eb27c04a1808ba2475/packages/teuchos/core/src/Teuchos_stacktrace.hpp
https://github.com/trilinos/Trilinos/blob/master/packages/teuchos/core/src/Teuchos_stacktrace.cpp
I implemented that 10 years ago. Since then, there seem to be a few other libraries that can do that:
I think they all follow basically the same approach.
I have not tested that with Fortran, only C and C++, but I think it would work.
I am doing what I can. I could have been a lot further along, but I felt putting my time into
stdliband the J3 repo and getting us organized was worth getting a bit delayed.
I completely agree! Thanks for the hard work!
I was editing my original post but lost the edits upon navigating to another link. I'll try to refine my ideas and add a usage example tonight.
I have an implementation of something that does pretty much what you're asking for here. https://gitlab.com/everythingfunctional/erloff
It uses a functional programming approach, and has some conveniences for callers to deal with errors/messages that come back. It enables subroutines to remain pure as well.
Here are a couple projects I've made use of it as well:
Let me know if you have any questions about it.
Most helpful comment
I am doing what I can. I could have been a lot further along, but I felt putting my time into
stdliband the J3 repo and getting us organized was worth getting a bit delayed.In the meantime, here is how to get nice stacktraces from user code:
https://github.com/trilinos/Trilinos/blob/4ddae567dc74b55dfa2fb7eb27c04a1808ba2475/packages/teuchos/core/src/Teuchos_stacktrace.hpp
https://github.com/trilinos/Trilinos/blob/master/packages/teuchos/core/src/Teuchos_stacktrace.cpp
I implemented that 10 years ago. Since then, there seem to be a few other libraries that can do that:
I think they all follow basically the same approach.
I have not tested that with Fortran, only C and C++, but I think it would work.