Skip to content

Messages

Compilers rarely stop at the first problem. Rather than throwing, errors, warnings and informational messages are attached to the node they apply to and collected up later, so a single pass can report everything it found and processing can carry on for as long as it is useful to do so.

Adding Messages

Every node has AddError, AddWarning and AddInfo methods, each with an overload that takes a code as well as the text:

sixty.AddError("Value must be less than 55.");
sixty.AddError("E017", "Value must be less than 55.");

The three levels come from MessageLevel. AddMessage takes a level, or a prebuilt Message, which is useful when a validator builds its messages up front. Message is an immutable record with static Error, Warning and Info factory methods.

Adding a message is thread safe, so processors in a parallel pipeline stage can raise messages against the nodes they are visiting without any locking of your own.

Finding Messages

Messages can be read back from a single node or from a whole subtree. Each level has the same set of members, with Messages covering all levels at once:

Member Description
Errors The error messages on this node.
HasErrors Whether this node has any errors.
ThisAndDescendentsHaveErrors Whether this node or anything beneath it has errors.
ThisAndDescendentsWithErrors The nodes at or beneath this one that have errors.

Warnings, Infos and Messages have the equivalent members.

sixty.AddError("Value must be less than 55.");

var expressionHasErrors = expression.ThisAndDescendentsHaveErrors;  // true.
var badNodes = expression.ThisAndDescendentsWithErrors;

Note the difference between HasErrors, which looks only at the node itself, and ThisAndDescendentsHaveErrors, which walks the subtree. The latter is what a pipeline uses by default to decide whether to continue to the next stage.

Formatting Messages

MessageFormatter turns the messages in a tree into strings ready for output. FormatErrors returns just the errors; Format takes a MessageLevel for a single level, or no level at all to return every message grouped by level, errors first.

foreach (var error in MessageFormatter.FormatErrors(expression))
{
    Console.Error.WriteLine(error);
}

By default only the level, code and text are written:

Error: Value must be less than 55.

Pass MessageFormatterOptions to include the source position of the node the message came from. Four combinations are available as static instances — Default, PrefixOnly, HighlightOnly and PrefixAndHighlight:

var errors = MessageFormatter.FormatErrors(expression, MessageFormatterOptions.PrefixAndHighlight);
MySource.code (1, 6): Error: Value must be less than 55.
50 + 60
     --

The prefix is the position's ToString, which for a text file is the file name plus one based line and column numbers, matching the format the C# compiler uses. The highlight repeats the source line with the position underlined, and preserves any tabs in the leading part of the line so that the underline stays aligned.

Nodes with no source position — those built programmatically rather than parsed — format as just the message. Highlighting is only available for positions in text files; a BinaryFilePosition will still be used as a prefix but has no source line to show.