
* Add aeso_utils:canonical_dir/1 * Add current file directory when resolving includes * Add CHANGELOG * Add documentation * Add a test case * Properly keep track of src_dir
81 lines
2.5 KiB
Erlang
81 lines
2.5 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
|
%%% @doc
|
|
%%% Sophia utility functions.
|
|
%%% @end
|
|
%%%-------------------------------------------------------------------
|
|
-module(aeso_utils).
|
|
|
|
-export([scc/1, canonical_dir/1]).
|
|
|
|
-export_type([graph/1]).
|
|
|
|
%% -- Simplistic canonical directory
|
|
%% Note: no attempts to be 100% complete
|
|
|
|
canonical_dir(Dir) ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
AbsName = filename:absname(Dir),
|
|
RelAbsName = filename:join(tl(filename:split(AbsName))),
|
|
case filelib:safe_relative_path(RelAbsName, Cwd) of
|
|
unsafe -> AbsName;
|
|
Simplified -> filename:absname(Simplified, "")
|
|
end.
|
|
|
|
%% -- Topological sort
|
|
|
|
-type graph(Node) :: #{Node => [Node]}. %% List of incoming edges (dependencies).
|
|
|
|
%% Topologically sorted strongly-connected components of a graph.
|
|
-spec scc(graph(Node)) -> [{cyclic, [Node]} | {acyclic, Node}].
|
|
scc(Graph) ->
|
|
Trees = dfs(Graph, lists:reverse(postorder(dff(reverse_graph(Graph))))),
|
|
Decode = fun(T) ->
|
|
case postorder(T) of
|
|
[I] -> case lists:member(I, maps:get(I, Graph, [])) of
|
|
true -> {cyclic, [I]};
|
|
false -> {acyclic, I}
|
|
end;
|
|
Is -> {cyclic, Is}
|
|
end end,
|
|
lists:map(Decode, Trees).
|
|
|
|
%% Depth first spanning forest of a graph.
|
|
dff(Graph) ->
|
|
dfs(Graph, maps:keys(Graph)).
|
|
|
|
dfs(Graph, Vs) ->
|
|
{_, Trees} = dfs(Graph, #{}, Vs, []),
|
|
Trees.
|
|
|
|
dfs(_Graph, Visited, [], Trees) -> {Visited, lists:reverse(Trees)};
|
|
dfs(Graph, Visited, [V | Vs], Trees) ->
|
|
case maps:is_key(V, Visited) of
|
|
true -> dfs(Graph, Visited, Vs, Trees);
|
|
false ->
|
|
{Visited1, Tree} = dfs1(Graph, Visited#{ V => true }, V),
|
|
dfs(Graph, Visited1, Vs, [Tree | Trees])
|
|
end.
|
|
|
|
dfs1(Graph, Visited, V) ->
|
|
Ws = maps:get(V, Graph, []),
|
|
{Visited1, Trees} = dfs(Graph, Visited, Ws, []),
|
|
{Visited1, {V, Trees}}.
|
|
|
|
%% Post-order traversal of a tree/forest.
|
|
postorder(Tree = {_, _}) -> postorder([Tree]);
|
|
postorder(Trees) when is_list(Trees) -> postorder(Trees, []).
|
|
|
|
postorder([], Acc) -> Acc;
|
|
postorder([{V, Trees1} | Trees], Acc) ->
|
|
postorder(Trees1, [V | postorder(Trees, Acc)]).
|
|
|
|
from_edges(Is, Es) ->
|
|
lists:foldl(fun({I, J}, G) ->
|
|
maps:update_with(I, fun(Js) -> lists:umerge([J], Js) end, [J], G)
|
|
end, maps:from_list([ {I, []} || I <- Is ]), Es).
|
|
|
|
reverse_graph(G) ->
|
|
from_edges(maps:keys(G), [ {J, I} || {I, Js} <- maps:to_list(G), J <- Js ]).
|
|
|