Fortran-202X-Proposals icon indicating copy to clipboard operation
Fortran-202X-Proposals copied to clipboard

Proposal: Exception Handling

Open jacobwilliams opened this issue 8 years ago • 7 comments

jacobwilliams avatar Jul 06 '17 01:07 jacobwilliams

try: except: would be nice... right now, my best implementation at this is a universal base object with an error stack... mixed with preprocessor macros to pull out file/line numbers. This leaves a lot to be desired.

zbeekman avatar Jul 21 '17 21:07 zbeekman

@zbeekman

try: except: would be nice... right now, my best implementation at this is a universal base object with an error stack... mixed with preprocessor macros to pull out file/line numbers. This leaves a lot to be desired.

A snippet example of your approach is appreciated. I have tried something similar, but I have never been satisfied... I like to see your approach.

Cheers

szaghi avatar Jul 22 '17 04:07 szaghi

I think this proposal needs more clearly defined requirements. What would the exceptions look like?

Would it be some kind of intrinsic exception type that could be defined and filled in by the client code and returned as argument from subroutines?

Or perhaps something more Pythonic like try/except as Zaak suggested? Honestly I can't imagine how this would work in Fortran. AFAIK statements in Fortran either just work, or they crash the program alltogether.

The best I can imagine is a standard-defined derived type that could be used by the user to set error code, message, level of severity, and raise method.

@zbeekman Any chance for a sneak peek into your solution? The best I ever did was an integer return code from subroutines and testing its value upon subroutine return.

milancurcic avatar Jul 26 '17 21:07 milancurcic

Also keep in mind the IEEE_EXCEPTIONS thing that's already in there.

jacobwilliams avatar Jul 27 '17 15:07 jacobwilliams

Here are some thoughts on how it could be done which would be minimally invasive to the language. They would require both a new module and some new keywords. There would be a module with a base exception type (and optionally some intrinsic subclasses). This would have a contructor function new_exception(character(len=*) :: message). This type would have a non-overrideable type-bound procedure called get_message() which returns the message passed on construction. The type would contain backtrace information from where it was thrown. The exception type could be subclassed by users if desired.

There would be the new keywords failable, throw, try, and check. failable would be an attribute for procedure declaration indicating that exceptions may occur. Only procedures with explicit interfaces may have this attribute. A procedure which calls a failable procedure is implicitly failable itself and thus must also have an explicit interface. On the backend (which would not be specified by the standard, of course), these procedures would contain an additional argument which is a polymorphic class exception.

During the routine, a user can call throw <<class(exception)>> which would assign the thrown error to the exception argument, then return. If a user wants to catch an exception, they would use the following block:

try
    call some_failable_subroutine()
except (a)
  class is(io_exception)
    print*, 'IO', a%get_message()
  type is(os_exception)
    print*, 'OS', a%get_message()
  class default
    print*, 'Some other sort of exception occurred'
end try

if a failable procedure is called outside of a try block, the effectively the compiler would insert

try
    call some_failable_subroutine_called_outside_of_try_block()
except (a)
  class default
    throw a
end try

If a throw command is called in the main program (including in the sense that an exception is uncaught) then this would produce a call to error stop (exception%get_message()). I'm pretty sure there are plans to allow error stop to be called with non-constant arguments, so that won't be a problem. The difference would be it would print the backtrace contained in the exception, rather than that for where error stop is effectively called.

Intrinsic procedures and operations may only produce exceptions of their own when called from the main program or a context with an explicit interface (needed so that the routine can be made implicitly failable). In those situations, however, it is processor dependent whether they do, indeed produce exceptions. If we want to go further, we could add a non_failable attribute which forbids a procedure from calling any failable procedures or intrinsic operations from throwing an exception within them.

Alternatively, rather than a procedure attribute, we could use something more like the result keyword. This could be throws(<list of exception types which may be thrown>).

Unfortunately, none of this is particularly compatible with how ieee_exceptions work, but that's because I don't much like them.

This is all just thinking off the top of my head, so there may be obvious problems or things which aren't clear. I just figured I'd put it out there. Thoughts?

cmacmackin avatar Jul 27 '17 16:07 cmacmackin

@milancurcic heh, it's not pretty... I'll try to find the code, or reproduce the underlying principle skeletally from memory.

This is the gist of it: You maintain a call stack explicitly. Define an "error stack" base object, with the following data components:

  • error_raised logical component which defaults to false
  • error_message which is empty character string by default
  • file_line a character string to hold the file & line number where the procedure was called (you could have two strings here or a string and an int, doesn't matter)
  • procedure_name a character string for the procedure name

Now every time you call a procedure the procedure requires an additional dummy argument which can be expanded by macros with most preprocessors/compilers. Something like:

call my_sub(arg1, arg2 ... , file_line=__FILE__//':'//__LINE__)

The __FILE__ macro often causes > 132 characters on a line, so it's best to isolate it on it's own line and/or tell the compiler not to complain about long lines

The first thing any procedure does upon entering is push it's name and file/line onto the call stack. If it encounters an error it can call raise and return control to the caller if an exception is found, or it can pop it's entry off the stack right before returning if no error was encountered. Then the caller always checks the call stack on return from the callee; if no exception was raised, it continues on, etc.

The exception object can either be explicitly declared and passed around, or it can be the passed argument of any method that extends the error type. One must be careful though, since you typically want a singleton (or one error stack per image/MPI rank/thread etc.)...

At any rate it's not the most elegant solution. I can't find my original code, and am working on posting something new, time permitting.

The one benefit is that it can be used in pure procedures, because you pass the object in and out explicitly, and the push, pop, raise and check methods can all be made pure.

zbeekman avatar Jul 27 '17 19:07 zbeekman

Hi @all,

Sorry I'm not following too much this threads ... :sweat:

I've a dummy-project to play with an implementation of "exceptions" in Fortran https://github.com/victorsndvg/ForEx .

From my point of view, the bigger problem here is that Fortran does not allow non-local jumps (http://publications.gbdirect.co.uk/c_book/chapter9/nonlocal_jumps.html). Without non-local jumps, you can control exceptions on "vertical subroutine hierarchies", but not if there are several try-catch blocks at the same level, you are not able to control the flow while catching exceptions.

In addition to what @cmacmackin said, I think a finally reserved word is mandatory. E.g. We need to explicitly deallocate allocated objects if a procedure throws an exception.

What do you think?

victorsndvg avatar Oct 17 '17 09:10 victorsndvg