+ 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;
+}
+
+void
+msg_location_merge (struct msg_location **dstp, const struct msg_location *src)
+{
+ struct msg_location *dst = *dstp;
+ if (!dst)
+ {
+ *dstp = msg_location_dup (src);
+ return;
+ }
+
+ if (dst->file_name != src->file_name)
+ {
+ /* Failure. */
+ return;
+ }
+ if (msg_point_compare_3way (&dst->start, &src->start) > 0)
+ dst->start = src->start;
+ if (msg_point_compare_3way (&dst->end, &src->end) < 0)
+ dst->end = src->end;
+}