Current section
Files
Jump to
Current section
Files
src/eyg@parser@location.erl
-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)),
<<<<<<<<<<Gutter/binary, Line/binary>>/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, []).