-module(glance_armstrong). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/glance_armstrong.gleam"). -export([format_diagnostic_without_span/1, format_reference_line/3, format_diagnostic_without_span_with_tips/2, format_source_diagnostic/3, format_source_diagnostic_with_tips/4, format_glance_parse_error/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Two-line terminal diagnostics: `line | source` then padded `^` + message.\n" " Used by [`schema_definition.format_parse_error`](schema_definition.html#format_parse_error).\n" ). -file("src/glance_armstrong.gleam", 130). -spec gutter_display_width(binary()) -> integer(). gutter_display_width(Gutter) -> string:length(Gutter). -file("src/glance_armstrong.gleam", 44). ?DOC(" Same visual style when no byte span exists (unknown or out-of-range).\n"). -spec format_diagnostic_without_span(binary()) -> binary(). format_diagnostic_without_span(Message) -> Gutter = <<" — | "/utf8>>, Code_line = <"/utf8>>, Pad = gutter_display_width(Gutter), Pointer = <<<<(gleam@string:repeat(<<" "/utf8>>, Pad))/binary, "^ "/utf8>>/binary, Message/binary>>, <<<>/binary, Pointer/binary>>. -file("src/glance_armstrong.gleam", 113). -spec render_gutter_block(integer(), binary(), integer(), integer(), binary()) -> binary(). render_gutter_block(Line_no, Line_text, Caret_col, Caret_width, Message) -> Num = gleam@string:pad_start( erlang:integer_to_binary(Line_no), 4, <<" "/utf8>> ), Gutter = <>, Code_line = <>, Pad_width = gutter_display_width(Gutter) + Caret_col, Spaces = gleam@string:repeat(<<" "/utf8>>, Pad_width), Carets = gleam@string:repeat(<<"^"/utf8>>, gleam@int:max(1, Caret_width)), Pointer = <<<<<>/binary, " "/utf8>>/binary, Message/binary>>, <<<>/binary, Pointer/binary>>. -file("src/glance_armstrong.gleam", 104). ?DOC(" Same gutter as [`format_source_diagnostic`](#format_source_diagnostic), for an arbitrary line (e.g. extra notes).\n"). -spec format_reference_line(integer(), binary(), binary()) -> binary(). format_reference_line(Line_no, Line_text, Message) -> W = gleam@int:min(40, gleam@int:max(1, erlang:byte_size(Line_text))), render_gutter_block(Line_no, Line_text, 0, W, Message). -file("src/glance_armstrong.gleam", 146). -spec format_one_tip_block(binary()) -> binary(). format_one_tip_block(Tip) -> case gleam@string:split(Tip, <<"\n"/utf8>>) of [] -> <<" • "/utf8>>; [First | Rest] -> Head = <<" • "/utf8, First/binary>>, gleam@list:fold(Rest, Head, fun(Acc, Line) -> case Line of <<""/utf8>> -> <>; _ -> <<<>/binary, Line/binary>> end end) end. -file("src/glance_armstrong.gleam", 141). -spec format_tip_blocks(list(binary())) -> binary(). format_tip_blocks(Tips) -> _pipe = gleam@list:map(Tips, fun format_one_tip_block/1), gleam@string:join(_pipe, <<"\n"/utf8>>). -file("src/glance_armstrong.gleam", 134). -spec format_tips_suffix(list(binary())) -> binary(). format_tips_suffix(Tips) -> case Tips of [] -> <<""/utf8>>; Lines -> <<"\n\n"/utf8, (format_tip_blocks(Lines))/binary>> end. -file("src/glance_armstrong.gleam", 63). ?DOC(" Like [`format_diagnostic_without_span`](#format_diagnostic_without_span), then bullet tips.\n"). -spec format_diagnostic_without_span_with_tips(binary(), list(binary())) -> binary(). format_diagnostic_without_span_with_tips(Message, Tips) -> <<(format_diagnostic_without_span(Message))/binary, (format_tips_suffix(Tips))/binary>>. -file("src/glance_armstrong.gleam", 161). -spec generic_param_highlight_bytes(binary()) -> gleam@option:option({integer(), integer()}). generic_param_highlight_bytes(Line_text) -> case gleam@string:split(Line_text, <<"("/utf8>>) of [Before, After_open | _] -> Open_col = erlang:byte_size(Before), case gleam@string:split(After_open, <<")"/utf8>>) of [Inside | _] -> W = (1 + erlang:byte_size(Inside)) + 1, {some, {Open_col, gleam@int:max(1, W)}}; _ -> none end; _ -> none end. -file("src/glance_armstrong.gleam", 177). -spec source_lines_with_byte_starts(binary()) -> list({integer(), binary()}). source_lines_with_byte_starts(Source) -> Parts = gleam@string:split(Source, <<"\n"/utf8>>), {Rows_rev, _} = gleam@list:fold( Parts, {[], 0}, fun(State, Line_text) -> {Acc_rows, Byte_offset} = State, {[{Byte_offset, Line_text} | Acc_rows], (Byte_offset + erlang:byte_size(Line_text)) + 1} end ), lists:reverse(Rows_rev). -file("src/glance_armstrong.gleam", 190). -spec source_indexed_lines(binary()) -> list({integer(), integer(), binary()}). source_indexed_lines(Source) -> Raw = source_lines_with_byte_starts(Source), gleam@list:index_map( Raw, fun(Pair, I) -> {Byte_start, Text} = Pair, {I + 1, Byte_start, Text} end ). -file("src/glance_armstrong.gleam", 82). -spec format_unexpected_eof_diagnostic(binary()) -> binary(). format_unexpected_eof_diagnostic(Source) -> Indexed = source_indexed_lines(Source), case gleam@list:last(Indexed) of {error, nil} -> format_diagnostic_without_span( <<"unexpected end of input when parsing Gleam (empty file)"/utf8>> ); {ok, {Line_no, _, Line_text}} -> Line_blen = erlang:byte_size(Line_text), Caret_col = gleam@int:max(0, Line_blen - 1), render_gutter_block( Line_no, Line_text, Caret_col, 1, <<"unexpected end of input when parsing Gleam"/utf8>> ) end. -file("src/glance_armstrong.gleam", 198). -spec find_line_for_byte(list({integer(), integer(), binary()}), integer()) -> {ok, {integer(), integer(), binary()}} | {error, nil}. find_line_for_byte(Indexed, Byte_pos) -> On_line = fun(Row) -> {_, Start, Text} = Row, After_start = Byte_pos >= Start, Line_past = Start + erlang:byte_size(Text), Before_end = Byte_pos =< Line_past, After_start andalso Before_end end, case gleam@list:filter(Indexed, On_line) of [Row@1 | _] -> {ok, Row@1}; [] -> case gleam@list:last(Indexed) of {ok, Row@2} -> {ok, Row@2}; {error, nil} -> {error, nil} end end. -file("src/glance_armstrong.gleam", 11). ?DOC(" `line | full line text` then a second line with spaces, carets, and `message`.\n"). -spec format_source_diagnostic(binary(), glance:span(), binary()) -> binary(). format_source_diagnostic(Source, Span, Message) -> {span, Raw_start, Raw_end} = Span, Start_byte = gleam@int:max(0, Raw_start), End_byte = gleam@int:max(Start_byte, Raw_end), Indexed = source_indexed_lines(Source), case find_line_for_byte(Indexed, Start_byte) of {error, nil} -> format_diagnostic_without_span(Message); {ok, {Line_no, Line_start, Line_text}} -> Col = Start_byte - Line_start, Line_blen = erlang:byte_size(Line_text), Col_clamped = gleam@int:min(Col, Line_blen), Max_w = gleam@int:max(1, Line_blen - Col_clamped), Raw_w = End_byte - Start_byte, {Caret_col, Caret_width} = case generic_param_highlight_bytes( Line_text ) of {some, {C, W}} -> case gleam_stdlib:contains_string( Message, <<"generic parameters"/utf8>> ) of true -> {C, W}; false -> {Col_clamped, gleam@int:max(1, gleam@int:min(Raw_w, Max_w))} end; none -> {Col_clamped, gleam@int:max(1, gleam@int:min(Raw_w, Max_w))} end, render_gutter_block( Line_no, Line_text, Caret_col, Caret_width, Message ) end. -file("src/glance_armstrong.gleam", 53). ?DOC(" Like [`format_source_diagnostic`](#format_source_diagnostic), then bullet tips (for longer help text).\n"). -spec format_source_diagnostic_with_tips( binary(), glance:span(), binary(), list(binary()) ) -> binary(). format_source_diagnostic_with_tips(Source, Span, Message, Tips) -> <<(format_source_diagnostic(Source, Span, Message))/binary, (format_tips_suffix(Tips))/binary>>. -file("src/glance_armstrong.gleam", 70). -spec format_glance_parse_error(binary(), glance:error()) -> binary(). format_glance_parse_error(Source, Error) -> case Error of unexpected_end_of_input -> format_unexpected_eof_diagnostic(Source); {unexpected_token, _, Pos} -> format_source_diagnostic( Source, {span, erlang:element(2, Pos), erlang:element(2, Pos) + 1}, <<"unexpected token"/utf8>> ) end.