-module(eyg@parser@location). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/eyg/parser/location.gleam"). -export([is_empty/1, source_context/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( " Render source-line context with carets under a span.\n" "\n" " The same renderer is used for parse errors (single position widened into\n" " a one-character span) and runtime errors (whole-expression spans). The\n" " interpreter does not depend on this module — the CLI calls it after\n" " composing the interpreter's description and hint.\n" ). -file("src/eyg/parser/location.gleam", 18). ?DOC(" Returns true when a span carries no location information.\n"). -spec is_empty({integer(), integer()}) -> boolean(). is_empty(Span) -> Span =:= {0, 0}. -file("src/eyg/parser/location.gleam", 73). -spec render_line(integer(), binary(), integer(), integer()) -> binary(). render_line(Line_num, Line, Col, Width) -> Num_str = erlang:integer_to_binary(Line_num), Gutter = <<<<" "/utf8, Num_str/binary>>/binary, " | "/utf8>>, Blank_gutter = gleam@string:repeat(<<" "/utf8>>, string:length(Gutter)), <<<<<<<<<>/binary, "\n"/utf8>>/binary, Blank_gutter/binary>>/binary, (gleam@string:repeat(<<" "/utf8>>, Col))/binary>>/binary, (gleam@string:repeat(<<"^"/utf8>>, Width))/binary>>. -file("src/eyg/parser/location.gleam", 36). -spec do_render( list(binary()), integer(), integer(), integer(), integer(), list(binary()) ) -> list(binary()). do_render(Lines, Start, End, Line_num, Offset, Acc) -> case Lines of [] -> lists:reverse(Acc); [Line | Rest] -> Line_len = erlang:byte_size(Line), Line_end = Offset + Line_len, Line_starts_inside = (Start =< Line_end) andalso (End >= Offset), case {Line_starts_inside, Start > Line_end} of {_, true} -> do_render(Rest, Start, End, Line_num + 1, Line_end + 1, Acc); {false, false} -> Acc; {true, false} -> Caret_start = gleam@int:max(0, Start - Offset), Caret_end = gleam@int:min(Line_len, End - Offset), Width = case Start =:= End of true -> 1; false -> gleam@int:max(1, Caret_end - Caret_start) end, Rendered = render_line(Line_num, Line, Caret_start, Width), Acc@1 = [Rendered | Acc], do_render( Rest, Start, End, Line_num + 1, Line_end + 1, Acc@1 ) end end. -file("src/eyg/parser/location.gleam", 29). ?DOC( " Render the source line(s) containing `span` with carets underneath.\n" "\n" " - A zero-width span (`#(p, p)`) renders a single `^` at column `p`.\n" " - A single-line span renders `^` characters spanning the range.\n" " - A multi-line span underlines from the start column to the end of the\n" " first line, then renders subsequent lines (up to and including the line\n" " containing `span.1`) with a row of `^`s underneath each.\n" ). -spec source_context(binary(), {integer(), integer()}) -> list(binary()). source_context(Source, Span) -> {Start, End} = Span, End@1 = gleam@int:max(End, Start), Lines = gleam@string:split(Source, <<"\n"/utf8>>), do_render(Lines, Start, End@1, 1, 0, []).