[m-rev.] for review: structured terms in error messages
Julien Fischer
jfischer at opturion.com
Fri Oct 14 17:48:45 AEDT 2022
On Fri, 14 Oct 2022, Zoltan Somogyi wrote:
> Print structured terms on one line if they fit.
>
> The compiler's diagnostic outputs often contain terms, representing
> either terms in the program, or other entities such as types or modes.
> These can be printed either as
>
> f(a, b)
>
> or as
>
> f(
> a,
> b
> )
...
> diff --git a/compiler/error_spec.m b/compiler/error_spec.m
> index aabb8612a..5bbf18db3 100644
> --- a/compiler/error_spec.m
> +++ b/compiler/error_spec.m
> @@ -424,6 +424,55 @@
> ; pragma_decl(string)
> % As above, but prefix the string with ":- pragma ".
>
> + ; left_paren_maybe_nl_inc(string, lp_piece_kind)
> + ; maybe_nl_dec_right_paren(string, rp_piece_kind)
> + % These two pieces are intended to help implement messages
> + % that should be formatted to look like either
> + %
> + % aaa(bbb, ccc)
> + %
> + % if there is space for this on the current line, or to look like
Delete "for this".
> + %
> + % aaa(
> + % bbb,
> + % ccc
> + % )
> + %
> + % if there isn't.
> + %
> + % The piece sequence that would yield the above would be
> + %
> + % fixed("aaa")
> + % left_paren_maybe_nl_inc("(", lp_suffix)
> + % fixed("bbb"),
> + % suffix(","),
> + % nl,
> + % fixed("ccc"),
> + % maybe_nl_dec_right_paren(")", rp_plain)
> + %
...
> diff --git a/compiler/write_error_spec.m b/compiler/write_error_spec.m
> index 79de042dc..1e55ff878 100644
> --- a/compiler/write_error_spec.m
> +++ b/compiler/write_error_spec.m
> @@ -525,7 +525,17 @@ do_write_error_pieces(Stream, Globals, MaybeContext, TreatAsFirst, FixedIndent,
> ),
> FirstIndent = (if TreatAsFirst = treat_as_first then 0 else 1),
> divide_paragraphs_into_lines(MaybeAvailLen, TreatAsFirst,
> - FirstIndent, Paragraphs, Lines),
> + FirstIndent, Paragraphs, Lines0),
> + try_to_join_lp_to_rp_lines(Lines0, Lines),
> + trace [compile_time(flag("debug_try_join_lp_to_rp")), io(!TIO)] (
> + io.stderr_stream(StdErr, !TIO),
> + io.write_string(StdErr, "START\n", !TIO),
> + list.foldl(io.write_line(StdErr), Paragraphs, !TIO),
> + list.foldl(io.write_line(StdErr), Lines0, !TIO),
> + io.write_string(StdErr, "JOINED\n", !TIO),
> + list.foldl(io.write_line(StdErr), Lines, !TIO),
> + io.write_string(StdErr, "END\n", !TIO)
> + ),
> write_msg_lines(Stream, PrefixStr, Lines, !IO)
> )
> ).
> @@ -568,15 +578,13 @@ write_msg_lines(Stream, PrefixStr, [Line | Lines], !IO) :-
> io::di, io::uo) is det.
>
> write_msg_line(Stream, PrefixStr, Line, !IO) :-
> - Line = error_line(_MaybeAvail, LineIndent, LineWords, _LineWordsLen),
> - (
> - LineWords = [],
> + Line = error_line(_MaybeAvail, LineIndent, LineWordsStr, _LineWordsLen,
> + _LineParen),
> + ( if LineWordsStr = "" then
> % Don't bother to print out out indents that are followed by nothing.
Double "out".
...
> @@ -946,14 +1005,36 @@ find_word_end(String, Cur, WordEnd) :-
> % to get the number of spaces this turns into.
> line_indent_level :: int,
>
> - % The words on the line.
> - line_words :: list(string),
> + % The words on the line as a single string, with one space
> + % between each pair of words.
> + line_words_str :: string,
>
> - % Total number of characters in the words, including
> - % the spaces between words.
> + % The length of the line_words_str field.
> %
> % This field is meaningful only if maybe_avail_len is yes(...).
> - line_words_len :: int
> + line_words_len :: int,
> +
> + % If this field is paren_none, this a normal line.
> + % If this field is paren_lp_end, this line ends a left
> + % parenthesis.
> + % If this field is paren_end_rp, the *next* line *starts*
> + % with a right parenthesis.
> + %
> + % We use these fields to try to put everything
> + %
> + % - in a paren_lp_end line,
> + % - in zero or more paren_none lines,
> + % - in a paren_end_rp line, and
> + % - the very next line (which starts with the rp)
> + %
> + % into a single line, if there is room. (Note that the code
> + % that creates a paren_end_rp line will also ensure that
> + % there *will be* a nexy line.)
s/nexy/next/
> + %
> + % It is ok for some of the lines to be squashed together
> + % to result from earlier squash operations, on inner
> + % parentheses.
> + line_paren :: paren_status
> ).
>
> % Groups the words in the given paragraphs into lines. The first line
...
> @@ -1062,6 +1159,198 @@ get_later_words(Avail, [Word | Words], CurLen, FinalLen,
> RestWords = [Word | Words]s
...
> +:- pred find_matching_rp_and_maybe_join(error_line::in,
> + list(error_line)::in, list(error_line)::out, list(error_line)::out) is det.
> +
> +find_matching_rp_and_maybe_join(LPLine, TailLines0, ReplacementLines,
> + LeftOverLines) :-
> + LPLine = error_line(MaybeAvailLen, LPIndent, LPLineWordsStr,
> + LPLineWordsLen, LPParen),
> + expect(unify(LPParen, paren_lp_end), $pred, "LPParen != paren_lp_end"),
> + ( if
> + find_matching_rp(TailLines0, cord.init, MidLinesCord, 0, MidLinesLen,
> + RPLine, LeftOverLinesPrime)
> + then
> + RPLine = error_line(_, _RPIndent, RPLineWordsStr, RPLineWordsLen,
> + RPParen),
> + MidLines = cord.list(MidLinesCord),
> + list.length(MidLines, NumMidLines),
> + MidLineSpaces = (if NumMidLines = 0 then 0 else NumMidLines - 1),
> + TotalLpRpLen =
> + LPLineWordsLen + MidLinesLen + MidLineSpaces + RPLineWordsLen,
> + ChunkLines = [LPLine | MidLines] ++ [RPLine],
> + ( if
> + (
> + MaybeAvailLen = no
> + ;
> + MaybeAvailLen = yes(AvailLen),
> + LPIndent * indent_increment + TotalLpRpLen =< AvailLen
> + )
> + then
> + % We insert spaces
> + % - between the middle lines, but
> + % - not after the ( in LPLine,
> + % - nor before the ) in RPLine.
> + MidLineStrs = list.map((func(L) = L ^ line_words_str), MidLines),
> + MidSpaceLinesStr = string.join_list(" ", MidLineStrs),
> + ReplacementLineStr =
> + LPLineWordsStr ++ MidSpaceLinesStr ++ RPLineWordsStr,
> + string.count_codepoints(ReplacementLineStr, ReplacementLineStrLen),
> + expect(unify(TotalLpRpLen, ReplacementLineStrLen), $pred,
> + "TotalLpRpLen != ReplacementLineStrLen"),
> + ReplacementLine = error_line(MaybeAvailLen, LPIndent,
> + ReplacementLineStr, TotalLpRpLen, RPParen),
> + ReplacementLines = [ReplacementLine]
> + else
> + ReplacementLines = ChunkLines
> + ),
> + LeftOverLines = LeftOverLinesPrime
> + else
> + % We can't find the rest of the patterm so we can't optimize anything.
s/patterm/pattern.
...
> +:- pred find_matching_rp(list(error_line)::in,
> + cord(error_line)::in, cord(error_line)::out,
> + int::in, int::out, error_line::out, list(error_line)::out) is semidet.
> +
> +find_matching_rp([], !MidLinesCord, !MidLinesLen, _, _) :-
> + fail.
> +find_matching_rp([HeadLine0 | TailLines0], !MidLinesCord, !MidLinesLen,
> + RPLine, LeftOverLines) :-
> + HeadLine0 = error_line(_HeadMaybeAvailLen, _HeadIndent, _HeadLineWordsStr,
> + HeadLineWordsLen, HeadParen),
> + (
> + HeadParen = paren_none,
> + cord.snoc(HeadLine0, !MidLinesCord),
> + !:MidLinesLen = !.MidLinesLen + HeadLineWordsLen,
> + find_matching_rp(TailLines0, !MidLinesCord, !MidLinesLen, RPLine,
> + LeftOverLines)
> + ;
> + HeadParen = paren_end_rp,
> + % The right parenthesis is at the start of the *next* line.
> + (
> + TailLines0 = [],
> + % There is no right paren; the original left paren is unbalanced.
> + fail
> + ;
> + TailLines0 = [RPLine | TailTailLines0],
> + cord.snoc(HeadLine0, !MidLinesCord),
> + !:MidLinesLen = !.MidLinesLen + HeadLineWordsLen,
> + LeftOverLines = TailTailLines0
> + )
> + ;
> + HeadParen = paren_lp_end,
> + find_matching_rp_and_maybe_join(HeadLine0, TailLines0,
> + ReplacementLines, AfterRpLines),
> + (
> + ReplacementLines = [],
> + % Getting here means that the text has _HeadLineWordsStr
> + % has disappeared, which is a bug.
> + unexpected($pred, "ReplacementLines = []")
> + ;
> + ReplacementLines = [HeadReplacementLine | TailReplacementLines],
> + (
> + TailReplacementLines = [],
> + % We replaced the inner pattern with a single line.
> + % Try to fit this single line into the larger pattern.
> + find_matching_rp([HeadReplacementLine | AfterRpLines],
> + !MidLinesCord, !MidLinesLen, RPLine, LeftOverLines)
> + ;
> + TailReplacementLines = [_ | _],
> + % We could't optimize the pattern starting at HeadLine0,
s/could't/couldn't/
> + % which is nested inside the larger pattern our ancestor
> + % find_matching_rp_and_maybe_join call was trying to
> + % optimize. But if even just the lines in this inner pattern
> + % are too long to fit on our caller's available space,
> + % then the large pattern that includes those lines *and *more*
> + % will also be too large to fit into that same space.
> + %
> + % NOTE: This assumes that the inner pattern is at least as
> + % indented as our caller's paren_lp_end line. As far as I (zs)
> + % know, this is true for all our error messages.
> + fail
> + )
> + )
> + ).
That looks fine otherwise.
Julien.
More information about the reviews
mailing list