sophia/src/aeso_utils.erl
Hans Svensson 03d6dd6ca2
Improve resolution of relative includes (#489)
* 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
2023-09-14 15:00:30 +02:00

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 ]).