# Error reporting and Errors reference

When building some kind of a language, the complicated errors often need to be reported. Often there are many errors at a time, or an error that needs to be tracked through multiple nested layers. And then these error messages need to be nicely printed, with the indentation by nested layers. Think of the errors from a &Cpp; compiler. Triceps is a kind of a language, so it has a class to represent such errors. It hasn't propagated to the Perl layer yet and is available only in the &Cpp; API.

The class is Errors, defined in

    common/Errors.h

, and inheriting from Starget (for single-threaded reference counting). The references to it are used so often, that

    Autoref

is defined to have its own name

    Erref

(yes, that's double

    r

, not triple), as well as a couple of helper methods on it.

Triceps also has the exceptions (more on them in &xrsp;) which also contain the Errors objects inside them.

In general an Errors object contains messages, not all of which have to be errors. Some might be warnings. But in practice is has turned out that without a special dedicated compile stage it's hard to report the warnings. Even when there is a special compile stage, and the code gets compiled before it runs, as it was in Aleri, with the warnings written to a log file, still people rarely pay attention to the warnings. You would not believe, how may people would be calling support while the source of their problem is clearly described in the warnings in the log file. Even in C/&Cpp; it's difficult to pay attention to the warnings. I better like the approach of a separate lint tool for this purpose: at least when you run it, you're definitely looking for warnings.

Because of this, the current Triceps approach is to not have warnings. If something looks possibly right but suspicious, report it as an error but provide an option to override that error (and tell about that option in the error message).

In general, the Errors are built of two kinds of information:

- the error messages;
- the nested Errors reports.

More exactly, an Errors contains a sequence of elements, each of which may contain a string, a nested Errors object, or both. When both, the idea is that the string gives a high-level description and the location of the error in the high-level object while the nested Errors digs into the further detail. The string gets printed before the nested Errors. The nested Errors get printed with an indentation. The indentation gets added only when the errors get printed, i.e. the top-level Errors object gets converted to a string. Until then the elements may be nested every which way without incurring any extra overhead.

Obviously, you must not try to nest an Errors object inside itself, directly or indirectly. Not only will it create a memory reference cycle, but also an endless recursion when printing.

The most typical way to construct an Errors object is with the functions taking the printf-like arguments. They have been a relatively recent addition, so there is plenty of code in Triceps that uses the older ways, but this is the latest and greatest, and recommended for all the future use.

The consistent theme in its usage is check if the Errors reference (Erref) is NULL, if it is, allocate a new Errors, and then add a formatted error message to it. So the printf-like methods are defined not on Errors but on Erref. They check if the Erref object is NULL, allocate a new Errors object into it if needed, and then format the arguments. The simplest one is:

    void f(const char *fmt, ...);

It adds a simple formatted message, always marked as an error. It is used like this:

    Erref e; // initially NULL by default
    ...
    e.f("a message with integer %d", n);

The message may be multi-line, it will be split appropriately, to make it all render with the correct indenting level. If the error message contains a \n at the end, that \n will be discarded (since the line feed after each line is implicit).

The next one is used to build the nested error messages:

    bool fAppend(Autoref clde, const char *fmt, ...);

It first checks that the child errors object is not NULL and contains an error, and if it does then it goes through the dance of allocating a new Errors object if needed, appending the formatted message and the child errors. The message goes before the child errors, unlike the method signature. So you can use it blindly like this to do the right thing:

    Erref checkSubObject(int idx);

    Erref checkObject()
    {
    	Erref err; // initially NULL by default
    	...
    	for (int i = 0; i < sz; i++)
    		err.fAppend(checkSubObject(i),  "error in the sub-object %d:", i);
    	...
    	return err; // NULL on success, Errors reference on error
    }

No additional ifs are necessary to check the result of

    checkSubObject()

,

    fAppend()

contains this if inside it. Similar to

    f()

, the error message may be multi-line.

The more basic way to create an Errors is by calling one of the constructors:

    Errors(bool e = false);
    Errors(const char *msg);
    Errors(const string &msg);
    Errors(const string &msg, Autoref clde);

For example:

    Erref err = Errors("error message");

The default constructor creates an empty object, which then needs to be filled with the messages (or not, returning an empty Errors object is also possible, meaning that no errors were found, though returning NULL is more efficient). The argument

    e

is an indicator than it contains an actual error. By default it's unset but will be set when an error message is added, or whan a nested Errors object with an error in it is added.

The rest of the above are the convenience constructors that make one-off Errors from one element (though of course more elements can be added afterwards). In all of them the error flag is always set, and the multi-line messages are properly handled.

After an Errors object is constructed, more elements can be added to it by one of the following methods.

    void appendMsg(bool e, const string &msg);

The argument

    e

shows whether the message is an error message (and it will set the error flag in Errors), otherwise just a warning. There are no special per-message indications whether it's an error or warning, the Errors keeps just a single flag per object, and the messages are eventually printed as-is. So if you want the user to recognize some message as a warning, prefix its text with something like warning:.

The argument

    msg

must be a single-line message, don't use a \n in it! If you want to append multiple lines, call

    appendMsg()

once per line, or use

    appendMultiline()

