%% SPDX-License-Identifier: Apache-2.0 %% SPDX-FileCopyrightText: 2024 The Gleam contributors -define(is_lowercase_char(X), (X > 96 andalso X < 123)). -define(is_underscore_char(X), (X == 95)). -define(is_digit_char(X), (X > 47 andalso X < 58)). -define(is_ascii_character(X), (erlang:is_integer(X) andalso X >= 32 andalso X =< 126)). -define(could_be_record(Tuple), erlang:is_tuple(Tuple) andalso erlang:is_atom(erlang:element(1, Tuple)) andalso erlang:element(1, Tuple) =/= false andalso erlang:element(1, Tuple) =/= true andalso erlang:element(1, Tuple) =/= nil ). -define(is_atom_char(C), (?is_lowercase_char(C) orelse ?is_underscore_char(C) orelse ?is_digit_char(C)) ). -define(grey, "\e[90m"). -define(reset_color, "\e[39m"). echo(Value, Message, Line) -> StringLine = erlang:integer_to_list(Line), StringValue = echo@inspect(Value), StringMessage = case Message of nil -> ""; M -> [" ", M] end, io:put_chars( standard_error, [ ?grey, ?FILEPATH, $:, StringLine, ?reset_color, StringMessage, $\n, StringValue, $\n ] ), Value. echo@inspect(Value) -> case Value of nil -> "Nil"; true -> "True"; false -> "False"; Int when erlang:is_integer(Int) -> erlang:integer_to_list(Int); Float when erlang:is_float(Float) -> io_lib_format:fwrite_g(Float); Binary when erlang:is_binary(Binary) -> inspect@binary(Binary); Bits when erlang:is_bitstring(Bits) -> inspect@bit_array(Bits); Atom when erlang:is_atom(Atom) -> inspect@atom(Atom); List when erlang:is_list(List) -> inspect@list(List); Map when erlang:is_map(Map) -> inspect@map(Map); Record when ?could_be_record(Record) -> inspect@record(Record); Tuple when erlang:is_tuple(Tuple) -> inspect@tuple(Tuple); Function when erlang:is_function(Function) -> inspect@function(Function); Any -> ["//erl(", io_lib:format("~p", [Any]), ")"] end. inspect@bit_array(Bits) -> Pieces = inspect@bit_array_pieces(Bits, []), Inner = lists:join(", ", lists:reverse(Pieces)), ["<<", Inner, ">>"]. inspect@bit_array_pieces(Bits, Acc) -> case Bits of <<>> -> Acc; <> -> inspect@bit_array_pieces(Rest, [erlang:integer_to_binary(Byte) | Acc]); _ -> Size = erlang:bit_size(Bits), <> = Bits, SizeString = [":size(", erlang:integer_to_binary(Size), ")"], Piece = [erlang:integer_to_binary(RemainingBits), SizeString], [Piece | Acc] end. inspect@binary(Binary) -> case inspect@maybe_utf8_string(Binary, <<>>) of {ok, InspectedUtf8String} -> InspectedUtf8String; {error, not_a_utf8_string} -> Segments = [erlang:integer_to_list(X) || <> <= Binary], ["<<", lists:join(", ", Segments), ">>"] end. inspect@atom(Atom) -> Binary = erlang:atom_to_binary(Atom), case inspect@maybe_gleam_atom(Binary, none, <<>>) of {ok, Inspected} -> Inspected; {error, _} -> ["atom.create(\"", Binary, "\")"] end. inspect@list(List) -> case inspect@list_loop(List, true) of {charlist, _} -> ["charlist.from_string(\"", erlang:list_to_binary(List), "\")"]; {proper, Elements} -> ["[", Elements, "]"]; {improper, Elements} -> ["//erl([", Elements, "])"] end. inspect@map(Map) -> Fields = [ [<<"#(">>, echo@inspect(Key), <<", ">>, echo@inspect(Value), <<")">>] || {Key, Value} <- maps:to_list(Map) ], ["dict.from_list([", lists:join(", ", Fields), "])"]. inspect@record(Record) -> [Atom | ArgsList] = Tuple = erlang:tuple_to_list(Record), case inspect@maybe_gleam_atom(Atom, none, <<>>) of {ok, Tag} -> Args = lists:join(", ", lists:map(fun echo@inspect/1, ArgsList)), [Tag, "(", Args, ")"]; _ -> inspect@tuple(Tuple) end. inspect@tuple(Tuple) when erlang:is_tuple(Tuple) -> inspect@tuple(erlang:tuple_to_list(Tuple)); inspect@tuple(Tuple) -> Elements = lists:map(fun echo@inspect/1, Tuple), ["#(", lists:join(", ", Elements), ")"]. inspect@function(Function) -> {arity, Arity} = erlang:fun_info(Function, arity), ArgsAsciiCodes = lists:seq($a, $a + Arity - 1), Args = lists:join(", ", lists:map(fun(Arg) -> <> end, ArgsAsciiCodes)), ["//fn(", Args, ") { ... }"]. inspect@maybe_utf8_string(Binary, Acc) -> case Binary of <<>> -> {ok, <<$", Acc/binary, $">>}; <> -> Escaped = inspect@escape_grapheme(First), inspect@maybe_utf8_string(Rest, <>); _ -> {error, not_a_utf8_string} end. inspect@escape_grapheme(Char) -> case Char of $" -> <<$\\, $">>; $\\ -> <<$\\, $\\>>; $\r -> <<$\\, $r>>; $\n -> <<$\\, $n>>; $\t -> <<$\\, $t>>; $\f -> <<$\\, $f>>; X when X > 126, X < 160 -> inspect@convert_to_u(X); X when X < 32 -> inspect@convert_to_u(X); Other -> <> end. inspect@convert_to_u(Code) -> erlang:list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])). inspect@list_loop(List, Ascii) -> case List of [] -> {proper, []}; [First] when Ascii andalso ?is_ascii_character(First) -> {charlist, nil}; [First] -> {proper, [echo@inspect(First)]}; [First | Rest] when erlang:is_list(Rest) -> StillAscii = Ascii andalso ?is_ascii_character(First), {Kind, Inspected} = inspect@list_loop(Rest, StillAscii), {Kind, [echo@inspect(First), ", " | Inspected]}; [First | ImproperRest] -> {improper, [echo@inspect(First), " | ", echo@inspect(ImproperRest)]} end. inspect@maybe_gleam_atom(Atom, PrevChar, Acc) when erlang:is_atom(Atom) -> Binary = erlang:atom_to_binary(Atom), inspect@maybe_gleam_atom(Binary, PrevChar, Acc); inspect@maybe_gleam_atom(Atom, PrevChar, Acc) -> case {Atom, PrevChar} of {<<>>, none} -> {error, nil}; {<>, none} when ?is_digit_char(First) -> {error, nil}; {<<"_", _/binary>>, none} -> {error, nil}; {<<"_">>, _} -> {error, nil}; {<<"_", _/binary>>, $_} -> {error, nil}; {<>, _} when not ?is_atom_char(First) -> {error, nil}; {<>, none} -> inspect@maybe_gleam_atom(Rest, First, <>); {<<"_", Rest/binary>>, _} -> inspect@maybe_gleam_atom(Rest, $_, Acc); {<>, $_} -> inspect@maybe_gleam_atom(Rest, First, <>); {<>, _} -> inspect@maybe_gleam_atom(Rest, First, <>); {<<>>, _} -> {ok, Acc}; _ -> erlang:throw({gleam_error, echo, Atom, PrevChar, Acc}) end. inspect@uppercase(X) -> X - 32.