Skip to content

Commit

Permalink
better cli and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielegenovese committed Jan 29, 2025
1 parent 2977c8a commit 06b3686
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 51 deletions.
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,25 @@ The project requires `erlang` and `rebar3` to run.

## Usage

Use `rebar3` to run the program from the command line or to compile it.
Use `rebar3` to compile it and run the binary:

```erlang
# rebar3 escriptize
# ./_build/default/bin/chorer ./path/to/your/program.erl main/0 path/to/folder
# ./_build/default/bin/chorer
Usage:
chorer <input> <entrypoint> <output> <minl> <ming>

Extract a choreography automata of an Erlang program.

Arguments:
input Erlang soure file (string)
entrypoint Entrypoint of the program (atom)
output Output directory for the generated dot files (string), default: .
minl Minimize the localviews , default: true
ming Minimize the globalviews , default: false
```

> A fourth and fifth optional parameter are used to specify if the the local view or the global view should be minimized. Default values are `true` for the local view and `false` for the global view.

or
Otherwise, use it with the Erlang shell:

```erlang
# rebar3 shell
Expand All @@ -27,11 +36,11 @@ finished

### Output

The tool will create a DOT file for each actor's local view and a DOT file for the global view in the specified folder. To visualize the Choreography Automatas copy and paste the `.dot` files' content in a [DOT viewer](https://dreampuf.github.io/GraphvizOnline).
The tool will create a DOT file for each actor's local view and a DOT file for the global view in the specified folder. To visualize the Choreography Automata copy and paste the `.dot` files' content in a [DOT viewer](https://dreampuf.github.io/GraphvizOnline).

## Documentation

The documentation of the project is aviable at this [link](https://gabrielegenovese.github.io/chorer/). You can also generete it with
The documentation of the project is available at this [link](https://gabrielegenovese.github.io/chorer/). You can also generate it with

```erlang
# rebar3 ex_doc --output docs
Expand Down
48 changes: 24 additions & 24 deletions src/choreography/gv.erl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ generate_possible_branches(NewBranchData, BaseBranchList) ->
%%% is receiving a message before the other procesess
maps:fold(
fun(Name, Pid, AccList) ->
% io:fwrite("[PROGSB] Name ~p MQ ~p~n", [Name, MessageQueue]),
% log:debug("[PROGSB] Name ~p MQ ~p~n", [Name, MessageQueue]),
case actor_emul:get_proc_mess_queue(Pid) of
?UNDEFINED -> AccList;
MessageQueue -> gen_branch_foreach_mess(NewBranchData, MessageQueue, Name, AccList)
Expand Down Expand Up @@ -139,14 +139,14 @@ manage_matched(BranchData, ProcName, Message, AccList, EdgesFound) ->
lists:foldl(
fun(EdgeFound, SecAccList) ->
DupData = dup_branch(BranchData),
% io:fwrite("[RECV1] Mess ~p Edge choose ~p~n", [Message, EdgeFound]),
% log:debug("[RECV1] Mess ~p Edge choose ~p~n", [Message, EdgeFound]),
NewMap = DupData#branch.proc_pid_m,
NewPid = maps:get(ProcName, NewMap, no_proc),
ProcFrom = Message#message.from,
MessData = Message#message.data,
PidFrom = maps:get(ProcFrom, NewMap, no_proc),
Label = format_send_label(ProcFrom, ProcName, MessData),
% io:fwrite("[RECV2] LABEL ~ts~n~n", [Label]),
% log:debug("[RECV2] LABEL ~ts~n~n", [Label]),
ProcFromData = actor_emul:get_proc_data(PidFrom),
case ProcFromData of
?UNDEFINED ->
Expand Down Expand Up @@ -384,7 +384,7 @@ is_one_edgerecv(ProcPid, EL) ->
%%% Evaluate a transition from an actor
eval_edge(EdgeInfo, ProcName, ProcPid, BData) ->
{Edge, _, _, PLabel} = EdgeInfo,
% io:fwrite("Proc ~p eval label ~p~n", [ProcName, PLabel]),
% log:debug("Proc ~p eval label ~p~n", [ProcName, PLabel]),
SLabel = share:atol(PLabel),
% IsArg = is_substring(SLabel, "arg"),
IsSpawn = is_substring(SLabel, "spawn"),
Expand Down Expand Up @@ -431,12 +431,12 @@ add_spawn_to_global(EInfo, SLabel, EmulProcName, Data) ->
% get spawn argument and add them to the local variables of the actor
LocalList = get_local_vars(EmulProcName, SLabel, FunSName),
lists:foreach(fun(Var) -> actor_emul:add_proc_spawnvars(FuncPid, Var) end, LocalList),
% io:fwrite("LocalList ~p~n", [LocalList]),
% log:debug("LocalList ~p~n", [LocalList]),
% create the edge on the global graph
VNew = share:add_vertex(Data#branch.graph),
[{_, StateM}] = ets:lookup(?DBMANAGER, global_state),
AggrGState = create_gv_state(NewMap, share:ltoa(FunSpawned), 1, EmulProcName, PV),
% io:fwrite("SPAWN AGGR ~p~n", [AggrGState]),
% log:debug("SPAWN AGGR ~p~n", [AggrGState]),
ets:insert(?DBMANAGER, {global_state, maps:put(VNew, AggrGState, StateM)}),
NewLabel = format_spawn_label(SLabel, EmulProcName, FunSpawned),
digraph:add_edge(Data#branch.graph, Data#branch.last_vertex, VNew, NewLabel),
Expand All @@ -458,14 +458,14 @@ format_spawn_label(SLabel, EmulProcName, FunSpawned) ->
get_local_vars(ProcId, Label, FunSName) ->
EM = db:get_lv_edge_additonal_info(element(1, remove_id_from_proc(ProcId))),
InputData = maps:get(Label, EM, []),
% io:fwrite("[GV] EmulProcName ~p Label ~p Input ~p~n", [FunSName, Label, EM]),
% log:debug("[GV] EmulProcName ~p Label ~p Input ~p~n", [FunSName, Label, EM]),
% add input data to local vars
case InputData of
[] ->
[];
_ ->
[{_, Input}] = ets:lookup(?ARGUMENTS, share:atol(FunSName)),
% io:fwrite("[GV] for fun ~p found ~p~n", [share:atol(FunSName), Input]),
% log:debug("[GV] for fun ~p found ~p~n", [share:atol(FunSName), Input]),
{LL, Remain} = lists:foldl(
fun({var, _, Name}, {A, In}) ->
case In of
Expand Down Expand Up @@ -521,7 +521,7 @@ check_send_to_not_existing_proc(Proc, ProcName) ->
S1 = string:split(SProc, ?ARITYSEP),
case S1 of
[_Name | T] when Check ->
% io:fwrite("[CHECK] Name ~p~n", [Name]),
% log:debug("[CHECK] Name ~p~n", [Name]),
S2 = string:split(share:atol(T), ?NSEQSEP),
case S2 of
[_Arity | _Seq] ->
Expand Down Expand Up @@ -549,7 +549,7 @@ manage_recv(ProcPid, Message) ->
{_, EdgeList} ->
%%% TODO: change to all when false branch is ready
IsRecvList = is_one_edgerecv(ProcPid, EdgeList),
% io:fwrite("IsRECV ~p EL ~p~n", [IsRecv, EL]),
% log:debug("IsRECV ~p EL ~p~n", [IsRecv, EL]),
case IsRecvList of
false ->
% TODO: manage when is not ONLY a receive edge, like:
Expand Down Expand Up @@ -635,7 +635,7 @@ custom_sort_edges(ProcPid, EL) ->
EL
),
Sort = lists:sort(fun({N1, _, _}, {N2, _, _}) -> N1 < N2 end, SepE),
% io:fwrite("sorted ~p~n", [Sort]),
% log:debug("sorted ~p~n", [Sort]),
[E || {_, _, E} <- Sort].

%%% Find the actor id from a variable's list, given the variable name
Expand All @@ -645,14 +645,14 @@ check_vars(ProcPid, VarName) ->
?UNDEFINED ->
VarName;
_ ->
% io:fwrite("Find var ~p in ~p pid ~p~n", [VarName, ProcLocalVars, ProcPid]),
% log:debug("Find var ~p in ~p pid ~p~n", [VarName, ProcLocalVars, ProcPid]),
VarValue = share:find_var(ProcLocalVars, VarName),
% io:fwrite("Found ~p~n", [VarValue]),
% log:debug("Found ~p~n", [VarValue]),
case VarValue of
not_found ->
VarName;
V ->
% io:fwrite("Found ~p~n", [V#variable.type]),
% log:debug("Found ~p~n", [V#variable.type]),
case V#variable.type of
"pid" -> V#variable.value;
pid -> V#variable.value;
Expand All @@ -674,7 +674,7 @@ is_pm_msg_compatible(ProcPid, CallingProc, PatternMatching, Message) ->
{IsCompatible, ToRegisterList} = check_msg_comp(
ProcPid, CallingProc, PatternMS, Message#message.data
),
% io:fwrite("Reg List ~p~n", [ToRegisterList]),
% log:debug("Reg List ~p~n", [ToRegisterList]),
lists:foreach(
fun(Item) -> register_var(Item) end,
ToRegisterList
Expand Down Expand Up @@ -738,12 +738,12 @@ register_var(Data) ->
{ProcPid, Name, Value} = Data,
%%% type = pid to change, for now it's ok like this because I only focus on pid exchange
V = #variable{name = share:ltoa(Name), type = pid, value = share:ltoa(Value)},
% io:fwrite("Added Var ~p~n", [V]),
% log:debug("Added Var ~p~n", [V]),
actor_emul:add_proc_localvars(ProcPid, V).

%%% Substitute pif_self to pid_ProcId
check_pid_self(Data, ProcId) ->
% io:fwrite("[C]Data ~p proc id ~p~n", [Data, ProcId]),
% log:debug("[C]Data ~p proc id ~p~n", [Data, ProcId]),
lists:flatten(string:replace(share:atol(Data), "pid_self", share:atol(ProcId))).

%%% Add a vertex to the global view, with some checks for recusive edges
Expand All @@ -753,26 +753,26 @@ check_pid_self(Data, ProcId) ->
complex_add_vertex_recv(Proc1, CurrVertex, Proc2, EdgeInfo, Data, Label) ->
ProcPid = Data#branch.proc_pid_m,
[{_, StateM}] = ets:lookup(?DBMANAGER, global_state),
% io:fwrite("stateM ~p~n", [StateM]),
% io:fwrite("Label ~p~n", [share:ltoa(Label)]),
% log:debug("stateM ~p~n", [StateM]),
% log:debug("Label ~p~n", [share:ltoa(Label)]),
VLast = Data#branch.last_vertex,
G = Data#branch.graph,
{_, _PV1, PV2, _} = EdgeInfo,
EL = digraph:out_edges(G, VLast),
SameL = check_same_label(G, EL, VLast, Label),
% io:fwrite("~n~n[COMPLEX] new check~n", []),
% log:debug("~n~n[COMPLEX] new check~n", []),
AggregateGlobalState = create_gv_state(ProcPid, Proc1, CurrVertex, Proc2, PV2),
case check_if_exist(StateM, AggregateGlobalState) of
nomatch ->
VNew = share:add_vertex(G),
digraph:add_edge(G, VLast, VNew, Label),
% io:fwrite("[DEBUG] Adding new global state ~p~n", [VNew]),
% log:debug("[DEBUG] Adding new global state ~p~n", [VNew]),
NewM = maps:put(VNew, AggregateGlobalState, StateM),
ets:insert(?DBMANAGER, {global_state, NewM}),
empty_filter_all_proc(Data#branch.proc_pid_m),
{VNew, true};
VFound ->
% io:fwrite("[EXTERNFOUND] ~p~n", [VFound]),
% log:debug("[EXTERNFOUND] ~p~n", [VFound]),
case SameL of
nomatch ->
digraph:add_edge(G, VLast, VFound, Label),
Expand Down Expand Up @@ -828,10 +828,10 @@ check_if_exist(StateM, AggregateGlobalState) ->
Acc =:= nomatch ->
%%% Check if the set has the same elements
Cond = Value =:= AggregateGlobalState,
% io:fwrite("[SUB] ~p~n", [Cond]),
% log:debug("[SUB] ~p~n", [Cond]),
case Cond of
true ->
% io:fwrite("[FOUND] FOUND n ~p ~n", [Key]),
% log:debug("[FOUND] FOUND n ~p ~n", [Key]),
Key;
false ->
Acc
Expand Down
56 changes: 36 additions & 20 deletions src/chorer.erl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
% #!/usr/bin/env escript

%%%------------------------------------------------------------------------------
%%% @doc
%%% The main module of the program.
Expand All @@ -8,41 +10,55 @@
-include("share/common_data.hrl").

%%% API
-export([main/1, generate/2, generate/5]).
-export([main/1, generate/5]).

%%%===================================================================
%%% API
%%%===================================================================

%%% @doc
%%% Function called when the tool is used from the CLI (Command Line Interface).
main([InputFile, EntryPoint, OutputDir] = _Args) ->
generate(InputFile, share:ltoa(EntryPoint), OutputDir, true, false);
main([InputFile, EntryPoint, OutputDir, Minimize] = _Args) ->
generate(InputFile, share:ltoa(EntryPoint), OutputDir, share:ltoa(Minimize), false);
main([InputFile, EntryPoint, OutputDir, MiniL, MinG] = _Args) ->
generate(InputFile, share:ltoa(EntryPoint), OutputDir, share:ltoa(MiniL), share:ltoa(MinG)).
%%% Define the cli arguments and run the program (using argparse).
main(Args) ->
argparse:run(Args, cli(), #{progname => ?MODULE}).

%%% @doc
%%% Generate the localviews and the globalview with base settings.
-spec generate(InputFile, EntryPoint) -> atom() when
InputFile :: string(),
EntryPoint :: atom().
generate(InputFile, EntryPoint) ->
generate(InputFile, EntryPoint, filename:dirname(InputFile), true, false).
cli() ->
#{
arguments => [
#{name => input, type => string, help => "Erlang soure file"},
#{name => entrypoint, type => {atom, unsafe}, help => "Entrypoint of the program"},
#{name => output, type => string, default => ".", help => "Output directory for the generated dot files"},
#{name => minl, type => boolean, default => true, help => "Minimize the localviews"},
#{name => ming, type => boolean, default => false, help => "Minimize the globalviews"}
],
help=> """
Extract a choreography automata of an Erlang program.
""",
handler =>
fun(
#{
input := InputFile,
entrypoint := EntryPoint,
output := OutputDir,
minl := MinL,
ming := MinG
}
) ->
generate(InputFile, EntryPoint, OutputDir, MinL, MinG)
end
}.

%%% @doc
%%% Generate the localviews and the globalview specifing the output directory.
%%% It initialize the ets tables and generates the localviews and globalview.
-spec generate(InputFile, EntryPoint, OutDir, MiniL, MinG) -> atom() when
-spec generate(InputFile, EntryPoint, OutDir, MinL, MinG) -> atom() when
InputFile :: string(),
EntryPoint :: atom(),
OutDir :: string(),
MiniL :: boolean(),
MinL :: boolean(),
MinG :: boolean().
generate(InputFile, EntryPoint, OutDir, MiniL, MinG) ->
io:fwrite("Analysing ~p, entrypoint: ~p~n", [InputFile, EntryPoint]),
Settings = settings:new_settings(InputFile, EntryPoint, OutDir, MiniL, MinG),
generate(InputFile, EntryPoint, OutDir, MinL, MinG) ->
io:fwrite("Analysing ~p, entrypoint: ~p, output: ~p~n", [InputFile, EntryPoint, OutDir]),
Settings = settings:new_settings(InputFile, EntryPoint, OutDir, MinL, MinG),
db:init(Settings),
md:extract(),
NoError = lv:generate(),
Expand Down

0 comments on commit 06b3686

Please sign in to comment.