Files
n8n-openai-adapter/lib/elixir/lib/inspect.ex
T

390 lines
10 KiB
Elixir

import Kernel, except: [inspect: 1]
import Inspect.Algebra
alias Code.Identifier
defprotocol Inspect do
@moduledoc """
The `Inspect` protocol is responsible for converting any Elixir
data structure into an algebra document. This document is then
formatted, either in pretty printing format or a regular one.
The `inspect/2` function receives the entity to be inspected
followed by the inspecting options, represented by the struct
`Inspect.Opts`.
Inspection is done using the functions available in `Inspect.Algebra`.
## Examples
Many times, inspecting a structure can be implemented in function
of existing entities. For example, here is `MapSet`'s `inspect`
implementation:
defimpl Inspect, for: MapSet do
import Inspect.Algebra
def inspect(dict, opts) do
concat(["#MapSet<", to_doc(MapSet.to_list(dict), opts), ">"])
end
end
The `concat/1` function comes from `Inspect.Algebra` and it
concatenates algebra documents together. In the example above,
it is concatenating the string `"MapSet<"` (all strings are
valid algebra documents that keep their formatting when pretty
printed), the document returned by `Inspect.Algebra.to_doc/2` and the
other string `">"`.
Since regular strings are valid entities in an algebra document,
an implementation of inspect may simply return a string,
although that will devoid it of any pretty-printing.
## Error handling
In case there is an error while your structure is being inspected,
Elixir will raise an `ArgumentError` error and will automatically fall back
to a raw representation for printing the structure.
You can however access the underlying error by invoking the Inspect
implementation directly. For example, to test Inspect.MapSet above,
you can invoke it as:
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
"""
# Handle structs in Any
@fallback_to_any true
def inspect(term, opts)
end
defimpl Inspect, for: Atom do
require Macro
def inspect(atom, opts) do
color(Identifier.inspect_as_atom(atom), color_key(atom), opts)
end
defp color_key(atom) when is_boolean(atom), do: :boolean
defp color_key(nil), do: nil
defp color_key(_), do: :atom
end
defimpl Inspect, for: BitString do
def inspect(term, opts) when is_binary(term) do
%Inspect.Opts{binaries: bins, base: base, printable_limit: printable_limit} = opts
if base == :decimal and
(bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
inspected =
case Identifier.escape(term, ?", printable_limit) do
{escaped, ""} -> [?", escaped, ?"]
{escaped, _} -> [?", escaped, ?", " <> ..."]
end
color(IO.iodata_to_binary(inspected), :string, opts)
else
inspect_bitstring(term, opts)
end
end
def inspect(term, opts) do
inspect_bitstring(term, opts)
end
defp inspect_bitstring("", opts) do
color("<<>>", :binary, opts)
end
defp inspect_bitstring(bitstring, opts) do
left = color("<<", :binary, opts)
right = color(">>", :binary, opts)
inner = each_bit(bitstring, opts.limit, opts)
group(concat(concat(left, nest(inner, 2)), right))
end
defp each_bit(_, 0, _) do
"..."
end
defp each_bit(<<>>, _counter, _opts) do
:doc_nil
end
defp each_bit(<<h::8>>, _counter, opts) do
Inspect.Integer.inspect(h, opts)
end
defp each_bit(<<h, t::bitstring>>, counter, opts) do
flex_glue(
concat(Inspect.Integer.inspect(h, opts), ","),
each_bit(t, decrement(counter), opts)
)
end
defp each_bit(bitstring, _counter, opts) do
size = bit_size(bitstring)
<<h::size(size)>> = bitstring
Inspect.Integer.inspect(h, opts) <> "::size(" <> Integer.to_string(size) <> ")"
end
@compile {:inline, decrement: 1}
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Inspect, for: List do
def inspect([], opts) do
color("[]", :list, opts)
end
# TODO: Remove :char_list and :as_char_lists handling in 2.0
def inspect(term, opts) do
%Inspect.Opts{
charlists: lists,
char_lists: lists_deprecated,
printable_limit: printable_limit
} = opts
lists =
if lists == :infer and lists_deprecated != :infer do
case lists_deprecated do
:as_char_lists ->
IO.warn(
"the :char_lists inspect option and its :as_char_lists " <>
"value are deprecated, use the :charlists option and its " <>
":as_charlists value instead"
)
:as_charlists
_ ->
IO.warn("the :char_lists inspect option is deprecated, use :charlists instead")
lists_deprecated
end
else
lists
end
open = color("[", :list, opts)
sep = color(",", :list, opts)
close = color("]", :list, opts)
cond do
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
inspected =
case Identifier.escape(IO.chardata_to_string(term), ?', printable_limit) do
{escaped, ""} -> [?', escaped, ?']
{escaped, _} -> [?', escaped, ?', " ++ ..."]
end
IO.iodata_to_binary(inspected)
keyword?(term) ->
container_doc(open, term, close, opts, &keyword/2, separator: sep, break: :strict)
true ->
container_doc(open, term, close, opts, &to_doc/2, separator: sep)
end
end
@doc false
def keyword({key, value}, opts) do
key = color(Identifier.inspect_as_key(key), :atom, opts)
concat(key, concat(" ", to_doc(value, opts)))
end
@doc false
def keyword?([{key, _value} | rest]) when is_atom(key) do
case Atom.to_charlist(key) do
'Elixir.' ++ _ -> false
_ -> keyword?(rest)
end
end
def keyword?([]), do: true
def keyword?(_other), do: false
end
defimpl Inspect, for: Tuple do
def inspect(tuple, opts) do
open = color("{", :tuple, opts)
sep = color(",", :tuple, opts)
close = color("}", :tuple, opts)
container_opts = [separator: sep, break: :flex]
container_doc(open, Tuple.to_list(tuple), close, opts, &to_doc/2, container_opts)
end
end
defimpl Inspect, for: Map do
def inspect(map, opts) do
inspect(map, "", opts)
end
def inspect(map, name, opts) do
map = :maps.to_list(map)
open = color("%" <> name <> "{", :map, opts)
sep = color(",", :map, opts)
close = color("}", :map, opts)
container_doc(open, map, close, opts, traverse_fun(map, opts), separator: sep, break: :strict)
end
defp traverse_fun(list, opts) do
if Inspect.List.keyword?(list) do
&Inspect.List.keyword/2
else
sep = color(" => ", :map, opts)
&to_map(&1, &2, sep)
end
end
defp to_map({key, value}, opts, sep) do
concat(concat(to_doc(key, opts), sep), to_doc(value, opts))
end
end
defimpl Inspect, for: Integer do
def inspect(term, %Inspect.Opts{base: base} = opts) do
inspected = Integer.to_string(term, base_to_value(base)) |> prepend_prefix(base)
color(inspected, :number, opts)
end
defp base_to_value(base) do
case base do
:binary -> 2
:decimal -> 10
:octal -> 8
:hex -> 16
end
end
defp prepend_prefix(value, :decimal), do: value
defp prepend_prefix(<<?-, value::binary>>, base) do
"-" <> prepend_prefix(value, base)
end
defp prepend_prefix(value, base) do
prefix =
case base do
:binary -> "0b"
:octal -> "0o"
:hex -> "0x"
end
prefix <> value
end
end
defimpl Inspect, for: Float do
def inspect(term, opts) do
inspected = IO.iodata_to_binary(:io_lib_format.fwrite_g(term))
color(inspected, :number, opts)
end
end
defimpl Inspect, for: Regex do
def inspect(regex, opts) do
{escaped, _} = Identifier.escape(regex.source, ?/, :infinity, &escape_map/1)
source = IO.iodata_to_binary(['~r/', escaped, ?/, regex.opts])
color(source, :regex, opts)
end
defp escape_map(?\a), do: '\\a'
defp escape_map(?\f), do: '\\f'
defp escape_map(?\n), do: '\\n'
defp escape_map(?\r), do: '\\r'
defp escape_map(?\t), do: '\\t'
defp escape_map(?\v), do: '\\v'
defp escape_map(_), do: false
end
defimpl Inspect, for: Function do
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
mod = fun_info[:module]
name = fun_info[:name]
cond do
fun_info[:type] == :external and fun_info[:env] == [] ->
inspected_as_atom = Identifier.inspect_as_atom(mod)
inspected_as_function = Identifier.inspect_as_function(name)
"&#{inspected_as_atom}.#{inspected_as_function}/#{fun_info[:arity]}"
match?('elixir_compiler_' ++ _, Atom.to_charlist(mod)) ->
if function_exported?(mod, :__RELATIVE__, 0) do
"#Function<#{uniq(fun_info)} in file:#{mod.__RELATIVE__}>"
else
default_inspect(mod, fun_info)
end
true ->
default_inspect(mod, fun_info)
end
end
defp default_inspect(mod, fun_info) do
inspected_as_atom = Identifier.inspect_as_atom(mod)
extracted_name = extract_name(fun_info[:name])
"#Function<#{uniq(fun_info)}/#{fun_info[:arity]} in #{inspected_as_atom}#{extracted_name}>"
end
defp extract_name([]) do
""
end
defp extract_name(name) do
case Identifier.extract_anonymous_fun_parent(name) do
{name, arity} ->
"." <> Identifier.inspect_as_function(name) <> "/" <> arity
:error ->
"." <> Identifier.inspect_as_function(name)
end
end
defp uniq(fun_info) do
Integer.to_string(fun_info[:new_index]) <> "." <> Integer.to_string(fun_info[:uniq])
end
end
defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
end
end
defimpl Inspect, for: Port do
def inspect(port, _opts) do
IO.iodata_to_binary(:erlang.port_to_list(port))
end
end
defimpl Inspect, for: Reference do
def inspect(ref, _opts) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> IO.iodata_to_binary(rest)
end
end
defimpl Inspect, for: Any do
def inspect(%module{} = struct, opts) do
try do
module.__struct__
rescue
_ -> Inspect.Map.inspect(struct, opts)
else
dunder ->
if :maps.keys(dunder) == :maps.keys(struct) do
pruned = :maps.remove(:__exception__, :maps.remove(:__struct__, struct))
colorless_opts = %{opts | syntax_colors: []}
Inspect.Map.inspect(pruned, Inspect.Atom.inspect(module, colorless_opts), opts)
else
Inspect.Map.inspect(struct, opts)
end
end
end
end