Max Motovilov wrote: First of all, thanks for your suggestions!
What exactly do you expect in this context?
Well, like I said, it is typical for a C/C++ preprocessor to allow macro re-definitions in violation of the standard, and there's likely quite a bit of [sloppy] code that won't be processed correctly otherwise. I am not 100% sure what is the best paradigm for handling this situation in the context of Wave.
One of the reasons Wave was written was exactly this sloppyness of existing preprocessors, which makes it nearly impossible to rely on the Standard and beeing portable at the same time.
It would, of course, be nice to have a generic mechanism for rolling back to pre-error state of the lexer/preprocessor system while providing sufficient information to enact recovery (i.e. providing the name of a failed macro so that user's code can call undefine_macro() and re-process the failed definition). I imagine that may be quite expensive, however, as it might entail having to save the state in the beginning of any operation that may consume tokens from the input without returning them to user -- potentially any operation!
Yes, this involves deep copying of the whole context object which may turn out to be a costly operation not good as a 'preventive' error handling scheme. Error recovery itself may be costly, even very costly, because in case of an error time doesn't matter so much anymore, but this should not have heavy inpact on the normal operation.
Perhaps a cheaper way is adding another hook to the context policy? For example,
template<typename X> bool pre_exception( const X& err );
that will have a chance to recover and return true or return false and allow the exception to proceed. Again, I am not sure just how easy it would be to incorporate such a policy hook into the existing code. In simple cases, the sequence of:
action; if( test ) throw some_exception( parameters );
could be rewritten as
do { action; } while( !test && checked_throw( some_exception( parameters ) ) );
where
template <typename X> bool checked_throw( const X& x ) { if( !pre_exception(x) ) throw x; return false; }
but it requires "action" to leave the system state unchanged whenever "test" is true at the end of it.
This solution seems to be cheaper at the first look, but it may turn out to be very costly afterwards, because Wave has to provide the user supplied function with all the information it may need to decide, what todo.
This may be easy to achieve in some cases and hard in others;
Yeah, that's not always possible.
aside from that, enough information has to be delivered to pre_exception() to undertake sensible actions which in all likelihood will require creating a separate exception class for each type of error or at least many of them.
Wouldn't make a lot of different exception types the overall error handling a lot more difficult for the average user?
After all, the easiest course may be to provide a default error recovery option for most errors.
Default error recovery is bad for two reasons: - first of all it leads towards more weakness of Wave with regard to the Standard as it makes it more forgiving. As it was pointed out already, Wave's main focus is Standards compliance and it should report as much error information as possible to the user. - second, what seems to be a good default in one context may be a very bad idea in others, i.e. undefining the first definition of a macro in case of a redefinition and using the second definition provided may be useful for you, others may want to keep the first definition and disregard the second one. But I agree with you that Wave as a library should give the user the possibility to recover from errors in a way it is appropriate to him/her.
Which may not be all that bad: for macro re-definition, use the new definition; for #undefine unknown macro -- ignore etc. I guess your is_recoverable() mechanics is good enough for that already, as long as the default recovery actions are reasonable.
The question is: what is reasonable?
Wave is a fairly specialized library and natural expectation is that it would be used by either C/C++ compilers or other applications working with C or C++ code in a way similar to a compiler (in my case -- a code instrumentation tool that attempts partial parsing of class definitions).
AFAIU compilers normally do not recover from errors in the sense that they try to correct them. And that's for a good reason: compilers normally are not to read the programmers minds, so they don't have enough information to do the right thing :-P Compilers simply report errors and try to continue from this point on leaving the required corrections to the author of the source code.
Thus any such code should benefit from handling preprocessor errors in the same exact way a typical C++ compiler would handle them.
That's correct, but normally doesn't involve any default error correction as you suggested to do above. Overall, I'm still not sure how to solve all these issues and I guess we will have to take smaller steps first to get a feeling how to design a generic error recovery scheme suitable for most of the needs a user of Wave might have. Thanks again! Regards Hartmut