+ ds_put_format (&s, ": %s", strerror (errnum));
+
+ struct msg *m = xmalloc (sizeof *m);
+ *m = (struct msg) {
+ .category = MSG_C_GENERAL,
+ .severity = MSG_S_ERROR,
+ .text = ds_steal_cstr (&s),
+ };
+ msg_emit (m);
+}
+
+void
+msg_set_handler (const struct msg_handler *handler)
+{
+ msg_handler = *handler;
+}
+\f
+/* msg_point. */
+
+/* Takes POINT, adds to it the syntax in SYNTAX, incrementing the line number
+ for each new-line in SYNTAX and the column number for each column, and
+ returns the result. */
+struct msg_point
+msg_point_advance (struct msg_point point, struct substring syntax)
+{
+ for (;;)
+ {
+ size_t newline = ss_find_byte (syntax, '\n');
+ if (newline == SIZE_MAX)
+ break;
+ point.line++;
+ point.column = 1;
+ ss_advance (&syntax, newline + 1);
+ }
+
+ point.column += ss_utf8_count_columns (syntax);
+ return point;
+}
+\f
+/* msg_location. */
+
+void
+msg_location_uninit (struct msg_location *loc)
+{
+ if (msg_handler.lex_source_unref)
+ msg_handler.lex_source_unref (loc->src);
+ intern_unref (loc->file_name);
+}
+
+void
+msg_location_destroy (struct msg_location *loc)
+{
+ if (loc)
+ {
+ msg_location_uninit (loc);
+ free (loc);
+ }
+}
+
+static int
+msg_point_compare_3way (const struct msg_point *a, const struct msg_point *b)
+{
+ return (!a->line ? 1
+ : !b->line ? -1
+ : a->line > b->line ? 1
+ : a->line < b->line ? -1
+ : !a->column ? 1
+ : !b->column ? -1
+ : a->column > b->column ? 1
+ : a->column < b->column ? -1
+ : 0);
+}
+
+void
+msg_location_remove_columns (struct msg_location *location)
+{
+ location->start.column = 0;
+ location->end.column = 0;
+}