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

245 lines
7.2 KiB
Elixir

defmodule Elixir.Typespec do
@moduledoc """
This is the module that converts Elixir typespecs
to Erlang typespecs syntax. Everytime @spec, @type
and @typep are used they proxy to the functions
in this module.
"""
defmacro deftype(name, options // []) do
_deftype(name, true, __CALLER__, options)
end
defmacro deftypep(name) do
_deftype(name, false, __CALLER__, [])
end
defmacro defspec(spec, block) do
_defspec(:spec, __CALLER__, spec, block)
end
defmacro defcallback(spec, block) do
_defspec(:callback, __CALLER__, spec, block)
end
@doc """
Get the types defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_types(module) do
Module.read_attribute(module, :type) ++ Module.read_attribute(module, :opaque)
end
@doc """
Get the specs defined for the given module. This function
is only available for modules being compiled. If the module
was already compiled, you need to loop its attributes
to get such information.
"""
def get_specs(module) do
specs = :ets.tab2list(spec_table_for(module))
keys = :lists.ukeysort(1, specs)
lc { k, _ } inlist keys, do: { k, :proplists.append_values(k, specs) }
end
## Typespec conversion
# Handle unions
defp typespec({ :|, line, [_,_] } = exprs, vars, caller) do
exprs = List.reverse(collect_union(exprs))
union = lc e inlist exprs, do: typespec(e, vars, caller)
{ :type, line, :union, union }
end
# Handle binaries
defp typespec({:<<>>, line, []}, _,_) do
{:type, line, :binary, [{:integer, line, 0}, {:integer, line, 0}]}
end
defp typespec({:<<>>, line, [{:|, _, [{:_, line1, atom}, {:*, _, [{:_, line2, atom}, unit]}]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, 0}, {:integer, line2, unit}]}
end
defp typespec({:<<>>, line, [{:|, line1, [{:_, line2, atom}, base]}]}, _, _) when is_atom(atom) do
{:type, line, :binary, [{:integer, line1, base}, {:integer, line2, 0}]}
end
# Handle ranges
defp typespec({:"..", line, args}, vars, caller) do
typespec({:range, line, args}, vars, caller)
end
# Handle aliases
defp typespec({:__aliases__, _, _} = alias, vars, caller) do
atom = Macro.expand alias, caller
typespec(atom, vars, caller)
end
# Handle type operator
defp typespec({:"::", line, [var, expr] }, vars, caller) do
left = typespec(var, [elem(var,1)|vars], caller)
right = typespec(expr, vars, caller)
{ :ann_type, line, [left, right] }
end
# Handle unary ops
defp typespec({op, line, [integer]}, _, _) when op in [:+, :-] and is_integer(integer) do
{ :op, line, op, {:integer, line, integer} }
end
# Handle remote calls
defp typespec({{:., line, [remote, name]}, _, args}, vars, caller) do
remote = Macro.expand remote, caller
unless is_atom(remote), do: raise(ArgumentError, message: "Invalid remote in typespec")
remote_type({typespec(remote, vars, caller), line, typespec(name, vars, caller), args}, vars, caller)
end
# Handle tuples
defp typespec({:tuple, line, atom}, vars, caller) when is_atom(atom) do
typespec({:{}, line, []}, vars, caller)
end
defp typespec({:{}, line, []}, _, _) do
{ :type, line, :tuple, :any }
end
defp typespec({:{}, line, t}, vars, caller) when is_list(t) do
args = lc e inlist t, do: typespec(e, vars, caller)
{ :type, line, :tuple, args }
end
# Handle funs
defp typespec({:fun, line, arguments}, vars, caller) when is_list(arguments) do
args =
case List.reverse(arguments) do
[[{:do,h}]|t] -> fn_args(line, List.reverse(t), h, vars, caller)
[] -> []
_ -> [fn_args(line, arguments, vars, caller)]
end
{ :type, line, :fun, args }
end
# Handle variables or local calls
defp typespec({name, line, atom}, vars, caller) when is_atom(atom) do
if List.member?(vars, name) do
{ :var, line, name }
else
typespec({name, line, []}, vars, caller)
end
end
# Handle local calls
defp typespec({name, line, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :type, line, name, arguments }
end
# Handle literals
defp typespec(atom, _, _) when is_atom(atom) do
{ :atom, 0, atom }
end
defp typespec(integer, _, _) when is_integer(integer) do
{ :integer, 0, integer }
end
defp typespec([], vars, caller) do
typespec({ nil, 0, [] }, vars, caller)
end
defp typespec([spec], vars, caller) do
typespec({ :list, 0, [spec] }, vars, caller)
end
defp typespec(l, _, _) when is_list(l) do
raise(ArgumentError, message: "Unexpected list #{inspect l}")
end
defp typespec(t, vars, caller) when is_tuple(t) do
args = lc e inlist tuple_to_list(t), do: typespec(e, vars, caller)
{ :type, 0, :tuple, args }
end
## Helpers
defp remote_type({remote, line, name, arguments}, vars, caller) do
arguments = lc arg inlist arguments, do: typespec(arg, vars, caller)
{ :remote_type, line, [ remote, name, arguments ] }
end
defp collect_union({ :|, _, [a, b] }), do: [b|collect_union(a)]
defp collect_union(v), do: [v]
defp fn_args(line, args, return, vars, caller) do
[fn_args(line, args, vars, caller), typespec(return, vars, caller)]
end
defp fn_args(line, [{:"...", _, _}], _vars, _caller) do
{ :type, line, :any }
end
defp fn_args(line, args, vars, caller) do
args = lc arg inlist args, do: typespec(arg, vars, caller)
{ :type, line, :product, args }
end
def _deftype({:"::", _, [name, definition]}, export, caller, opts) do
_deftype(name, definition, export, caller, opts)
end
def _deftype(name, export, caller, opts) do
_deftype(name, { :term, caller.line, nil }, export, caller, opts)
end
defp _deftype({name, _, args}, definition, export, caller, options) do
args = if is_atom(args), do: [], else: lc(arg inlist args, do: variable(arg))
vars = lc {:var, _, var} inlist args, do: var
spec = typespec(definition, vars, caller)
vars = lc ({:var, _, _} = var) inlist args, do: var
attr = if options[:opaque], do: :opaque, else: :type
export = if export do
quote do: Module.compile_type(__MODULE__, :export_type, [{name, length(vars)}])
else
nil
end
quote do
name = unquote(name)
spec = unquote(Macro.escape(spec))
vars = unquote(Macro.escape(vars))
type = { name, spec, vars }
Module.compile_type __MODULE__, unquote(attr), type
unquote(export)
{ unquote(attr), type }
end
end
defp _defspec(type, caller, {name, line, args},[{:do,return}]) do
spec = { :type, line, :fun, fn_args(line, args, return, [], caller) }
code = Macro.escape { {type, { name, length(args) }}, [spec] }
table = spec_table_for(caller.module)
quote do
code = unquote(code)
:ets.insert(unquote(table), code)
code
end
end
defp spec_table_for(module) do
table = list_to_atom Erlang.lists.concat([:s, module])
unless table == :ets.info(table, :name), do:
raise(ArgumentError, message: "cannot manage specs for #{inspect module} because it was already compiled")
table
end
defp variable({name, line, _}) do
{:var, line, name}
end
end