Files
n8n-openai-adapter/lib/elixir/lib/binary/inspect.ex
T
2013-01-30 11:15:40 -07:00

447 lines
10 KiB
Elixir

import Kernel, except: [inspect: 1]
defprotocol Binary.Inspect do
@moduledoc """
The `Binary.Inspect` protocol is responsible for
converting any structure to a binary for textual
representation. All basic data structures
(tuple, list, function, pid, etc) implement the
inspect protocol. Other structures are advised to
implement the protocol in order to provide pretty
printing.
"""
@only [BitString, List, Tuple, Atom, Number, Function, PID, Port, Reference]
def inspect(thing, opts)
end
defmodule Binary.Inspect.Utils do
@moduledoc false
## container_join
def container_join(tuple, first, last, opts) when is_tuple(tuple) do
container_join(tuple_to_list(tuple), first, last, opts)
end
def container_join(list, first, last, opts) do
first <> do_container_join(list, opts, Keyword.get(opts, :limit, :infinity)) <> last
end
defp do_container_join(_, _opts, 0) do
"..."
end
defp do_container_join([h], opts, _counter) do
Binary.Inspect.inspect(h, opts)
end
defp do_container_join([h|t], opts, counter) when is_list(t) do
Binary.Inspect.inspect(h, opts) <> "," <> do_container_join(t, opts, decrement(counter))
end
defp do_container_join([h|t], opts, _counter) do
Binary.Inspect.inspect(h, opts) <> "|" <> Binary.Inspect.inspect(t, opts)
end
defp do_container_join([], _opts, _counter) do
""
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
## escape
# It is considerably faster to loop the binary
# and convert it to a list as we go compared
# to looping the binary and creating a binary
# as we go.
def escape(other, char) do
list_to_binary [char|do_escape(other, char)]
end
defp do_escape(<<char, t :: binary>>, char) do
[?\\, char | do_escape(t, char)]
end
defp do_escape(<<h, t :: binary>>, char) when
h == ?# or h == ?\a or
h == ?\b or h == ?\d or
h == ?\e or h == ?\f or
h == ?\n or h == ?\r or
h == ?\\ or h == ?\t or
h == ?\v do
[?\\, escape_map(h) | do_escape(t, char)]
end
defp do_escape(<<h, t :: binary>>, char) do
[h | do_escape(t,char)]
end
defp do_escape(<<>>, char) do
[char]
end
defp escape_map(?#), do: ?#
defp escape_map(?\a), do: ?a
defp escape_map(?\b), do: ?b
defp escape_map(?\d), do: ?d
defp escape_map(?\e), do: ?e
defp escape_map(?\f), do: ?f
defp escape_map(?\n), do: ?n
defp escape_map(?\r), do: ?r
defp escape_map(?\\), do: ?\\
defp escape_map(?\t), do: ?t
defp escape_map(?\v), do: ?v
end
defimpl Binary.Inspect, for: Atom do
require Macro
import Binary.Inspect.Utils
@moduledoc """
Represents the atom as an Elixir term. The atoms false, true
and nil are simply quoted. Modules are properly represented
as modules using the dot notation.
Notice that in Elixir, all operators can be represented using
literal atoms (`:+`, `:-`, etc).
## Examples
inspect(:foo) #=> ":foo"
inspect(nil) #=> "nil"
inspect(Foo.Bar) #=> "Foo.Bar"
"""
def inspect(false, _), do: "false"
def inspect(true, _), do: "true"
def inspect(nil, _), do: "nil"
def inspect(:"", _), do: ":\"\""
def inspect(Elixir, _), do: "Elixir"
def inspect(atom, _) do
binary = atom_to_binary(atom)
cond do
valid_atom_identifier?(binary) ->
":" <> binary
valid_ref_identifier?(binary) ->
Module.to_binary(atom)
atom in Macro.binary_ops or atom in Macro.unary_ops ->
":" <> binary
true ->
":" <> escape(binary, ?")
end
end
# Detect if atom is an atom alias (Elixir-Foo-Bar-Baz)
defp valid_ref_identifier?("Elixir" <> rest) do
valid_ref_piece?(rest)
end
defp valid_ref_identifier?(_), do: false
defp valid_ref_piece?(<<?-, h, t :: binary>>) when h in ?A..?Z do
valid_ref_piece? valid_identifier?(t)
end
defp valid_ref_piece?(<<>>), do: true
defp valid_ref_piece?(_), do: false
# Detect if atom
defp valid_atom_identifier?(<<h, t :: binary>>) when h in ?a..?z or h in ?A..?Z or h == ?_ do
case valid_identifier?(t) do
<<>> -> true
<<??>> -> true
<<?!>> -> true
_ -> false
end
end
defp valid_atom_identifier?(_), do: false
defp valid_identifier?(<<h, t :: binary>>)
when h in ?a..?z
when h in ?A..?Z
when h in ?0..?9
when h == ?_ do
valid_identifier? t
end
defp valid_identifier?(other), do: other
end
defimpl Binary.Inspect, for: BitString do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents the string as itself escaping
all necessary characters.
## Examples
inspect("bar") #=> "bar"
inspect("f\"oo") #=> "f\"oo"
"""
def inspect(thing, opts) when is_binary(thing) do
if String.printable?(thing) do
escape(thing, ?")
else
as_bitstring(thing, opts)
end
end
def inspect(thing, opts) do
as_bitstring(thing, opts)
end
## Bitstrings
defp as_bitstring(bitstring, opts) do
"<<" <> each_bit(bitstring, Keyword.get(opts, :limit, :infinity)) <> ">>"
end
defp each_bit(_, 0) do
"..."
end
defp each_bit(<<h, t :: bitstring>>, counter) when t != <<>> do
integer_to_binary(h) <> "," <> each_bit(t, decrement(counter))
end
defp each_bit(<<h :: size(8)>>, _counter) do
integer_to_binary(h)
end
defp each_bit(<<>>, _counter) do
<<>>
end
defp each_bit(bitstring, _counter) do
size = bit_size(bitstring)
<<h :: size(size)>> = bitstring
integer_to_binary(h) <> "::size(" <> integer_to_binary(size) <> ")"
end
defp decrement(:infinity), do: :infinity
defp decrement(counter), do: counter - 1
end
defimpl Binary.Inspect, for: List do
import Binary.Inspect.Utils
@moduledoc %B"""
Represents a list checking if it can be printed or not.
If so, a single-quoted representation is returned,
otherwise the brackets syntax is used.
Inspecting a list is conservative as it does not try
to guess how the list is encoded. That said, `'josé'`
will likely be inspected as `[106,111,115,195,169]`
because we can't know if it is encoded in utf-8
or iso-5569-1, which is common in Erlang libraries.
## Examples
inspect('bar') #=> 'bar'
inspect([0|'bar']) #=> "[0,98,97,114]"
inspect([:foo,:bar]) #=> "[:foo, :bar]"
"""
def inspect([], _), do: "[]"
def inspect(thing, opts) do
cond do
:io_lib.printable_list(thing) ->
escape(:unicode.characters_to_binary(thing), ?')
Keyword.keyword?(thing) ->
"[" <> join_keywords(thing, opts) <> "]"
true ->
container_join(thing, "[", "]", opts)
end
end
defp join_keywords(thing, opts) do
Enum.join(lc {key, value} inlist thing do
key_to_binary(key, opts) <> ": " <> Binary.Inspect.inspect(value, opts)
end, ", ")
end
defp key_to_binary(key, opts) do
case Binary.Inspect.Atom.inspect(key, opts) do
":" <> right -> right
other -> other
end
end
end
defimpl Binary.Inspect, for: Tuple do
import Binary.Inspect.Utils
@moduledoc """
Inspect tuples. If the tuple represents a record,
it shows it nicely formatted using the access syntax.
## Examples
inspect({1,2,3}) #=> "{1,2,3}"
inspect(ArgumentError.new) #=> ArgumentError[message: "argument error"]
"""
def inspect({}, _), do: "{}"
def inspect(tuple, opts) do
unless opts[:raw] do
record_protocol(tuple, opts)
end || container_join(tuple, "{", "}", opts)
end
## Helpers
defp record_protocol(tuple, opts) do
name = elem(tuple, 0)
if is_atom(name) and match?("Elixir-" <> _, atom_to_binary(name)) do
unless name in [BitString, List, Tuple, Atom, Number, Any] do
try do
target = Module.concat(Binary.Inspect, name)
target.inspect(tuple, opts)
rescue
UndefinedFunctionError ->
record_inspect(tuple, opts)
end
end
end
end
defp record_inspect(record, opts) do
list = tuple_to_list(record)
[name|tail] = list
if (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
if Enum.first(tail) == :__exception__ do
record_join(name, tl(fields), tl(tail), opts)
else
record_join(name, fields, tail, opts)
end
end
end
defp record_fields(name) do
try do
name.__record__(:fields)
rescue
_ -> nil
end
end
defp record_join(name, fields, tail, opts) do
fields = lc { field, _ } inlist fields, do: field
Binary.Inspect.Atom.inspect(name, opts) <> "[" <>
record_join(fields, tail, opts) <> "]"
end
defp record_join([f], [v], opts) do
atom_to_binary(f, :utf8) <> ": " <> Binary.Inspect.inspect(v, opts)
end
defp record_join([fh|ft], [vh|vt], opts) do
atom_to_binary(fh, :utf8) <> ": " <>
Binary.Inspect.inspect(vh, opts) <> ", " <>
record_join(ft, vt, opts)
end
defp record_join([], [], _opts) do
""
end
end
defimpl Binary.Inspect, for: Number do
@moduledoc """
Represents the number as a binary.
## Examples
inspect(1) #=> "1"
"""
def inspect(thing, _) when is_integer(thing) do
list_to_binary integer_to_list(thing)
end
def inspect(thing, _) do
list_to_binary :io_lib.format("~p", [thing])
end
end
defimpl Binary.Inspect, for: Regex do
@moduledoc %B"""
Represents the Regex using the `%r""` syntax.
## Examples
inspect(%r/foo/m) #=> "%r\"foo\"m"
"""
def inspect(regex, _opts) when size(regex) == 5 do
"%r" <> Binary.Inspect.inspect(Regex.source(regex), []) <> Regex.opts(regex)
end
def inspect(other, opts) do
Binary.Inspect.inspect other, Keyword.put(opts, :raw, true)
end
end
defimpl Binary.Inspect, for: Function do
@moduledoc """
Inspect functions, when possible, in a literal form.
"""
def inspect(function, _opts) do
fun_info = :erlang.fun_info(function)
if fun_info[:type] == :external and fun_info[:env] == [] do
"function(#{Kernel.inspect(fun_info[:module])}.#{fun_info[:name]}/#{fun_info[:arity]})"
else
'#Fun' ++ rest = :erlang.fun_to_list(function)
"#Function" <> list_to_binary(rest)
end
end
end
defimpl Binary.Inspect, for: PID do
@moduledoc "Inspect PIDs"
def inspect(pid, _) do
"#PID" <> list_to_binary pid_to_list(pid)
end
end
defimpl Binary.Inspect, for: Port do
@moduledoc "Inspect ports"
def inspect(port, _) do
list_to_binary :erlang.port_to_list(port)
end
end
defimpl Binary.Inspect, for: Reference do
@moduledoc "Inspect references"
def inspect(ref, _) do
'#Ref' ++ rest = :erlang.ref_to_list(ref)
"#Reference" <> list_to_binary(rest)
end
end