to append the whole multi-line message.

    void appendMultiline(bool e, const string &msg);

The same as

    appendMsg()

, only it will safely break a multi-line message into multiple single-liners and will ignore the \n at the end. Inside the Errors object each line will become a separate message.

    bool append(const string &msg, Autoref clde);

The

    append()

method is somewhat like

    Erref::fAppend()

but different, because they represent different ages of the API. When

    append()

was written, the warnings were considered important, and a common way to return a no-error condition was by returning an Errors object with no contents. When

    fAppend()

was written, I've pretty much given up on the use of warnings, and the preferred way to return the no-error condition had become by returning NULL.

In

    append()

the following situations are possible:

- The argument

      clde

  is NULL or empty (that is contains no messages nor sub-objects), and the error flag in it is false. In this case

      append()

  does nothing, and the message

      msg

  is thrown away.
- The argument

      clde

  is not empty but the error flag is false. Both

      msg

  and child object will be appended to the Errors.
- The argument

      clde

  is not empty and the error flag is true. Both

      msg

  and child object will be appended to the Errors, and the error flag will be set in it.
- The argument

      clde

  is empty and the error flag is true. The

      msg

  will be appended to the Errors and the error flag will be set in it. The child object will be thrown away (it's reference-counted, so it will be freed when the last reference is gone).

Just like

    appendMsg()

, the argument

    msg

must be a single-line message.

The return value of

    append()

will be true if the child element contained any data in it or an error indication flag. This can be used together with another method

    void replaceMsg(const string &msg);

to add a complex high-level description if a child element has reported an error. By now it has become old-fashioned, and

    Erref::fAppend()

is a better choice. Before

    fAppend()

came along, the typical way to create an error message was by calling

    strprintf()

that formats a printf-like string into a &Cpp; string object. And it's kind of expensive to call

    strprintf()

in any case, even if the child returned no error. So the optimization was to call

    append()

with an empty string, and if it happened to actually append something, replace that empty message with the real one. As the following pattern shows:

    Erref clde = someThing(...);
    if (e.append("", clde)) {
        // ... generate msg in some complicated way
        e.replaceMsg(strprintf(...));
    }

The

    replaceMsg()

replaces the string portion of the last element, which owns the last added child error.

This pattern can still be used if the error message is truly difficult to generate, but usually

    fAppend()

is a better choice.

It's also possible to include the contents of another Errors directly, without nesting it:

    bool absorb(Autoref clde);

The return value has the same meaning as with

    append()

(but

    replaceMsg()

can not be used with this method for anything reasonable). The argument

    clde

may be NULL. The error flag propagates to the parent as usual.

Finally, an Errors object can be cleared to its empty state:

    void clear();

Usually the clearing of an Errors object is not such a good idea. Remember, they are built into a tree. So if you later modify an Errors object, that would also modify what is seen by all the objects that refer to it. The proper approach is to treat an Errors object as append-only while it's being built. And after it has been completed and returned, treat it as read-only.

To get the number of elements in Errors, use

    size_t size() const;

Remember, a child object with its associated top-level error message counts as a single element, not two of them.

However the more typical methods are:

    // In Erref:
    bool isEmpty();
    bool hasError();

    // In Errors:
    bool containsNothing();
    bool containsError();

They check whether there is nothing at all or whether there is an error. The methods of Erref have the special convenience that they can be called on NULL references. Quite a few Triceps methods return a NULL Erref if there was no error. In the following example, even if

    er

is NULL, these calls are still safe and officially supported. But not the methods of Errors, that are named differently to make sure that they are not used by mistake. In the previous versions of Triceps, the methods of Errors had this convenience but the newer &Cpp; compilers disallow calling methods on the NULL pointers.

    Erref er = NULL;
    er.isEmpty();
    er.hasError();
    parent_er->append(msg, er);
    parent_er->absorb(er);
    parent_er.fAppend(er, msg);

The data gets extracted from Erref by converting it to a string, either appending to an existing string, or creating a new one:

    void printTo(string &res, const string &indent = "", const string &subindent = "  ");
    string print(const string &indent = "", const string &subindent = "  ");

The

    res

argument provides the result string to append the error messages to.

    print()

works by constructing a new string internally, calling

    printTo()

on it, and then returning it, so

    print()

is more expensive than

    printTo()

.

The messages will be separated by the newline characters \n, with the proper indentation after each of these characters. The

    indent

argument specifies the initial indentation,

    subindent

the additional indentation for each level.

The very first line will have nothing prepended to it, it's assumed that you've already done that indentation manually, or maybe you're appending to the end of some ongoing line. But all the following top-level lines will have the

    indent

text following the \n. The nested error lines will have the concatenation of

    indent

and one copy of

    subindent

in front of them after \n, the second level of nesting will have an

    indent

and two copies of

    subindent

, and so on.

All the lines are terminated by \n (except if the Errors object is empty, then nothing will be appended).

Note that the constant

    NOINDENT

has no special effect on the printing of Errors (unlike the printing of types), it's just treated here as an empty string.
error handling in &Cpp;
Errors
Erref
Autoref
Aleri
Errors multi-line
NOINDENT