Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Exception Handling #6

Open
jacobwilliams opened this issue Jul 6, 2017 · 7 comments
Open

Proposal: Exception Handling #6

jacobwilliams opened this issue Jul 6, 2017 · 7 comments

Comments

@jacobwilliams
Copy link
Member

No description provided.

@zbeekman
Copy link
Member

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.

@szaghi
Copy link
Member

szaghi commented Jul 22, 2017

@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

@milancurcic
Copy link

milancurcic commented Jul 26, 2017

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.

@jacobwilliams
Copy link
Member Author

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

@cmacmackin
Copy link

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?

@zbeekman
Copy link
Member

zbeekman commented Jul 27, 2017

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

@victorsndvg
Copy link
Member

Hi @ALL,

Sorry I'm not following too much this threads ... 😓

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants