├── case-studies ├── barber │ ├── trace │ │ ├── trace_80.log │ │ ├── trace_79.log │ │ ├── trace_result.log │ │ ├── trace_74.log │ │ ├── trace_81.log │ │ └── trace_78.log │ ├── info.txt │ ├── barber.erl │ └── barber_fixed.erl ├── purchase │ ├── trace │ │ ├── trace_68.log │ │ ├── trace_69.log │ │ ├── trace_70.log │ │ ├── trace_67.log │ │ ├── trace_result.log │ │ └── trace_63.log │ ├── info.txt │ ├── purchase.erl │ └── purchase_fixed.erl ├── airline │ ├── trace │ │ ├── trace_result.log │ │ ├── trace_67.log │ │ ├── trace_68.log │ │ └── trace_63.log │ ├── costIC1405bookChapterDebugging.pdf │ ├── info.txt │ └── airline.erl ├── tcp │ ├── info.txt │ ├── tcp.erl │ ├── tcp-fixed2.erl │ └── tcp-fixed1.erl └── info.txt ├── .gitignore ├── examples ├── sum.erl ├── prova.erl ├── sum copy.erl ├── fact.erl ├── factorial.erl ├── jump2.erl ├── jump1.erl ├── auto.erl ├── case.erl ├── provaMod.erl ├── sum_tuple copy.erl ├── exPat.erl ├── sum_tuple.erl ├── child.erl ├── acknowledge.erl ├── ping.erl ├── client_server.erl ├── hello_world.erl ├── proxy_server.erl ├── airline.erl ├── mergesort.erl ├── tcp.erl ├── tcpFixed.erl ├── dining_ex.erl ├── dining.erl ├── dining_bug.erl ├── dining_simple.erl └── dining_simple_bug.erl ├── Makefile ├── src ├── sched.erl ├── tracer.erl ├── cauder.hrl ├── et_viewer.erl ├── cauder_gui.hrl ├── cauder.erl ├── bwd_sem.erl ├── roll.erl ├── utils_gui.erl ├── License_et_viewer.txt ├── fwd_sem.erl ├── utils.erl └── cauder_gui.erl ├── README.md └── LICENSE /case-studies/barber/trace/trace_80.log: -------------------------------------------------------------------------------- 1 | {80,send,1} 2 | -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_68.log: -------------------------------------------------------------------------------- 1 | {68,send,0} 2 | -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_69.log: -------------------------------------------------------------------------------- 1 | {69,send,1} 2 | -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_70.log: -------------------------------------------------------------------------------- 1 | {70,send,2} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.beam 3 | *.tex 4 | *.log 5 | *.aux 6 | -------------------------------------------------------------------------------- /examples/sum.erl: -------------------------------------------------------------------------------- 1 | -module(sum). 2 | -export([main/2]). 3 | 4 | main(X, Y) -> X + Y. 5 | -------------------------------------------------------------------------------- /case-studies/barber/trace/trace_79.log: -------------------------------------------------------------------------------- 1 | {79,send,0} 2 | {79,'receive',11} 3 | {79,send,13} 4 | -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_67.log: -------------------------------------------------------------------------------- 1 | {67,'receive',1} 2 | {67,'receive',2} 3 | {67,send,3} 4 | -------------------------------------------------------------------------------- /case-studies/airline/trace/trace_result.log: -------------------------------------------------------------------------------- 1 | call "airline:main()" 2 | main_pid 63 3 | result none 4 | -------------------------------------------------------------------------------- /case-studies/barber/trace/trace_result.log: -------------------------------------------------------------------------------- 1 | call "barber:main()" 2 | main_pid 74 3 | result none 4 | -------------------------------------------------------------------------------- /examples/prova.erl: -------------------------------------------------------------------------------- 1 | -module(prova). 2 | -export([quad/1]). 3 | 4 | quad(N) -> provaMod:inc(N)*N. 5 | -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_result.log: -------------------------------------------------------------------------------- 1 | call "purchase:main()" 2 | main_pid 63 3 | result false 4 | -------------------------------------------------------------------------------- /examples/sum copy.erl: -------------------------------------------------------------------------------- 1 | -module(sum). 2 | -export([main/0]). 3 | 4 | main() -> {X,Y} = {1,2}, 5 | X + Y. 6 | -------------------------------------------------------------------------------- /case-studies/barber/trace/trace_74.log: -------------------------------------------------------------------------------- 1 | {74,spawn,78} 2 | {74,spawn,79} 3 | {74,spawn,80} 4 | {74,'receive',13} 5 | -------------------------------------------------------------------------------- /examples/fact.erl: -------------------------------------------------------------------------------- 1 | -module(fact). 2 | -export([fact/1]). 3 | 4 | fact(0) -> 1; 5 | fact(N) when N>0 -> N * fact(N-1). 6 | -------------------------------------------------------------------------------- /examples/factorial.erl: -------------------------------------------------------------------------------- 1 | -module(factorial). 2 | -export([fact/1]). 3 | 4 | fact(0) -> 1; 5 | fact(N) -> N * fact(N-1). 6 | -------------------------------------------------------------------------------- /examples/jump2.erl: -------------------------------------------------------------------------------- 1 | -module(jump2). 2 | -export([main/0]). 3 | 4 | main() -> 5 | local_main(). 6 | 7 | local_main() -> ok2. -------------------------------------------------------------------------------- /case-studies/purchase/trace/trace_63.log: -------------------------------------------------------------------------------- 1 | {63,spawn,67} 2 | {63,spawn,68} 3 | {63,spawn,69} 4 | {63,spawn,70} 5 | {63,'receive',3} 6 | -------------------------------------------------------------------------------- /examples/jump1.erl: -------------------------------------------------------------------------------- 1 | -module(jump1). 2 | -export([main/0]). 3 | 4 | main() -> 5 | jump2:main(), 6 | local_main(). 7 | 8 | local_main() -> ok1. -------------------------------------------------------------------------------- /examples/auto.erl: -------------------------------------------------------------------------------- 1 | -module(auto). 2 | -export([main/0]). 3 | 4 | main() -> 5 | self() ! hello, 6 | receive 7 | hello -> ok; 8 | _ -> not_ok 9 | end. -------------------------------------------------------------------------------- /case-studies/airline/costIC1405bookChapterDebugging.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mistupv/cauder-core/HEAD/case-studies/airline/costIC1405bookChapterDebugging.pdf -------------------------------------------------------------------------------- /examples/case.erl: -------------------------------------------------------------------------------- 1 | -module(ex). 2 | -export([ex/0]). 3 | 4 | ex() -> 5 | X = 1, 6 | Y = 1, 7 | case X of 8 | Y -> true; 9 | _ -> false 10 | end. 11 | -------------------------------------------------------------------------------- /examples/provaMod.erl: -------------------------------------------------------------------------------- 1 | -module(provaMod). 2 | -export([main/0,inc/1]). 3 | 4 | main() -> 5 | X=prova:quad(5), 6 | inc(X). 7 | 8 | inc(X) -> 9 | X+1. 10 | -------------------------------------------------------------------------------- /examples/sum_tuple copy.erl: -------------------------------------------------------------------------------- 1 | -module(sum_tuple). 2 | -export([main/2, sum_tup/1]). 3 | 4 | main(X,Y) -> 5 | Tup = {X,Y}, 6 | sum_tup(Tup). 7 | 8 | sum_tup({X,Y}) -> X + Y. 9 | -------------------------------------------------------------------------------- /case-studies/airline/info.txt: -------------------------------------------------------------------------------- 1 | Example described in the chapter on debugging in the final book of 2 | COST Action 1405, which is to appear. 3 | A copy of the chapter is available in this directory. 4 | 5 | 6 | -------------------------------------------------------------------------------- /case-studies/airline/trace/trace_67.log: -------------------------------------------------------------------------------- 1 | {67,send,0} 2 | {67,'receive',2} 3 | {67,send,4} 4 | {67,'receive',6} 5 | {67,send,7} 6 | {67,'receive',9} 7 | {67,send,11} 8 | {67,'receive',14} 9 | {67,send,15} 10 | {67,'receive',17} 11 | -------------------------------------------------------------------------------- /case-studies/airline/trace/trace_68.log: -------------------------------------------------------------------------------- 1 | {68,send,1} 2 | {68,'receive',3} 3 | {68,send,5} 4 | {68,'receive',8} 5 | {68,send,10} 6 | {68,'receive',12} 7 | {68,send,13} 8 | {68,'receive',16} 9 | {68,send,18} 10 | {68,'receive',19} 11 | -------------------------------------------------------------------------------- /examples/exPat.erl: -------------------------------------------------------------------------------- 1 | -module(exPat). 2 | -export([ack/2,main/0]). 3 | 4 | main() -> 5 | self()!{1,2}, 6 | ack(1,2). 7 | 8 | ack(N,M) -> 9 | receive 10 | {N,M} -> 11 | true; 12 | _ -> false 13 | end. 14 | -------------------------------------------------------------------------------- /case-studies/barber/trace/trace_81.log: -------------------------------------------------------------------------------- 1 | {81,send,2} 2 | {81,'receive',3} 3 | {81,send,5} 4 | {81,'receive',4} 5 | {81,send,7} 6 | {81,'receive',6} 7 | {81,send,9} 8 | {81,send,10} 9 | {81,'receive',8} 10 | {81,send,12} 11 | {81,send,14} 12 | -------------------------------------------------------------------------------- /case-studies/purchase/info.txt: -------------------------------------------------------------------------------- 1 | Example described in 2 | 3 | Elena Giachino, Ivan Lanese, Claudio Antares Mezzina: 4 | Causal-Consistent Reversible Debugging. FASE 2014: 370-384 5 | 6 | in the context of language muOz, here translated into Erlang. 7 | -------------------------------------------------------------------------------- /examples/sum_tuple.erl: -------------------------------------------------------------------------------- 1 | -module(sum_tuple). 2 | -export([main/2, sum_tuple/1]). 3 | 4 | main(X, Y) -> 5 | Tuple = {X,Y}, 6 | spawn(?MODULE, sum_tuple, [Tuple, Y]). 7 | 8 | sum_tuple({X,Y}) -> X + Y. 9 | 10 | sum_tuple({X,Y}, Z) -> X + Y + Z. 11 | -------------------------------------------------------------------------------- /examples/child.erl: -------------------------------------------------------------------------------- 1 | -module(child). 2 | -export([main/0, child/0]). 3 | 4 | main() -> 5 | Child = spawn(?MODULE, child, []), 6 | Child ! {self(), hello}, 7 | receive 8 | hello2 -> ok 9 | end. 10 | 11 | child() -> 12 | receive 13 | {Parent, hello} -> Parent ! hello2 14 | end, 15 | main(). -------------------------------------------------------------------------------- /case-studies/barber/trace/trace_78.log: -------------------------------------------------------------------------------- 1 | {78,spawn,81} 2 | {78,'receive',0} 3 | {78,send,3} 4 | {78,'receive',1} 5 | {78,send,4} 6 | {78,'receive',2} 7 | {78,send,6} 8 | {78,'receive',5} 9 | {78,send,8} 10 | {78,'receive',7} 11 | {78,'receive',9} 12 | {78,send,11} 13 | {78,'receive',10} 14 | {78,'receive',12} 15 | {78,send,15} 16 | {78,'receive',14} 17 | -------------------------------------------------------------------------------- /examples/acknowledge.erl: -------------------------------------------------------------------------------- 1 | -module(acknowledge). 2 | -export([main/0, other_main/0]). 3 | 4 | main() -> 5 | MyPid = self(), 6 | OtherPid = spawn(?MODULE, other_main, []), 7 | OtherPid ! MyPid, 8 | receive 9 | ack -> ok; 10 | _ -> not_ok 11 | end. 12 | 13 | other_main() -> 14 | receive 15 | Pid -> Pid ! ack 16 | end. 17 | -------------------------------------------------------------------------------- /examples/ping.erl: -------------------------------------------------------------------------------- 1 | -module(ping). 2 | -export([main/0,ping/1,pong/0]). 3 | 4 | 5 | main() -> Pong = spawn(ping,pong,[]), 6 | self() ! 3, ping(Pong). 7 | 8 | ping(Pong) -> receive 9 | kill -> done; 10 | N -> Pong ! {self(),N-1}, ping(Pong) 11 | end. 12 | 13 | pong() -> receive 14 | {S,0} -> S ! kill; 15 | {S,N} when N>0 -> S ! N, pong() 16 | end. -------------------------------------------------------------------------------- /examples/client_server.erl: -------------------------------------------------------------------------------- 1 | -module(client_server). 2 | -export([main/0, server/0, client/1]). 3 | 4 | main() -> 5 | S = spawn(?MODULE, server, []), 6 | spawn(?MODULE, client, [S]), 7 | client(S). 8 | 9 | server() -> 10 | receive 11 | {P, _} -> 12 | P ! ack, 13 | server() 14 | end. 15 | 16 | client(S) -> 17 | S ! {self(), req}, 18 | receive 19 | ack -> ok 20 | end. 21 | -------------------------------------------------------------------------------- /examples/hello_world.erl: -------------------------------------------------------------------------------- 1 | -module(hello_world). 2 | -export([main/0, target/0, echo/0]). 3 | 4 | main() -> 5 | P2 = spawn(?MODULE, echo, []), 6 | P3 = spawn(?MODULE, target, []), 7 | P3 ! hello, 8 | P2 ! {P3, world}. 9 | 10 | target() -> 11 | receive 12 | A -> 13 | receive 14 | B -> {A, B} 15 | end 16 | end. 17 | 18 | echo() -> 19 | receive 20 | {P, M} -> P ! M 21 | end. 22 | -------------------------------------------------------------------------------- /case-studies/airline/trace/trace_63.log: -------------------------------------------------------------------------------- 1 | {63,spawn,67} 2 | {63,spawn,68} 3 | {63,'receive',0} 4 | {63,send,2} 5 | {63,'receive',1} 6 | {63,send,3} 7 | {63,'receive',4} 8 | {63,send,6} 9 | {63,'receive',5} 10 | {63,send,8} 11 | {63,'receive',7} 12 | {63,send,9} 13 | {63,'receive',10} 14 | {63,send,12} 15 | {63,'receive',11} 16 | {63,send,14} 17 | {63,'receive',13} 18 | {63,send,16} 19 | {63,'receive',15} 20 | {63,send,17} 21 | {63,'receive',18} 22 | {63,send,19} 23 | -------------------------------------------------------------------------------- /case-studies/tcp/info.txt: -------------------------------------------------------------------------------- 1 | Example described in 2 | 3 | Rafael Caballero, Enrique Martin-Martin, Adrián Riesco, Salvador Tamarit: 4 | Declarative debugging of concurrent Erlang programs. J. Log. Algebraic Methods Program. 101: 22-41 (2018) 5 | 6 | The file tcp.erl contains two bugs, the first one fixed in 7 | tcp-fixed1.erl and both fixed in tcp-fixed2.erl 8 | 9 | Traces exercising the bugs are not currently available (the bugs are 10 | nondeterministic). 11 | 12 | -------------------------------------------------------------------------------- /examples/proxy_server.erl: -------------------------------------------------------------------------------- 1 | -module(proxy_server). 2 | -export([main/0, server/0, proxy/1]). 3 | 4 | main() -> 5 | S = spawn(?MODULE, server, []), 6 | P = spawn(?MODULE, proxy, [S]), 7 | P ! {self(), req}, 8 | receive 9 | result -> ok 10 | end. 11 | 12 | proxy(SPid) -> 13 | receive 14 | {CPid, req} -> 15 | SPid ! {self(), req}, 16 | receive 17 | result -> CPid ! result 18 | end 19 | end. 20 | 21 | server() -> 22 | receive 23 | {PPid, req} -> 24 | PPid ! result 25 | end. 26 | 27 | -------------------------------------------------------------------------------- /case-studies/airline/airline.erl: -------------------------------------------------------------------------------- 1 | -module(airline). 2 | -export([main/0,agent/2]). 3 | 4 | main() -> 5 | Main = self(), 6 | spawn(?MODULE, agent ,[1,Main]), 7 | spawn(?MODULE, agent, [2,Main]), 8 | seats(3). 9 | 10 | 11 | seats(Num) -> 12 | receive 13 | {numOfSeats,Pid} -> Pid ! {seats,Num}, seats(Num); 14 | {sell,Pid} -> io:format("Seat sold!~n"), Pid!{booked,Num},seats(Num-1) 15 | end. 16 | 17 | agent(NAg,Pid) -> 18 | Pid ! {numOfSeats,self()}, 19 | receive 20 | {seats,Num} when Num > 0 -> Pid ! {sell,self()}, 21 | receive {booked,_} -> agent(NAg,Pid) end; 22 | _ -> io:format("Agent~p done!~n",[NAg]) 23 | end. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | compile: clean 3 | @mkdir ebin 4 | @erlc -o ebin src/*.erl 5 | @$(MAKE) script 6 | 7 | script: 8 | @echo "erl -noshell -pa ebin -eval \"cauder:start().\" -s init stop" > cauder.sh 9 | @chmod +x cauder.sh 10 | 11 | docs: clean-docs 12 | @erl -noshell -run edoc_run files '["src/cauder.erl", \ 13 | "src/fwd_sem.erl", \ 14 | "src/bwd_sem.erl", \ 15 | "src/utils.erl"]' '[{dir, "docs"}]' 16 | 17 | clean: clean-docs 18 | @rm -Rf ebin 19 | 20 | clean-docs: 21 | @rm -Rf docs 22 | 23 | debug: clean 24 | @mkdir ebin 25 | @erlc -Ddebug -o ebin src/*.erl 26 | @$(MAKE) script 27 | -------------------------------------------------------------------------------- /src/sched.erl: -------------------------------------------------------------------------------- 1 | -module(sched). 2 | -export([select_opt/2]). 3 | 4 | -include("cauder.hrl"). 5 | 6 | select_opt(Sem, System) -> 7 | Sched = System#sys.sched, 8 | select_opt(Sem, System, Sched). 9 | 10 | select_opt(Sem, System, ?SCHED_RANDOM) -> 11 | Opts = Sem:eval_opts(System), 12 | select_rand(Opts); 13 | select_opt(Sem, System, ?SCHED_PRIO_RANDOM) -> 14 | Opts = 15 | case Sem:eval_procs_opts(System) of 16 | [] -> Sem:eval_sched_opts(System); 17 | ProcsOpts -> ProcsOpts 18 | end, 19 | select_rand(Opts). 20 | 21 | select_rand(Opts) -> 22 | case Opts of 23 | [] -> none; 24 | _ -> 25 | RandIdx = rand:uniform(length(Opts)), 26 | lists:nth(RandIdx, Opts) 27 | end. -------------------------------------------------------------------------------- /examples/airline.erl: -------------------------------------------------------------------------------- 1 | %airline example 2 | 3 | -module(airline). 4 | -export([main/0,seatManager/1,booker/1]). 5 | 6 | main() -> Pid=spawn(?MODULE,seatManager,[3]), 7 | spawn(?MODULE,booker,[Pid]), 8 | spawn(?MODULE,booker,[Pid]). 9 | 10 | seatManager(NumOfSeats) -> 11 | receive {askForNum,Pid} -> 12 | Pid!{num,NumOfSeats},seatManager(NumOfSeats); 13 | {book,Pid} -> io:format("Booked seat number ~w ~n",[NumOfSeats]),Pid!{booked,NumOfSeats},seatManager(NumOfSeats-1) 14 | end. 15 | 16 | booker(Pid) -> Pid!{askForNum,self()}, 17 | receive {num,NumOfSeats} when 18 | NumOfSeats > 0 -> Pid!{book,self()}, 19 | receive {booked,N} -> booker(Pid) 20 | end; 21 | {num,0} -> 0 22 | end. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CauDEr 2 | 3 | A causal-consistent debugger for Erlang 4 | 5 | ## Dependencies 6 | 7 | This project uses [wx](http://erlang.org/doc/apps/wx/chapter.html), the Erlang binding of wxWidgets. 8 | Thus, you must have [built Erlang/OTP with wxWidgets](http://erlang.org/doc/installation_guide/INSTALL.html#Advanced-configuration-and-build-of-ErlangOTP_Building_Building-with-wxErlang). 9 | 10 | ## Compilation 11 | 12 | First, compile the project: 13 | ``` 14 | make 15 | ``` 16 | Then, execute the script *cauder.sh* to start CauDEr 17 | ``` 18 | ./cauder.sh 19 | ``` 20 | An astonishing graphical interface will appear in your screen. 21 | 22 | ![GUI screenshot](https://github.com/mistupv/cauder-core/blob/screens/cauder.png) 23 | 24 | ## How to use 25 | 26 | Check the [Wiki](https://github.com/mistupv/cauder-core/wiki) for information on how to use CauDEr. 27 | -------------------------------------------------------------------------------- /case-studies/purchase/purchase.erl: -------------------------------------------------------------------------------- 1 | %FASE 2014 example 2 | 3 | -module(purchase). 4 | -export([main/0,asynchAnd/2,checkCredit/2,checkAddress/1,checkItem/1]). 5 | 6 | main() -> Pid=spawn(?MODULE,asynchAnd,[2,self()]), 7 | spawn(?MODULE,checkCredit,[15,Pid]), 8 | spawn(?MODULE,checkAddress,[Pid]), 9 | spawn(?MODULE,checkItem,[Pid]), 10 | receive 11 | true -> io:format("All checks successful.~n"),true; 12 | false -> io:format("Check failure.~n"),false 13 | end. 14 | 15 | asynchAnd(N,Out) -> 16 | if 17 | N > 0 -> receive 18 | true -> asynchAnd(N-1,Out); 19 | false -> Out!false 20 | end; 21 | true -> Out!true 22 | end. 23 | 24 | checkCredit(Price,Pid) -> if 25 | Price < 10 -> io:format("Credit check successful~n"),Pid!true; 26 | true -> io:format("Credit check failed~n"),Pid!false 27 | end. 28 | 29 | checkAddress(Pid) -> io:format("Address check successful~n"),Pid!true. 30 | 31 | checkItem(Pid) -> io:format("Item check successful~n"),Pid!true. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /case-studies/purchase/purchase_fixed.erl: -------------------------------------------------------------------------------- 1 | %FASE 2014 example 2 | 3 | -module(purchase_fixed). 4 | -export([main/0,asynchAnd/2,checkCredit/2,checkAddress/1,checkItem/1]). 5 | 6 | main() -> Pid=spawn(?MODULE,asynchAnd,[3,self()]), 7 | spawn(?MODULE,checkCredit,[15,Pid]), 8 | spawn(?MODULE,checkAddress,[Pid]), 9 | spawn(?MODULE,checkItem,[Pid]), 10 | receive 11 | true -> io:format("All checks successful.~n"),true; 12 | false -> io:format("Check failure.~n"),false 13 | end. 14 | 15 | asynchAnd(N,Out) -> 16 | if 17 | N > 0 -> receive 18 | true -> asynchAnd(N-1,Out); 19 | false -> Out!false 20 | end; 21 | true -> Out!true 22 | end. 23 | 24 | checkCredit(Price,Pid) -> if 25 | Price < 10 -> io:format("Credit check successful~n"),Pid!true; 26 | true -> io:format("Credit check failed~n"),Pid!false 27 | end. 28 | 29 | checkAddress(Pid) -> io:format("Address check successful~n"),Pid!true. 30 | 31 | checkItem(Pid) -> io:format("Item check successful~n"),Pid!true. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ivan Lanese, Naoki Nishida, Adrian Palacios and German Vidal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /case-studies/tcp/tcp.erl: -------------------------------------------------------------------------------- 1 | -module(tcp). 2 | -export([main/0, server_fun/3, client_fun/4, ack/5]). 3 | 4 | main() -> 5 | Server_PID = spawn(?MODULE, server_fun, [self(), 50, 500]), 6 | spawn(?MODULE, client_fun, [Server_PID, 57, 100, client1]), 7 | spawn(?MODULE, client_fun, [Server_PID, 50, 200, client2]), 8 | receive {data,D} -> D end. 9 | 10 | server_fun(Main_PID, Port, Seq) -> 11 | receive 12 | {Client_PID, {syn, Port, SeqCl}} -> 13 | Ack_PID = spawn(?MODULE, ack, [Main_PID, Port, SeqCl+1, Seq+1, Client_PID]), 14 | Client_PID ! {Ack_PID, {syn_ack, SeqCl+1, Seq}}, 15 | server_fun(Main_PID, Port, Seq+1); 16 | {Client_PID, {syn, _, _}} -> 17 | Client_PID ! rst 18 | end. 19 | 20 | ack(Main_PID, Port, Ack, Seq, Client_PID) -> 21 | receive 22 | {Client_PID, {ack, Port, Seq, Ack}} -> 23 | receive 24 | {Client_PID, {data, Port, D}} -> Main_PID ! {data, D}; 25 | _ -> Main_PID ! {data, error_data} 26 | end; 27 | _ -> Main_PID ! {data, error_ack} 28 | end. 29 | 30 | client_fun(Server_PID, Port, Seq, Data) -> 31 | Server_PID ! {self(), {syn, Port, Seq}}, syn_ack(Port, Data, Seq+1). 32 | 33 | syn_ack(Port, Data, Ack) -> 34 | receive 35 | rst -> {port_rejected, Port} ; 36 | {Ack_PID, {syn_ack, Ack, Seq}} -> 37 | Ack_PID ! {self(), {Ack, Port, Seq+1, ack}}, 38 | Ack_PID ! {self(), {data, Port, Data}}, 39 | {Seq+1, Ack, Port, Data} 40 | end. 41 | -------------------------------------------------------------------------------- /case-studies/tcp/tcp-fixed2.erl: -------------------------------------------------------------------------------- 1 | -module('tcp-fixed2'). 2 | -export([main/0, server_fun/3, client_fun/4, ack/5]). 3 | 4 | main() -> 5 | Server_PID = spawn(?MODULE, server_fun, [self(), 50, 500]), 6 | spawn(?MODULE, client_fun, [Server_PID, 57, 100, client1]), 7 | spawn(?MODULE, client_fun, [Server_PID, 50, 200, client2]), 8 | receive {data,D} -> D end. 9 | 10 | server_fun(Main_PID, Port, Seq) -> 11 | receive 12 | {Client_PID, {syn, Port, SeqCl}} -> 13 | Ack_PID = spawn(?MODULE, ack, [Main_PID, Port, SeqCl+1, Seq+1, Client_PID]), 14 | Client_PID ! {Ack_PID, {syn_ack, SeqCl+1, Seq}}, 15 | server_fun(Main_PID, Port, Seq+1); 16 | {Client_PID, {syn, _, _}} -> 17 | Client_PID ! rst, server_fun(Main_PID,Port,Seq+1) 18 | end. 19 | 20 | ack(Main_PID, Port, Ack, Seq, Client_PID) -> 21 | receive 22 | {Client_PID, {ack, Port, Seq, Ack}} -> 23 | receive 24 | {Client_PID, {data, Port, D}} -> Main_PID ! {data, D}; 25 | _ -> Main_PID ! {data, error_data} 26 | end; 27 | _ -> Main_PID ! {data, error_ack} 28 | end. 29 | 30 | client_fun(Server_PID, Port, Seq, Data) -> 31 | Server_PID ! {self(), {syn, Port, Seq}}, syn_ack(Port, Data, Seq+1). 32 | 33 | syn_ack(Port, Data, Ack) -> 34 | receive 35 | rst -> {port_rejected, Port} ; 36 | {Ack_PID, {syn_ack, Ack, Seq}} -> 37 | Ack_PID ! {self(), {ack, Port, Seq+1, Ack}}, 38 | Ack_PID ! {self(), {data, Port, Data}}, 39 | {Seq+1, Ack, Port, Data} 40 | end. 41 | -------------------------------------------------------------------------------- /case-studies/tcp/tcp-fixed1.erl: -------------------------------------------------------------------------------- 1 | -module('tcp-fixed1'). 2 | -export([main/0, server_fun/3, client_fun/4, ack/5]). 3 | 4 | main() -> 5 | Server_PID = spawn(?MODULE, server_fun, [self(), 50, 500]), 6 | spawn(?MODULE, client_fun, [Server_PID, 57, 100, client1]), 7 | spawn(?MODULE, client_fun, [Server_PID, 50, 200, client2]), 8 | receive {data,D} -> D end. 9 | 10 | server_fun(Main_PID, Port, Seq) -> 11 | receive 12 | {Client_PID, {syn, Port, SeqCl}} -> 13 | Ack_PID = spawn(?MODULE, ack, [Main_PID, Port, SeqCl+1, Seq+1, Client_PID]), 14 | Client_PID ! {Ack_PID, {syn_ack, SeqCl+1, Seq}}, 15 | server_fun(Main_PID, Port, Seq+1); 16 | {Client_PID, {syn, _, _}} -> 17 | Client_PID ! rst, server_fun(Main_PID,Port,Seq+1) % OK 18 | end. 19 | 20 | ack(Main_PID, Port, Ack, Seq, Client_PID) -> 21 | receive 22 | {Client_PID, {ack, Port, Seq, Ack}} -> 23 | receive 24 | {Client_PID, {data, Port, D}} -> Main_PID ! {data, D}; 25 | _ -> Main_PID ! {data, error_data} 26 | end; 27 | _ -> Main_PID ! {data, error_ack} 28 | end. 29 | 30 | client_fun(Server_PID, Port, Seq, Data) -> 31 | Server_PID ! {self(), {syn, Port, Seq}}, syn_ack(Port, Data, Seq+1). 32 | 33 | syn_ack(Port, Data, Ack) -> 34 | receive 35 | rst -> {port_rejected, Port} ; 36 | {Ack_PID, {syn_ack, Ack, Seq}} -> 37 | Ack_PID ! {self(), {Ack, Port, Seq+1, ack}}, 38 | Ack_PID ! {self(), {data, Port, Data}}, 39 | {Seq+1, Ack, Port, Data} 40 | end. 41 | -------------------------------------------------------------------------------- /examples/mergesort.erl: -------------------------------------------------------------------------------- 1 | %Works only with compiler optimizations disabled 2 | 3 | -module(mergesort). 4 | -export([mergesort/1, mergesort/2]). 5 | 6 | 7 | mergesort(L) -> 8 | mergesort(L, none). 9 | 10 | mergesort([], Parent) -> send_return([],Parent); 11 | mergesort([X], Parent) -> send_return([X],Parent); 12 | mergesort(L, Parent) -> 13 | Half = length(L) div 2, 14 | L1 = take(Half, L), 15 | L2 = last(length(L) - Half, L), 16 | spawn(?MODULE, mergesort , [L1, self()]), 17 | spawn(?MODULE, mergesort , [L2, self()]), 18 | LOrd1 = 19 | receive 20 | {result, LOrd1_} -> 21 | LOrd1_ 22 | end, 23 | LOrd2 = 24 | receive 25 | {result, LOrd2_} -> 26 | LOrd2_ 27 | end, 28 | send_return(merge(LOrd1, LOrd2), Parent). 29 | 30 | send_return(Result,none) -> 31 | Result; 32 | send_return(Result,Pid) -> 33 | Pid!{result, Result}. 34 | 35 | merge([], []) -> 36 | []; 37 | merge([], S2) -> 38 | S2; 39 | merge(S1, []) -> 40 | S1; 41 | merge([H1 | T1], [H2 | T2]) -> 42 | B = H1 < H2, 43 | case B of 44 | false -> [H2 | merge([H1 | T1], T2)]; % Correct 45 | %false -> [H1 | merge([H2 | T1], T2)]; % Incorrect 46 | true -> [H1 | merge(T1, [H2 | T2])] 47 | end. 48 | 49 | 50 | take(0,_) -> []; 51 | take(1,[H|_])->[H]; 52 | take(_,[])->[]; 53 | % take(N,[H|T])->[H | take(N-1, T)]. % Correct 54 | take(N,[_|T])->[N | take(N-1, T)]. % Incorrect 55 | 56 | last(N, List) -> 57 | lists:reverse(take(N, lists:reverse(List))). 58 | -------------------------------------------------------------------------------- /examples/tcp.erl: -------------------------------------------------------------------------------- 1 | -module(tcp). 2 | -export([main/0, server_fun/3, client_fun/4, ack/5]). 3 | 4 | main() -> 5 | Server_PID = spawn(?MODULE, server_fun, [self(), 50, 500]), 6 | spawn(?MODULE, client_fun, [Server_PID, 57, 100, client1]), 7 | spawn(?MODULE, client_fun, [Server_PID, 50, 200, client2]), 8 | receive {data,D} -> D end. 9 | 10 | server_fun(Main_PID, Port, Seq) -> 11 | receive 12 | {Client_PID, {syn, Port, SeqCl}} -> 13 | Ack_PID = spawn(?MODULE, ack, [Main_PID, Port, SeqCl+1, Seq+1, Client_PID]), 14 | Client_PID ! {Ack_PID, {syn_ack, SeqCl+1, Seq}}, 15 | server_fun(Main_PID, Port, Seq+1); 16 | {Client_PID, {syn, _, _}} -> 17 | Client_PID ! rst%, server_fun(Main_PID,Port,Seq+1) % BUG 18 | %Client_PID ! rst, server_fun(Main_PID,Port,Seq+1) % OK 19 | end. 20 | 21 | ack(Main_PID, Port, Ack, Seq, Client_PID) -> 22 | receive 23 | {Client_PID, {ack, Port, Seq, Ack}} -> 24 | receive 25 | {Client_PID, {data, Port, D}} -> Main_PID ! {data, D}; 26 | _ -> Main_PID ! {data, error_data} 27 | end; 28 | _ -> Main_PID ! {data, error_ack} 29 | end. 30 | 31 | client_fun(Server_PID, Port, Seq, Data) -> 32 | Server_PID ! {self(), {syn, Port, Seq}}, syn_ack(Port, Data, Seq+1). 33 | 34 | syn_ack(Port, Data, Ack) -> 35 | receive 36 | rst -> {port_rejected, Port} ; 37 | {Ack_PID, {syn_ack, Ack, Seq}} -> 38 | Ack_PID ! {self(), {Ack, Port, Seq+1, ack}}, % BUG 39 | %Ack_PID ! {self(), {ack, Port, Seq+1, Ack}}, % OK 40 | Ack_PID ! {self(), {data, Port, Data}}, 41 | {Seq+1, Ack, Port, Data} 42 | end. 43 | -------------------------------------------------------------------------------- /examples/tcpFixed.erl: -------------------------------------------------------------------------------- 1 | -module(tcpFixed). 2 | -export([main/0, server_fun/3, client_fun/4, ack/5]). 3 | 4 | main() -> 5 | Server_PID = spawn(?MODULE, server_fun, [self(), 50, 500]), 6 | spawn(?MODULE, client_fun, [Server_PID, 57, 100, client1]), 7 | spawn(?MODULE, client_fun, [Server_PID, 50, 200, client2]), 8 | receive {data,D} -> D end. 9 | 10 | server_fun(Main_PID, Port, Seq) -> 11 | receive 12 | {Client_PID, {syn, Port, SeqCl}} -> 13 | Ack_PID = spawn(?MODULE, ack, [Main_PID, Port, SeqCl+1, Seq+1, Client_PID]), 14 | Client_PID ! {Ack_PID, {syn_ack, SeqCl+1, Seq}}, 15 | server_fun(Main_PID, Port, Seq+1); 16 | {Client_PID, {syn, _, _}} -> 17 | Client_PID ! rst, server_fun(Main_PID,Port,Seq+1) 18 | %Client_PID ! rst, server_fun(Main_PID,Port,Seq+1) % OK 19 | end. 20 | 21 | ack(Main_PID, Port, Ack, Seq, Client_PID) -> 22 | receive 23 | {Client_PID, {ack, Port, Seq, Ack}} -> 24 | receive 25 | {Client_PID, {data, Port, D}} -> Main_PID ! {data, D}; 26 | _ -> Main_PID ! {data, error_data} 27 | end; 28 | _ -> Main_PID ! {data, error_ack} 29 | end. 30 | 31 | client_fun(Server_PID, Port, Seq, Data) -> 32 | Server_PID ! {self(), {syn, Port, Seq}}, syn_ack(Port, Data, Seq+1). 33 | 34 | syn_ack(Port, Data, Ack) -> 35 | receive 36 | rst -> {port_rejected, Port} ; 37 | {Ack_PID, {syn_ack, Ack, Seq}} -> 38 | %Ack_PID ! {self(), {Ack, Port, Seq+1, ack}}, % BUG 39 | Ack_PID ! {self(), {ack, Port, Seq+1, Ack}}, % OK 40 | Ack_PID ! {self(), {data, Port, Data}}, 41 | {Seq+1, Ack, Port, Data} 42 | end. 43 | -------------------------------------------------------------------------------- /case-studies/info.txt: -------------------------------------------------------------------------------- 1 | Each case study includes a bugged file, (possibly) a trace exercising 2 | the bug, (possibly) its fixed version, and additional information, 3 | typically including a reference to where the program and the bug are 4 | described or the description itself. 5 | 6 | Traces are in a dedicated directory including a file trace_result.log 7 | with general information and a number of files trace_NNN.log 8 | containing the trace of file NNN. 9 | 10 | The file trace_result.log contains the following information: 11 | 12 | 1) executed call 13 | 14 | call "module:function()" e.g. call "purchase:main()" 15 | 16 | 2) pid assigned to main process 17 | 18 | main_pid NNN e.g. main_pid 63 19 | 20 | 3) result of the main process 21 | 22 | result VALUE e.g. result false 23 | 24 | Each file trace_NNN.log is a sequence of tuples with the following forms: 25 | 26 | 1) spawn instructions 27 | 28 | {PID1,spawn,PID2} e.g. {63,spawn,70} 29 | 30 | Process with pid PID1 spawned process with pid PID2 31 | 32 | 2) send instructions 33 | 34 | {PID,send,MSGID} e.g. {70,send,2} 35 | 36 | Process with pid PID sent message with unique message identifier MSGID. 37 | 38 | NOTE: unique message identifiers do not exist in Erlang and are added 39 | by our instrumentation. They allow one to link a send with the 40 | corresponding receive and allow one to have more precise causal 41 | information. 42 | 43 | 3) receive instructions 44 | 45 | {PID,'receive',MSGID} e.g. {63,'receive',3} 46 | 47 | Process with pid PID received message with unique message identifier 48 | MSGID. 49 | 50 | NOTE: unique message identifiers do not exist in Erlang and are added 51 | by our instrumentation. They allow one to link a send with the 52 | corresponding receive and allow one to have more precise causal 53 | information. 54 | -------------------------------------------------------------------------------- /src/tracer.erl: -------------------------------------------------------------------------------- 1 | -module(tracer). 2 | -export([init/0]). 3 | -include("cauder.hrl"). 4 | 5 | pp(CoreForm) -> lists:flatten(core_pp:format(CoreForm)). 6 | 7 | %%convert from trace record to report for collector 8 | showEvent(CollectorPid,#trace{type=Type,from=From,to=To,val=Val,time=Time})-> 9 | case Type of 10 | ?RULE_SPAWN-> 11 | et_collector:report_event(CollectorPid,85,From,To,Type,Val); 12 | _-> 13 | et_collector:report_event(CollectorPid,85,From,To,Type,Val++" ("++integer_to_list(Time)++")") 14 | end. 15 | 16 | %%cit converts the FIELDS of the trace into human-readable form and in the receive case, it associates its sender 17 | parseTrace(ListTrace,#trace{type=Type,from=From,to=To,val=Val,time=Time})-> 18 | case Type of 19 | ?RULE_RECEIVE-> 20 | [ReceiveTo]=[Send#trace.from||Send<-ListTrace,Send#trace.type==?RULE_SEND,Send#trace.val==Val,Send#trace.time==Time], 21 | #trace{type=Type,from=pp(From),to=pp(ReceiveTo),val=pp(Val),time=Time}; 22 | ?RULE_SEND-> 23 | #trace{type=Type,from=pp(From),to=pp(To),val=pp(Val),time=Time}; 24 | ?RULE_SPAWN-> 25 | #trace{type=Type,from=pp(From),to=pp(To),val=Val,time=Time} 26 | end. 27 | 28 | init()->%%initialize the tracer 29 | {ok,CollectorPid}=et_collector:start_link([]), 30 | et_viewer:start([{max_actors,20},{collector_pid,CollectorPid},{async,true},{async_patterns,{?RULE_SPAWN,?RULE_SEND,?RULE_RECEIVE,exit}}]), 31 | showingTrace(CollectorPid,[]). 32 | 33 | 34 | showingTrace(CollectorPid,LastTrace)->%%start receive message for handle the viewer 35 | receive 36 | {show,ListTrace} when is_list(ListTrace)-> 37 | case ListTrace==LastTrace of %I compare the incoming list with the last list 38 | true->% if they are the same except the last one and I don't change the graphics 39 | showingTrace(CollectorPid,ListTrace); 40 | false-> %otherwise I save it, as the events appear, I empty the collector's events and re-fill it 41 | ParsedTrace=[parseTrace(ListTrace,Trace)||Trace<-ListTrace], 42 | et_collector:multicast(CollectorPid,clean_all),%%%%RESET THE VIEWER GRAPHIC CHART 43 | [showEvent(CollectorPid,Item)||Item<-ParsedTrace],%%FILL VIEWER EVENTS 44 | showingTrace(CollectorPid,ListTrace) 45 | end; 46 | close-> 47 | et_collector:stop(CollectorPid); 48 | _-> 49 | io:fwrite("I DIDN'T UNDERSTAND THE MESSAGE YOU SENT ME!~n"), 50 | showingTrace(CollectorPid,LastTrace) 51 | end. 52 | -------------------------------------------------------------------------------- /case-studies/barber/info.txt: -------------------------------------------------------------------------------- 1 | Buggy file: barber.erl 2 | Fixed file: barber_fixed.erl 3 | 4 | ----------------------------------------------------------- 5 | Typical session: 6 | 7 | $ erl 8 | > c(barber). 9 | {ok,barber} 10 | > barber:main(). 11 | 12 | Customers: John and Joe 13 | 14 | 'John' finished 15 | 16 | **deadlock** 17 | 18 | Expected behaviour: 19 | 20 | $ c(barber_fixed). 21 | {ok,barber_fixed} 22 | > barber_fixed:main(). 23 | 24 | Customers: John and Joe 25 | 26 | 'John' finished 27 | 'Joe' finished 28 | stop 29 | ----------------------------------------------------------- 30 | 31 | Using the debugger 32 | 33 | * First, instrument the buggy code and get a trace: 34 | 35 | >>move to the main folder of the tracer (branch: simple_inst) 36 | >>copy the source file (barber.erl) to the examples folder 37 | 38 | $ make load 39 | > tracer:inst(barber,[{dir,"examples"}]). // creates baber.erl in the current dir 40 | // this is the instrumented version 41 | > tracer:trace("barber:main()",[{log_dir,"barber_results"}]). 42 | > q(). 43 | 44 | >>the logs can be found in the folder "barber_results" 45 | 46 | * Now, let's inspect the buggy execution using CauDEr: 47 | 48 | >>move to cauder (branch: replay) 49 | 50 | $ ./cauder.sh 51 | 52 | >> Load in barber.erl and the logged trace in barber_results 53 | >> (just need to point to the folder "barber_results") 54 | 55 | * A typical debugging session: 56 | 57 | >> Replay process 71 (main) 100 steps 58 | -->some processes still have a nonempty log, but this is fine 59 | -->we want to see what happened with joe 60 | >> Rollback receive of message 1 61 | -->many actions undone because of causal dependencies 62 | >> Replay 73 (shop) 1 step 63 | -->we receive new customer joe, as expected 64 | >> Let us now look at process 73 (shop) and its log, and see if everything goes fine: 65 | --> it should first send "wakeup" to the barber (replay send 4): ok 66 | --> it should now receive "ready" from the barber (replay receive 2): ok 67 | --> it should now send a customer (John) to the barber (replay send 6): ok 68 | --> let us now go forward step by step till the next loop of barber_shop... 69 | after 9 steps, we see that the next call is barber_shop(_,[John]), 70 | which is incorrect! (we sent John, but remove Joe from the waiting room) 71 | 72 | * Solution: change the implementation of removeCustomer for a function that 73 | removes the last element instead of the first one... -> barber_fixed.erl -------------------------------------------------------------------------------- /src/cauder.hrl: -------------------------------------------------------------------------------- 1 | -define(APP_STRING, "CauDEr"). 2 | 3 | -define(ID_GAMMA, 0). 4 | 5 | -ifdef(debug). 6 | -define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE, ?LINE, X])). 7 | -define(TO_STRING(X), lists:flatten(io_lib:format("~p",[X]))). 8 | -else. 9 | -define(LOG(X), true). 10 | -define(TO_STRING(X), true). 11 | -endif. 12 | 13 | -define(FWD_SEM, fwd_sem). 14 | -define(BWD_SEM, bwd_sem). 15 | 16 | -define(TYPE_MSG, msg). 17 | -define(TYPE_PROC, proc). 18 | 19 | -define(RULE_SEQ, seq). 20 | -define(RULE_CHECK, check). 21 | -define(RULE_SEND, send). 22 | -define(RULE_RECEIVE, 'receive'). 23 | -define(RULE_SPAWN, spawn). 24 | -define(RULE_SELF, self). 25 | -define(RULE_SCHED, sched). 26 | 27 | % ets defs 28 | -define(APP_REF, '_._app'). 29 | -define(GUI_REF, '_._gui'). 30 | 31 | -define(FILE_PATH, 200). 32 | -define(REPLAY_DATA, 201). 33 | 34 | -define(FUN_DEFS, 300). 35 | -define(FRESH_PID, 301). 36 | -define(FRESH_TIME, 302). 37 | -define(FRESH_VAR, 303). 38 | 39 | -define(MULT_FWD, mult_fwd). 40 | -define(MULT_BWD, mult_bwd). 41 | 42 | -define(NOT_EXP, not_exp). 43 | -define(NULL_RULE, null_rule). 44 | -define(NULL_OPT, null_opt). 45 | 46 | -define(PRINT_MAIL, print_mail). 47 | -define(PRINT_HIST, print_hist). 48 | -define(PRINT_ENV, print_env). 49 | -define(PRINT_EXP, print_exp). 50 | %%define print viewer 51 | -define(PRINT_VIEWER, print_viewer). 52 | %% 53 | -define(PRINT_FULL, print_full). 54 | -define(COMP_OPT, comp_opt). 55 | -define(PRINT_FULL_ENV, print_full_env). 56 | 57 | -define(SCHED_RANDOM, random). 58 | -define(SCHED_PRIO_RANDOM, prio_random). 59 | 60 | -define(CAUDER_GREEN, {34,139,34}). 61 | 62 | -define(LAST_PATH, last_path). 63 | 64 | -record(proc, {pid, 65 | hist = [], 66 | log = [], 67 | env = [], 68 | exp, 69 | mail = [], 70 | spf = undef}). 71 | 72 | -record(msg, {dest, 73 | val, 74 | time}). 75 | 76 | -record(sys, {sched = ?SCHED_PRIO_RANDOM, 77 | msgs = [], 78 | procs = [], 79 | trace = [], 80 | roll = []}). 81 | 82 | -record(opt, {sem, % forward or backward 83 | type, % proc or msg 84 | id, % integer 85 | rule}). % seq, spawn, ... 86 | 87 | -record(trace, {type, 88 | from, 89 | to, 90 | val, 91 | time}). 92 | 93 | -record(replay, {call, 94 | main_pid, 95 | log_path}). 96 | 97 | -------------------------------------------------------------------------------- /case-studies/barber/barber.erl: -------------------------------------------------------------------------------- 1 | % sleeping barber problem (with a bug) 2 | 3 | -module(barber). 4 | -export([main/0,open_barber_shop/0,barber/1,customer/3]). 5 | 6 | main() -> 7 | io:format("~nCustomers: John and Joe~n~n"), 8 | ShopPid = spawn(?MODULE, open_barber_shop, []), 9 | spawn(?MODULE, customer, [ShopPid,'John',self()]), 10 | spawn(?MODULE, customer, [ShopPid,'Joe',self()]), 11 | receive 12 | {Name1, State1} -> io:format("~p ~p~n",[Name1,State1]) 13 | end, 14 | receive 15 | {Name2, State2} -> io:format("~p ~p~n",[Name2,State2]) 16 | end, 17 | ShopPid ! stop. 18 | 19 | %%customer 20 | customer(ShopPid,Name,MainPid) -> 21 | ShopPid ! {new,{self(),Name}}, 22 | receive 23 | X -> MainPid ! {Name,X} 24 | end. 25 | 26 | 27 | %% barber %% 28 | barber(ShopPid) -> 29 | ShopPid ! ready, 30 | receive 31 | wakeup -> barber(ShopPid); 32 | {customer, Customer} -> 33 | ShopPid ! {cut, Customer}, 34 | barber(ShopPid) 35 | end. 36 | 37 | %% shop %% 38 | open_barber_shop() -> 39 | BarberPid = spawn(?MODULE, barber, [self()]), 40 | barber_shop(BarberPid, []). 41 | 42 | %% main loop 43 | barber_shop(BarberPid, CustomersInChairs) -> 44 | receive 45 | {cut, {CustomerPid,_}} -> 46 | CustomerPid ! finished, 47 | barber_shop(BarberPid, CustomersInChairs); 48 | ready -> 49 | respond_to_barber(BarberPid, CustomersInChairs); 50 | {new,CustomerInfo} -> 51 | add_customer_if_available(BarberPid, CustomerInfo, CustomersInChairs); 52 | stop -> stop 53 | end. 54 | 55 | respond_to_barber(BarberPid, []) -> 56 | barber_shop(BarberPid, []); 57 | respond_to_barber(BarberPid, List) -> 58 | BarberPid ! {customer, last(List)}, 59 | barber_shop(BarberPid, removeCustomer(List)). 60 | 61 | % assuming 2 chairs 62 | add_customer_if_available(BarberPid, {CustomerPid,_CustomerName}, [X,Y|R]) -> 63 | CustomerPid ! no_room, 64 | barber_shop(BarberPid, [X,Y|R]); 65 | add_customer_if_available(BarberPid, {CustomerPid,CustomerName}, List) -> 66 | BarberPid ! wakeup, 67 | barber_shop(BarberPid, [{CustomerPid,CustomerName}|List]). 68 | 69 | last([A]) -> A; 70 | last([_A|R]) -> last(R). 71 | 72 | removeCustomer([_A|R]) -> R. 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | %%%%%%%% 121 | 122 | %removeCustomer([_A]) -> []; 123 | %removeCustomer([A|R]) -> [A|removeCustomer(R)]. 124 | 125 | -------------------------------------------------------------------------------- /case-studies/barber/barber_fixed.erl: -------------------------------------------------------------------------------- 1 | % sleeping barber problem (correct version) 2 | 3 | -module(barber_fixed). 4 | -export([main/0,open_barber_shop/0,barber/1,customer/3]). 5 | 6 | main() -> 7 | io:format("~nCustomers: John and Joe~n~n"), 8 | ShopPid = spawn(?MODULE, open_barber_shop, []), 9 | spawn(?MODULE, customer, [ShopPid,'John',self()]), 10 | spawn(?MODULE, customer, [ShopPid,'Joe',self()]), 11 | receive 12 | {Name1, State1} -> io:format("~p ~p~n",[Name1,State1]) 13 | end, 14 | receive 15 | {Name2, State2} -> io:format("~p ~p~n",[Name2,State2]) 16 | end, 17 | ShopPid ! stop. 18 | 19 | %%customer 20 | customer(ShopPid,Name,MainPid) -> 21 | ShopPid ! {new,{self(),Name}}, 22 | receive 23 | X -> MainPid ! {Name,X} 24 | end. 25 | 26 | 27 | %% barber %% 28 | barber(ShopPid) -> 29 | ShopPid ! ready, 30 | receive 31 | wakeup -> barber(ShopPid); 32 | {customer, Customer} -> 33 | ShopPid ! {cut, Customer}, 34 | barber(ShopPid) 35 | end. 36 | 37 | %% shop %% 38 | open_barber_shop() -> 39 | BarberPid = spawn(?MODULE, barber, [self()]), 40 | barber_shop(BarberPid, []). 41 | 42 | %% main loop 43 | barber_shop(BarberPid, CustomersInChairs) -> 44 | receive 45 | {cut, {CustomerPid,_}} -> 46 | CustomerPid ! finished, 47 | barber_shop(BarberPid, CustomersInChairs); 48 | ready -> 49 | respond_to_barber(BarberPid, CustomersInChairs); 50 | {new,CustomerInfo} -> 51 | add_customer_if_available(BarberPid, CustomerInfo, CustomersInChairs); 52 | stop -> stop 53 | end. 54 | 55 | respond_to_barber(BarberPid, []) -> 56 | barber_shop(BarberPid, []); 57 | respond_to_barber(BarberPid, List) -> 58 | BarberPid ! {customer, last(List)}, 59 | barber_shop(BarberPid, removeCustomer(List)). 60 | 61 | % assuming 2 chairs 62 | add_customer_if_available(BarberPid, {CustomerPid,_CustomerName}, [X,Y|R]) -> 63 | CustomerPid ! no_room, 64 | barber_shop(BarberPid, [X,Y|R]); 65 | add_customer_if_available(BarberPid, {CustomerPid,CustomerName}, List) -> 66 | BarberPid ! wakeup, 67 | barber_shop(BarberPid, [{CustomerPid,CustomerName}|List]). 68 | 69 | last([A]) -> A; 70 | last([_A|R]) -> last(R). 71 | 72 | removeCustomer([_A]) -> []; 73 | removeCustomer([A|R]) -> [A|removeCustomer(R)]. 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | %%%%%%%% 122 | 123 | %removeCustomer([_A]) -> []; 124 | %removeCustomer([A|R]) -> [A|removeCustomer(R)]. 125 | 126 | -------------------------------------------------------------------------------- /src/et_viewer.erl: -------------------------------------------------------------------------------- 1 | %IMPORTANT 2 | %%THIS FILE IS MODIFIED FROM et_viewer.erl OF OTP STANDARD LIBRARY FOR PARTIAL-ORDER GRAPHIC CHART 3 | %%UNDER COPYRIGHT OF ERICSSON AB: 4 | %% %CopyrightBegin% 5 | %% 6 | %% Copyright Ericsson AB 2000-2016. All Rights Reserved. 7 | %% 8 | %% Licensed under the Apache License, Version 2.0 (the "License"); 9 | %% you may not use this file except in compliance with the License. 10 | %% You may obtain a copy of the License at 11 | %% 12 | %% http://www.apache.org/licenses/LICENSE-2.0 13 | %% 14 | %% Unless required by applicable law or agreed to in writing, software 15 | %% distributed under the License is distributed on an "AS IS" BASIS, 16 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | %% See the License for the specific language governing permissions and 18 | %% limitations under the License. 19 | %% 20 | %% %CopyrightEnd% 21 | %IMPORTANT END 22 | 23 | %MyCopyright 24 | %Copyright [Ivan Lanese] [Luca Tabanelli] 25 | 26 | %Licensed under the Apache License, Version 2.0 (the "License"); 27 | %you may not use this file except in compliance with the License. 28 | %You may obtain a copy of the License at 29 | 30 | % http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | %Unless required by applicable law or agreed to in writing, software 33 | %distributed under the License is distributed on an "AS IS" BASIS, 34 | %WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | %See the License for the specific language governing permissions and 36 | %limitations under the License. 37 | %MyCopyrightEnd 38 | 39 | -module(et_viewer). 40 | -export([file/1, 41 | start/0, 42 | start/1, 43 | start/2, 44 | start_link/1, 45 | start_link/2, 46 | open_event/2, 47 | stop/1, 48 | get_collector_pid/1]). 49 | 50 | -include_lib("et/include/et.hrl"). 51 | -include_lib("et/src/et_internal.hrl"). 52 | 53 | 54 | -define(unknown, "UNKNOWN"). 55 | 56 | file(FileName) -> 57 | start_link([{trace_client, {file, FileName}}], default). 58 | 59 | start() -> 60 | start([{trace_global, true}], default). 61 | 62 | start(GUI) when GUI =:= wx; GUI =:= default -> 63 | start_link([{trace_global, true}], GUI); 64 | start(Options) -> 65 | start_link([{parent_pid, undefined} | Options], default). 66 | 67 | start(Options, GUI) -> 68 | start_link([{parent_pid, undefined} | Options], GUI). 69 | 70 | start_link(GUI) when GUI =:= wx; GUI =:= default -> 71 | start_link([{trace_global, true}], GUI); 72 | start_link(Options) -> 73 | start_link(Options, default). 74 | 75 | start_link(Options, GUI) -> 76 | case GUI of 77 | wx -> 78 | et_wx_viewer:start_link(Options); 79 | default -> 80 | start_link(Options, which_gui()) 81 | end. 82 | 83 | which_gui() -> wx. 84 | 85 | get_collector_pid(ViewerPid) -> 86 | call(ViewerPid, get_collector_pid). 87 | 88 | stop(ViewerPid) -> 89 | call(ViewerPid, stop). 90 | 91 | open_event(ViewerPid, N) -> 92 | call(ViewerPid, {open_event, N}). 93 | 94 | call(ViewerPid, Request) -> 95 | gen_server:call(ViewerPid, Request, infinity). 96 | 97 | -------------------------------------------------------------------------------- /examples/dining_ex.erl: -------------------------------------------------------------------------------- 1 | -module(dining_ex). 2 | -export([main/0, waiter/0, fork/1, philo/2]). 3 | 4 | main() -> 5 | spawn(?MODULE, waiter, []). 6 | 7 | spawn_forks(0, Dict) -> 8 | Dict; 9 | spawn_forks(N, Dict) -> 10 | Pair = {N, spawn(?MODULE, fork, [free])}, 11 | spawn_forks(N-1, [Pair] ++ Dict). 12 | 13 | spawn_philos(0, Dict, _) -> 14 | Dict; 15 | spawn_philos(N, Dict, Pid) -> 16 | Pair = {spawn(?MODULE, philo, [Pid, N]), N}, 17 | spawn_philos(N-1, [Pair] ++ Dict, Pid). 18 | 19 | waiter() -> 20 | ForkDict = spawn_forks(5, []), 21 | PhiloDict = spawn_philos(5, [], self()), 22 | waiter_1(ForkDict, PhiloDict). 23 | 24 | waiter_1(ForkDict, PhiloDict) -> 25 | receive 26 | {eaten, PhiloPid} -> 27 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 28 | LeftForkId = PhiloId, 29 | RightForkId = 1 + (LeftForkId rem 5), 30 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 31 | RightPid = proplists:get_value(RightForkId, ForkDict), 32 | set_state(LeftPid, free), 33 | set_state(RightPid, free); 34 | {hungry, PhiloPid} -> 35 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 36 | LeftForkId = PhiloId, 37 | RightForkId = 1 + (LeftForkId rem 5), 38 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 39 | RightPid = proplists:get_value(RightForkId, ForkDict), 40 | LeftForkState = ask_state(LeftPid), 41 | RightForkState = ask_state(RightPid), 42 | case {LeftForkState, RightForkState} of 43 | {free, free} -> 44 | set_state(LeftPid, used), 45 | set_state(RightPid, used), 46 | PhiloPid ! eat; 47 | _ -> 48 | PhiloPid ! think 49 | end 50 | end, 51 | waiter_1(ForkDict, PhiloDict). 52 | 53 | ask_state(Pid) -> 54 | Pid ! {get_state, self()}, 55 | receive 56 | State -> State 57 | end. 58 | 59 | set_state(Pid, State) -> 60 | Pid ! {set_state, State, self()}, 61 | receive 62 | been_set -> ok 63 | end. 64 | 65 | philo(WaiterPid, PhiloId) -> 66 | think(PhiloId), 67 | request_until_eaten(WaiterPid, PhiloId), 68 | philo(WaiterPid, PhiloId). 69 | 70 | think(PhiloId) -> 71 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is thinking~n"), 72 | ThinkTime = rand:uniform(1000), 73 | timer:sleep(ThinkTime), 74 | ok. 75 | 76 | eat(PhiloId) -> 77 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " has eaten~n"), 78 | timer:sleep(1000), 79 | ok. 80 | 81 | request_until_eaten(WaiterPid, PhiloId) -> 82 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is hungry~n"), 83 | WaiterPid ! {hungry, self()}, 84 | receive 85 | think -> 86 | think(PhiloId), 87 | request_until_eaten(WaiterPid, PhiloId); 88 | eat -> 89 | eat(PhiloId), 90 | WaiterPid ! {eaten, self()} 91 | end. 92 | 93 | fork(State) -> 94 | receive 95 | {get_state, WaiterPid} -> 96 | WaiterPid ! State, 97 | fork(State); 98 | {set_state, NewState, WaiterPid} -> 99 | WaiterPid ! been_set, 100 | fork(NewState) 101 | end. 102 | -------------------------------------------------------------------------------- /examples/dining.erl: -------------------------------------------------------------------------------- 1 | -module(dining). 2 | -export([main/0, waiter/0, fork/1, philo/2]). 3 | 4 | main() -> 5 | spawn(?MODULE, waiter, []). 6 | 7 | spawn_forks(0, Dict) -> 8 | Dict; 9 | spawn_forks(N, Dict) -> 10 | Pair = {N, spawn(?MODULE, fork, [free])}, 11 | spawn_forks(N-1, [Pair] ++ Dict). 12 | 13 | spawn_philos(0, Dict, _) -> 14 | Dict; 15 | spawn_philos(N, Dict, Pid) -> 16 | Pair = {spawn(?MODULE, philo, [Pid, N]), N}, 17 | spawn_philos(N-1, [Pair] ++ Dict, Pid). 18 | 19 | waiter() -> 20 | ForkDict = spawn_forks(5, []), 21 | PhiloDict = spawn_philos(5, [], self()), 22 | waiter_1(ForkDict, PhiloDict). 23 | 24 | waiter_1(ForkDict, PhiloDict) -> 25 | receive 26 | {eaten, PhiloPid} -> 27 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 28 | LeftForkId = PhiloId, 29 | RightForkId = 1 + (LeftForkId rem 5), % Correct version 30 | % RightForkId = 1 + (5 rem LeftForkId), % Buggy version 31 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 32 | RightPid = proplists:get_value(RightForkId, ForkDict), 33 | set_state(LeftPid, free), 34 | set_state(RightPid, free); 35 | {hungry, PhiloPid} -> 36 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 37 | LeftForkId = PhiloId, 38 | RightForkId = 1 + (LeftForkId rem 5), 39 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 40 | RightPid = proplists:get_value(RightForkId, ForkDict), 41 | LeftForkState = ask_state(LeftPid), 42 | RightForkState = ask_state(RightPid), 43 | case {LeftForkState, RightForkState} of 44 | {free, free} -> 45 | set_state(LeftPid, used), 46 | set_state(RightPid, used), 47 | PhiloPid ! eat; 48 | _ -> 49 | PhiloPid ! think 50 | end 51 | end, 52 | waiter_1(ForkDict, PhiloDict). 53 | 54 | ask_state(Pid) -> 55 | Pid ! {get_state, self()}, 56 | receive 57 | {state, State, _} -> State 58 | end. 59 | 60 | set_state(Pid, State) -> 61 | Pid ! {set_state, State, self()}, 62 | receive 63 | {been_set, _} -> ok 64 | end. 65 | 66 | philo(WaiterPid, PhiloId) -> 67 | think(PhiloId), 68 | request_until_eaten(WaiterPid, PhiloId), 69 | philo(WaiterPid, PhiloId). 70 | 71 | think(PhiloId) -> 72 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is thinking~n"), 73 | ThinkTime = rand:uniform(1000), 74 | timer:sleep(ThinkTime), 75 | ok. 76 | 77 | eat(PhiloId) -> 78 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " has eaten~n"), 79 | timer:sleep(1000), 80 | ok. 81 | 82 | request_until_eaten(WaiterPid, PhiloId) -> 83 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is hungry~n"), 84 | WaiterPid ! {hungry, self()}, 85 | receive 86 | think -> 87 | think(PhiloId), 88 | request_until_eaten(WaiterPid, PhiloId); 89 | eat -> 90 | eat(PhiloId), 91 | WaiterPid ! {eaten, self()} 92 | end. 93 | 94 | fork(State) -> 95 | receive 96 | {get_state, WaiterPid} -> 97 | WaiterPid ! {state, State, self()}, 98 | fork(State); 99 | {set_state, NewState, WaiterPid} -> 100 | WaiterPid ! {been_set, self()}, 101 | fork(NewState) 102 | end. 103 | -------------------------------------------------------------------------------- /examples/dining_bug.erl: -------------------------------------------------------------------------------- 1 | -module(dining_bug). 2 | -export([main/0, waiter/0, fork/1, philo/2]). 3 | 4 | main() -> 5 | spawn(?MODULE, waiter, []). 6 | 7 | spawn_forks(0, Dict) -> 8 | Dict; 9 | spawn_forks(N, Dict) -> 10 | Pair = {N, spawn(?MODULE, fork, [free])}, 11 | spawn_forks(N-1, [Pair] ++ Dict). 12 | 13 | spawn_philos(0, Dict, _) -> 14 | Dict; 15 | spawn_philos(N, Dict, Pid) -> 16 | Pair = {spawn(?MODULE, philo, [Pid, N]), N}, 17 | spawn_philos(N-1, [Pair] ++ Dict, Pid). 18 | 19 | waiter() -> 20 | ForkDict = spawn_forks(5, []), 21 | PhiloDict = spawn_philos(5, [], self()), 22 | waiter_1(ForkDict, PhiloDict). 23 | 24 | waiter_1(ForkDict, PhiloDict) -> 25 | receive 26 | {eaten, PhiloPid} -> 27 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 28 | LeftForkId = PhiloId, 29 | % RightForkId = 1 + (LeftForkId rem 5), % Correct version 30 | RightForkId = 1 + (5 rem LeftForkId), % Buggy version 31 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 32 | RightPid = proplists:get_value(RightForkId, ForkDict), 33 | set_state(LeftPid, free), 34 | set_state(RightPid, free); 35 | {hungry, PhiloPid} -> 36 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 37 | LeftForkId = PhiloId, 38 | RightForkId = 1 + (LeftForkId rem 5), 39 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 40 | RightPid = proplists:get_value(RightForkId, ForkDict), 41 | LeftForkState = ask_state(LeftPid), 42 | RightForkState = ask_state(RightPid), 43 | case {LeftForkState, RightForkState} of 44 | {free, free} -> 45 | set_state(LeftPid, used), 46 | set_state(RightPid, used), 47 | PhiloPid ! eat; 48 | _ -> 49 | PhiloPid ! think 50 | end 51 | end, 52 | waiter_1(ForkDict, PhiloDict). 53 | 54 | ask_state(Pid) -> 55 | Pid ! {get_state, self()}, 56 | receive 57 | {state, State, _} -> State 58 | end. 59 | 60 | set_state(Pid, State) -> 61 | Pid ! {set_state, State, self()}, 62 | receive 63 | {been_set, _} -> ok 64 | end. 65 | 66 | philo(WaiterPid, PhiloId) -> 67 | think(PhiloId), 68 | request_until_eaten(WaiterPid, PhiloId), 69 | philo(WaiterPid, PhiloId). 70 | 71 | think(PhiloId) -> 72 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is thinking~n"), 73 | ThinkTime = rand:uniform(1000), 74 | timer:sleep(ThinkTime), 75 | ok. 76 | 77 | eat(PhiloId) -> 78 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " has eaten~n"), 79 | timer:sleep(1000), 80 | ok. 81 | 82 | request_until_eaten(WaiterPid, PhiloId) -> 83 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is hungry~n"), 84 | WaiterPid ! {hungry, self()}, 85 | receive 86 | think -> 87 | think(PhiloId), 88 | request_until_eaten(WaiterPid, PhiloId); 89 | eat -> 90 | eat(PhiloId), 91 | WaiterPid ! {eaten, self()} 92 | end. 93 | 94 | fork(State) -> 95 | receive 96 | {get_state, WaiterPid} -> 97 | WaiterPid ! {state, State, self()}, 98 | fork(State); 99 | {set_state, NewState, WaiterPid} -> 100 | WaiterPid ! {been_set, self()}, 101 | fork(NewState) 102 | end. 103 | -------------------------------------------------------------------------------- /examples/dining_simple.erl: -------------------------------------------------------------------------------- 1 | -module(dining_simple). 2 | -export([main/0, waiter/0, fork/1, philo/2]). 3 | 4 | main() -> 5 | spawn(?MODULE, waiter, []). 6 | 7 | spawn_forks(0, Dict) -> 8 | Dict; 9 | spawn_forks(N, Dict) -> 10 | Pair = {N, spawn(?MODULE, fork, [free])}, 11 | spawn_forks(N-1, [Pair] ++ Dict). 12 | 13 | spawn_philos(0, Dict, _) -> 14 | Dict; 15 | spawn_philos(N, Dict, Pid) -> 16 | Pair = {spawn(?MODULE, philo, [Pid, N]), N}, 17 | spawn_philos(N-1, [Pair] ++ Dict, Pid). 18 | 19 | waiter() -> 20 | ForkDict = spawn_forks(5, []), 21 | PhiloDict = spawn_philos(5, [], self()), 22 | waiter_1(ForkDict, PhiloDict). 23 | 24 | waiter_1(ForkDict, PhiloDict) -> 25 | receive 26 | {eaten, PhiloPid} -> 27 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 28 | LeftForkId = PhiloId, 29 | RightForkId = 1 + (LeftForkId rem 5), 30 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 31 | RightPid = proplists:get_value(RightForkId, ForkDict), 32 | set_state(LeftPid, free), 33 | set_state(RightPid, free); 34 | {hungry, PhiloPid} -> 35 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 36 | LeftForkId = PhiloId, 37 | RightForkId = 1 + (LeftForkId rem 5), 38 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 39 | RightPid = proplists:get_value(RightForkId, ForkDict), 40 | LeftForkState = ask_state(LeftPid), 41 | RightForkState = ask_state(RightPid), 42 | case {LeftForkState, RightForkState} of 43 | {free, free} -> 44 | set_state(LeftPid, used), 45 | set_state(RightPid, used), 46 | PhiloPid ! eat; 47 | _ -> 48 | PhiloPid ! think 49 | end 50 | end, 51 | waiter_1(ForkDict, PhiloDict). 52 | 53 | ask_state(Pid) -> 54 | Pid ! {get_state, self()}, 55 | receive 56 | {state, State, _} -> State % Correct version 57 | % State -> State % Buggy version 58 | end. 59 | 60 | set_state(Pid, State) -> 61 | Pid ! {set_state, State, self()}, 62 | receive 63 | {been_set, _} -> ok % Correct version 64 | % been_set -> ok % Buggy version 65 | end. 66 | 67 | philo(WaiterPid, PhiloId) -> 68 | think(PhiloId), 69 | request_until_eaten(WaiterPid, PhiloId), 70 | philo(WaiterPid, PhiloId). 71 | 72 | think(PhiloId) -> 73 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is thinking~n"), 74 | ThinkTime = rand:uniform(1000), 75 | timer:sleep(ThinkTime), 76 | ok. 77 | 78 | eat(PhiloId) -> 79 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " has eaten~n"), 80 | timer:sleep(1000), 81 | ok. 82 | 83 | request_until_eaten(WaiterPid, PhiloId) -> 84 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is hungry~n"), 85 | WaiterPid ! {hungry, self()}, 86 | receive 87 | think -> 88 | think(PhiloId), 89 | request_until_eaten(WaiterPid, PhiloId); 90 | eat -> 91 | eat(PhiloId), 92 | WaiterPid ! {eaten, self()} 93 | end. 94 | 95 | fork(State) -> 96 | receive 97 | {get_state, WaiterPid} -> 98 | WaiterPid ! {state, State, self()}, % Correct version 99 | % WaiterPid ! State, % Buggy version 100 | fork(State); 101 | {set_state, NewState, WaiterPid} -> 102 | WaiterPid ! {been_set, self()}, % Correct version 103 | % WaiterPid ! been_set, % Buggy version 104 | fork(NewState) 105 | end. 106 | -------------------------------------------------------------------------------- /examples/dining_simple_bug.erl: -------------------------------------------------------------------------------- 1 | -module(dining_simple_bug). 2 | -export([main/0, waiter/0, fork/1, philo/2]). 3 | 4 | main() -> 5 | spawn(?MODULE, waiter, []). 6 | 7 | spawn_forks(0, Dict) -> 8 | Dict; 9 | spawn_forks(N, Dict) -> 10 | Pair = {N, spawn(?MODULE, fork, [free])}, 11 | spawn_forks(N-1, [Pair] ++ Dict). 12 | 13 | spawn_philos(0, Dict, _) -> 14 | Dict; 15 | spawn_philos(N, Dict, Pid) -> 16 | Pair = {spawn(?MODULE, philo, [Pid, N]), N}, 17 | spawn_philos(N-1, [Pair] ++ Dict, Pid). 18 | 19 | waiter() -> 20 | ForkDict = spawn_forks(5, []), 21 | PhiloDict = spawn_philos(5, [], self()), 22 | waiter_1(ForkDict, PhiloDict). 23 | 24 | waiter_1(ForkDict, PhiloDict) -> 25 | receive 26 | {eaten, PhiloPid} -> 27 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 28 | LeftForkId = PhiloId, 29 | RightForkId = 1 + (LeftForkId rem 5), 30 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 31 | RightPid = proplists:get_value(RightForkId, ForkDict), 32 | set_state(LeftPid, free), 33 | set_state(RightPid, free); 34 | {hungry, PhiloPid} -> 35 | PhiloId = proplists:get_value(PhiloPid, PhiloDict), 36 | LeftForkId = PhiloId, 37 | RightForkId = 1 + (LeftForkId rem 5), 38 | LeftPid = proplists:get_value(LeftForkId, ForkDict), 39 | RightPid = proplists:get_value(RightForkId, ForkDict), 40 | LeftForkState = ask_state(LeftPid), 41 | RightForkState = ask_state(RightPid), 42 | case {LeftForkState, RightForkState} of 43 | {free, free} -> 44 | set_state(LeftPid, used), 45 | set_state(RightPid, used), 46 | PhiloPid ! eat; 47 | _ -> 48 | PhiloPid ! think 49 | end 50 | end, 51 | waiter_1(ForkDict, PhiloDict). 52 | 53 | ask_state(Pid) -> 54 | Pid ! {get_state, self()}, 55 | receive 56 | % {state, State, _} -> State % Correct version 57 | State -> State % Buggy version 58 | end. 59 | 60 | set_state(Pid, State) -> 61 | Pid ! {set_state, State, self()}, 62 | receive 63 | % {been_set, _} -> ok % Correct version 64 | been_set -> ok % Buggy version 65 | end. 66 | 67 | philo(WaiterPid, PhiloId) -> 68 | think(PhiloId), 69 | request_until_eaten(WaiterPid, PhiloId), 70 | philo(WaiterPid, PhiloId). 71 | 72 | think(PhiloId) -> 73 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is thinking~n"), 74 | ThinkTime = rand:uniform(1000), 75 | timer:sleep(ThinkTime), 76 | ok. 77 | 78 | eat(PhiloId) -> 79 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " has eaten~n"), 80 | timer:sleep(1000), 81 | ok. 82 | 83 | request_until_eaten(WaiterPid, PhiloId) -> 84 | io:fwrite("Philo " ++ integer_to_list(PhiloId) ++ " is hungry~n"), 85 | WaiterPid ! {hungry, self()}, 86 | receive 87 | think -> 88 | think(PhiloId), 89 | request_until_eaten(WaiterPid, PhiloId); 90 | eat -> 91 | eat(PhiloId), 92 | WaiterPid ! {eaten, self()} 93 | end. 94 | 95 | fork(State) -> 96 | receive 97 | {get_state, WaiterPid} -> 98 | % WaiterPid ! {state, State, self()}, % Correct version 99 | WaiterPid ! State, % Buggy version 100 | fork(State); 101 | {set_state, NewState, WaiterPid} -> 102 | % WaiterPid ! {been_set, self()}, % Correct version 103 | WaiterPid ! been_set, % Buggy version 104 | fork(NewState) 105 | end. 106 | -------------------------------------------------------------------------------- /src/cauder_gui.hrl: -------------------------------------------------------------------------------- 1 | -record(status, {loaded = false, 2 | running = false}). 3 | 4 | -define(FRAME_SIZE_INIT, {800, 600}). 5 | -define(FRAME_SIZE_MIN, {800, 600}). 6 | -define(FRAME_SIZE_MAX, {800, 600}). 7 | 8 | -define(FONT_SIZES, [8, 10, 12, 13, 14, 16, 20, 24, 32, 36, 42, 48]). 9 | 10 | -define(ABOUT, ?wxID_ABOUT). 11 | -define(EXIT, ?wxID_EXIT). 12 | -define(OPEN, ?wxID_OPEN). 13 | -define(ZOOM_IN, 100). 14 | -define(ZOOM_OUT, 101). 15 | -define(TOGGLE_MAIL, 102). 16 | -define(TOGGLE_HIST, 103). 17 | -define(TOGGLE_ENV, 104). 18 | -define(TOGGLE_EXP, 105). 19 | %%toggle for the viewer 20 | -define(TOGGLE_VIEWER,114). 21 | %% 22 | -define(RADIO_CONC, 106). 23 | -define(RADIO_FULL, 107). 24 | -define(TOGGLE_COMP, 108). 25 | -define(RADIO_RAND, 109). 26 | -define(RADIO_PRIO, 110). 27 | -define(RADIO_REL_ENV, 111). 28 | -define(RADIO_FULL_ENV, 112). 29 | -define(REPLAY, 113). 30 | 31 | 32 | -define(START_BUTTON, 400). 33 | 34 | -define(FORW_INT_BUTTON, 410). 35 | -define(FORW_SCH_BUTTON, 411). 36 | -define(BACK_INT_BUTTON, 412). 37 | -define(BACK_SCH_BUTTON, 413). 38 | 39 | -define(FORWARD_BUTTON, 422). 40 | -define(BACKWARD_BUTTON, 423). 41 | -define(NORMALIZE_BUTTON, 424). 42 | -define(ROLL_BUTTON, 425). 43 | 44 | -define(ROLL_SEND_BUTTON, 426). 45 | -define(ROLL_SPAWN_BUTTON, 427). 46 | -define(ROLL_REC_BUTTON, 428). 47 | -define(ROLL_VAR_BUTTON, 429). 48 | 49 | -define(SYSTEM, 500). 50 | -define(STATUS, 501). 51 | -define(FRAME, 502). 52 | -define(MENU_VIEW, 503). 53 | -define(MENU_COMP, 504). 54 | -define(MENU_SCHED, 505). 55 | -define(INPUT_TEXT, 510). 56 | -define(PID_TEXT, 511). 57 | -define(STEP_TEXT, 512). 58 | -define(STATE_TEXT, 513). 59 | -define(CODE_TEXT, 514). 60 | -define(ROLL_PID_TEXT, 515). 61 | -define(ROLL_STEP_TEXT, 516). 62 | -define(STATUS_BAR, 520). 63 | -define(INPUT_SIZER, 530). 64 | -define(FUN_CHOICE, 531). 65 | -define(LEFT_NOTEBOOK, 540). 66 | -define(RIGHT_NOTEBOOK, 541). 67 | -define(RBOT_NOTEBOOK, 542). 68 | -define(TRACE_TEXT, 550). 69 | -define(ROLL_LOG_TEXT, 551). 70 | 71 | -define(ROLL_SEND_ID_TEXT, 560). 72 | -define(ROLL_SPAWN_ID_TEXT, 561). 73 | -define(ROLL_REC_ID_TEXT, 562). 74 | -define(ROLL_VAR_ID_TEXT, 563). 75 | 76 | -define(PAGEPOS_CODE, 0). 77 | -define(PAGEPOS_STATE, 1). 78 | -define(PAGEPOS_MANU, 0). 79 | -define(PAGEPOS_SEMI, 1). 80 | -define(PAGEPOS_AUTO, 2). 81 | -define(PAGEPOS_TRACE, 0). 82 | -define(PAGEPOS_ROLL, 1). 83 | 84 | -define(NULL_LABEL, null_label). 85 | 86 | -define(INFO_TEXT, "A Causal-consistent Debugger for Erlang. More info at: https://github.com/mistupv/cauder"). 87 | 88 | -define(ERROR_NUM_STEP, "The number of steps is not correct."). 89 | -define(ERROR_NUM_ARGS, "The number of arguments is not correct."). 90 | 91 | -define(HELP_OPEN_ITEM, "Open and compile an Erlang file"). 92 | -define(HELP_REPLAY_ITEM, "Replay an execution from a log file"). 93 | -define(HELP_QUIT_ITEM, "Quit this program"). 94 | -define(HELP_ZOOM_IN_ITEM, "Increase text font size"). 95 | -define(HELP_ZOOM_OUT_ITEM, "Decrease text font size"). 96 | -define(HELP_TOGGLE_MAIL, "Show or hide process mailboxes"). 97 | -define(HELP_TOGGLE_HIST, "Show or hide process histories"). 98 | -define(HELP_TOGGLE_ENV, "Show or hide process environments"). 99 | -define(HELP_TOGGLE_EXP, "Show or hide process expressions"). 100 | %%define helper for toggle viewer 101 | -define(HELP_TOGGLE_VIEWER, "Show or hide the graphic chart of current trace"). 102 | %% 103 | -define(HELP_RADIO_CONC, "Show only concurrent history"). 104 | -define(HELP_RADIO_FULL, "Show complete history"). 105 | -define(HELP_RADIO_REN_ENV, "Show relevant bindings from environment"). 106 | -define(HELP_RADIO_FULL_ENV, "Show all bindings from environment"). 107 | -define(HELP_TOGGLE_COMP, "Allow compiler optimizations when loading files"). 108 | -define(HELP_RADIO_RAND, "Set scheduler to random choice among options"). 109 | -define(HELP_RADIO_PRIO, "Set scheduler to random choice among options (priority to process options)"). 110 | -------------------------------------------------------------------------------- /src/cauder.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc The main module for the rev-erlang project. 3 | %%% This module includes functions for starting the application 4 | %%% and interact with the reversible semantics for Erlang 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | 8 | -module(cauder). 9 | -export([start/0, 10 | start_refs/1, stop_refs/0, 11 | eval_opts/1, eval_step/2, eval_mult/3, eval_norm/1, 12 | eval_roll/3, eval_roll_send/2, eval_roll_spawn/2, 13 | eval_roll_rec/2, eval_roll_var/2, eval_replay/0]). 14 | 15 | -include("cauder.hrl"). 16 | 17 | %%-------------------------------------------------------------------- 18 | %% @doc Starts the GUI 19 | %% @end 20 | %%-------------------------------------------------------------------- 21 | start() -> 22 | cauder_gui:setup_gui(), 23 | ok. 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @doc Starts the ETS servers and initializes them 27 | %% @end 28 | %%-------------------------------------------------------------------- 29 | start_refs(FunDefs) -> 30 | ?LOG("starting refs"), 31 | ref_start(), 32 | ref_add(?FUN_DEFS, FunDefs), 33 | ref_add(?FRESH_PID, 2), 34 | ref_add(?FRESH_TIME, 1), 35 | ref_add(?FRESH_VAR, 1). 36 | 37 | %%-------------------------------------------------------------------- 38 | %% @doc Stops the ETS servers 39 | %% @end 40 | %%-------------------------------------------------------------------- 41 | stop_refs() -> 42 | ?LOG("stopping refs"), 43 | ref_stop(). 44 | 45 | %%-------------------------------------------------------------------- 46 | %% @doc Returns all the evaluation options for a given System 47 | %% @end 48 | %%-------------------------------------------------------------------- 49 | eval_opts(System) -> 50 | FwdOpts = fwd_sem:eval_opts(System), 51 | BwdOpts = bwd_sem:eval_opts(System), 52 | FwdOpts ++ BwdOpts. 53 | 54 | eval_step(System, Option) -> 55 | #opt{sem = Semantics, type = Type, id = Id} = Option, 56 | NewSystem = 57 | case Type of 58 | ?TYPE_MSG -> Semantics:eval_sched(System,Id); 59 | ?TYPE_PROC -> Semantics:eval_step(System,cerl:c_int(Id)) 60 | end, 61 | NewSystem. 62 | 63 | %%-------------------------------------------------------------------- 64 | %% @doc Performs Steps evaluation steps in System in 65 | %% the Option direction 66 | %% @end 67 | %%-------------------------------------------------------------------- 68 | eval_mult(System, Option, Steps) -> 69 | eval_mult_1(System, Option, Steps, 0). 70 | 71 | eval_mult_1(System, _Option, Steps, Steps) -> 72 | {System, Steps}; 73 | eval_mult_1(System, Option, Steps, StepsDone) -> 74 | Sem = 75 | case Option of 76 | ?MULT_FWD -> fwd_sem; 77 | ?MULT_BWD -> bwd_sem 78 | end, 79 | SelOpt = sched:select_opt(Sem, System), 80 | case SelOpt of 81 | none -> 82 | {System, StepsDone}; 83 | _ -> 84 | NewSystem = eval_step(System, SelOpt), 85 | eval_mult_1(NewSystem, Option, Steps, StepsDone + 1) 86 | end. 87 | 88 | %%-------------------------------------------------------------------- 89 | %% @doc Performs evaluation steps (except for sched steps) in System 90 | %% until the system becomes "normalized" (more info on the paper) 91 | %% @end 92 | %%-------------------------------------------------------------------- 93 | eval_norm(System) -> 94 | eval_norm_1(System, 0). 95 | 96 | eval_norm_1(System, Steps) -> 97 | Opts = fwd_sem:eval_opts(System), 98 | ProcsOpts = utils:filter_procs_opts(Opts), 99 | case ProcsOpts of 100 | [] -> 101 | {System, Steps}; 102 | _Other -> 103 | RandIdx = rand:uniform(length(ProcsOpts)), 104 | RandOpt = lists:nth(RandIdx, ProcsOpts), 105 | NewSystem = eval_step(System, RandOpt), 106 | eval_norm_1(NewSystem, Steps + 1) 107 | end. 108 | 109 | eval_roll(System, Pid, Steps) -> 110 | EmptyLogSystem = utils:empty_log(System), 111 | {RolledSystem, StepsDone} = eval_roll_1(EmptyLogSystem, Pid, Steps, 0), 112 | FocusLog = utils:must_focus_log(RolledSystem), 113 | {FocusLog, RolledSystem, StepsDone}. 114 | 115 | eval_roll_1(System, _Pid, Steps, Steps) -> 116 | {System, Steps}; 117 | eval_roll_1(System, Pid, Steps, StepsDone) -> 118 | case roll:can_roll(System, Pid) of 119 | false -> 120 | {System, StepsDone}; 121 | true -> 122 | NewSystem = roll:eval_step(System, Pid), 123 | eval_roll_1(NewSystem, Pid, Steps, StepsDone + 1) 124 | end. 125 | 126 | eval_roll_send(System, Id) -> 127 | case roll:can_roll_send(System, Id) of 128 | false -> 129 | {false, false, System}; 130 | true -> 131 | EmptyLogSystem = utils:empty_log(System), 132 | RolledSystem = roll:eval_roll_send(EmptyLogSystem, Id), 133 | FocusLog = utils:must_focus_log(RolledSystem), 134 | {true, FocusLog, RolledSystem} 135 | end. 136 | 137 | eval_roll_spawn(System, Id) -> 138 | case roll:can_roll_spawn(System, Id) of 139 | false -> 140 | {false, false, System}; 141 | true -> 142 | EmptyLogSystem = utils:empty_log(System), 143 | RolledSystem = roll:eval_roll_spawn(EmptyLogSystem, Id), 144 | FocusLog = utils:must_focus_log(RolledSystem), 145 | {true, FocusLog, RolledSystem} 146 | end. 147 | 148 | eval_roll_rec(System, Id) -> 149 | case roll:can_roll_rec(System, Id) of 150 | false -> 151 | {false, false, System}; 152 | true -> 153 | EmptyLogSystem = utils:empty_log(System), 154 | RolledSystem = roll:eval_roll_rec(EmptyLogSystem, Id), 155 | FocusLog = utils:must_focus_log(RolledSystem), 156 | {true, FocusLog, RolledSystem} 157 | end. 158 | 159 | eval_roll_var(System, Id) -> 160 | case roll:can_roll_var(System, Id) of 161 | false -> 162 | {false, false, System}; 163 | true -> 164 | EmptyLogSystem = utils:empty_log(System), 165 | RolledSystem = roll:eval_roll_var(EmptyLogSystem, Id), 166 | FocusLog = utils:must_focus_log(RolledSystem), 167 | {true, FocusLog, RolledSystem} 168 | end. 169 | 170 | eval_replay() -> 171 | ok. 172 | 173 | ref_add(Id, Ref) -> 174 | ets:insert(?APP_REF, {Id, Ref}). 175 | 176 | ref_start() -> 177 | ?APP_REF = ets:new(?APP_REF, [set, public, named_table]), 178 | ok. 179 | 180 | ref_stop() -> 181 | ets:delete(?APP_REF). 182 | 183 | -------------------------------------------------------------------------------- /src/bwd_sem.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Some functions that implement the backward (reversible) 3 | %%% semantics for Erlang. These can be divided into functions to get 4 | %%% the evaluation options and functions to perform the evaluation 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | 8 | -module(bwd_sem). 9 | -export([eval_step/2, eval_sched/2, eval_opts/1, 10 | eval_procs_opts/1, eval_sched_opts/1]). 11 | 12 | -include("cauder.hrl"). 13 | 14 | %%-------------------------------------------------------------------- 15 | %% @doc Performs an evaluation step in process Pid, given System 16 | %% @end 17 | %%-------------------------------------------------------------------- 18 | eval_step(System, Pid) -> 19 | Procs = System#sys.procs, 20 | Msgs = System#sys.msgs, 21 | Trace = System#sys.trace, 22 | {Proc, RestProcs} = utils:select_proc(Procs, Pid), 23 | #proc{pid = Pid, hist = [CurHist|RestHist]} = Proc, 24 | case CurHist of 25 | {tau, OldEnv, OldExp} -> 26 | OldProc = Proc#proc{hist = RestHist, env = OldEnv, exp = OldExp}, 27 | System#sys{msgs = Msgs, procs = [OldProc|RestProcs]}; 28 | {self, OldEnv, OldExp} -> 29 | OldProc = Proc#proc{hist = RestHist, env = OldEnv, exp = OldExp}, 30 | System#sys{msgs = Msgs, procs = [OldProc|RestProcs]}; 31 | {send, OldEnv, OldExp, DestPid, {MsgValue, Time}} -> 32 | {_Msg, RestMsgs} = utils:select_msg(Msgs, Time), 33 | OldProc = Proc#proc{hist = RestHist, env = OldEnv, exp = OldExp}, 34 | TraceItem = #trace{type = ?RULE_SEND, from = Pid, to = DestPid, val = MsgValue, time = Time}, 35 | OldTrace = lists:delete(TraceItem, Trace), 36 | System#sys{msgs = RestMsgs, procs = [OldProc|RestProcs], trace = OldTrace}; 37 | {spawn, OldEnv, OldExp, SpawnPid} -> 38 | {_SpawnProc, OldRestProcs} = utils:select_proc(RestProcs, SpawnPid), 39 | OldProc = Proc#proc{hist = RestHist, env = OldEnv, exp = OldExp}, 40 | TraceItem = #trace{type = ?RULE_SPAWN, from = Pid, to = SpawnPid}, 41 | OldTrace = lists:delete(TraceItem, Trace), 42 | System#sys{msgs = Msgs, procs = [OldProc|OldRestProcs], trace = OldTrace}; 43 | {rec, OldEnv, OldExp, OldMsg, OldMail} -> 44 | OldProc = Proc#proc{hist = RestHist, env = OldEnv, exp = OldExp, mail = OldMail}, 45 | {MsgValue, Time} = OldMsg, 46 | TraceItem = #trace{type = ?RULE_RECEIVE, from = Pid, val = MsgValue, time = Time}, 47 | OldTrace = lists:delete(TraceItem, Trace), 48 | System#sys{msgs = Msgs, procs = [OldProc|RestProcs], trace = OldTrace} 49 | end. 50 | 51 | %%-------------------------------------------------------------------- 52 | %% @doc Performs an evaluation step in message Id, given System 53 | %% @end 54 | %%-------------------------------------------------------------------- 55 | eval_sched(System, Id) -> 56 | Procs = System#sys.procs, 57 | Msgs = System#sys.msgs, 58 | Proc = utils:select_proc_with_time(Procs, Id), 59 | Pid = Proc#proc.pid, 60 | {_, RestProcs} = utils:select_proc(Procs, Pid), 61 | Mail = Proc#proc.mail, 62 | {LastMsg,RestMail} = utils:last_msg_rest(Mail), 63 | {Value, Id} = LastMsg, 64 | OldMsg = #msg{dest = Pid, val = Value, time = Id}, 65 | OldProc = Proc#proc{mail = RestMail}, 66 | OldMsgs = [OldMsg|Msgs], 67 | OldProcs = [OldProc|RestProcs], 68 | System#sys{msgs = OldMsgs, procs = OldProcs}. 69 | 70 | %%-------------------------------------------------------------------- 71 | %% @doc Gets the evaluation options for a given System 72 | %% @end 73 | %%-------------------------------------------------------------------- 74 | eval_opts(System) -> 75 | SchedOpts = eval_sched_opts(System), 76 | ProcsOpts = eval_procs_opts(System), 77 | SchedOpts ++ ProcsOpts. 78 | 79 | eval_sched_opts(#sys{procs = Procs}) -> 80 | Opts = [eval_sched_opt(Proc) || Proc <- Procs], 81 | lists:filter(fun (X) -> 82 | case X of 83 | ?NULL_OPT -> false; 84 | _ -> true 85 | end 86 | end, Opts). 87 | 88 | eval_procs_opts(System) -> 89 | Procs = System#sys.procs, 90 | Msgs = System#sys.msgs, 91 | ProcPairs = [utils:select_proc(Procs, Proc#proc.pid) || Proc <- Procs ], 92 | Opts = [eval_proc_opt(#sys{msgs = Msgs, procs = RestProcs}, Proc) || {Proc, RestProcs} <- ProcPairs], 93 | lists:filter( fun (X) -> 94 | case X of 95 | ?NULL_OPT -> false; 96 | _Other -> true 97 | end 98 | end, Opts). 99 | 100 | eval_proc_opt(RestSystem, CurProc) -> 101 | RestProcs = RestSystem#sys.procs, 102 | Msgs = RestSystem#sys.msgs, 103 | Hist = CurProc#proc.hist, 104 | Rule = 105 | case Hist of 106 | [] -> 107 | ?NULL_RULE; 108 | [CurHist|_RestHist] -> 109 | case CurHist of 110 | {tau,_,_} -> ?RULE_SEQ; 111 | {self,_,_} -> ?RULE_SELF; 112 | {send,_,_, DestPid, {MsgValue, Time}} -> 113 | MsgList = [ M || M <- Msgs, M#msg.time == Time, 114 | M#msg.dest == DestPid, 115 | M#msg.val == MsgValue ], 116 | case MsgList of 117 | [] -> ?NULL_RULE; 118 | _ -> ?RULE_SEND 119 | end; 120 | {spawn,_,_,SpawnPid} -> 121 | {SpawnProc, _RestProcs} = utils:select_proc(RestProcs, SpawnPid), 122 | #proc{hist = SpawnHist, mail = SpawnMail} = SpawnProc, 123 | case {SpawnHist, SpawnMail} of 124 | {[], []} -> ?RULE_SPAWN; 125 | _ -> ?NULL_RULE 126 | end; 127 | {rec,_,_, ConsMsg, OldMail} -> 128 | Mail = CurProc#proc.mail, 129 | case utils:is_queue_minus_msg(OldMail, ConsMsg, Mail) of 130 | true -> ?RULE_RECEIVE; 131 | false -> ?NULL_RULE 132 | end 133 | end 134 | end, 135 | case Rule of 136 | ?NULL_RULE -> ?NULL_OPT; 137 | OtherRule -> 138 | Pid = CurProc#proc.pid, 139 | #opt{sem = ?MODULE, type = ?TYPE_PROC, id = cerl:concrete(Pid), rule = OtherRule} 140 | end. 141 | 142 | eval_sched_opt(Proc) -> 143 | #proc{hist = Hist, mail = Mail} = Proc, 144 | Rule = 145 | case Mail of 146 | [] -> ?NULL_RULE; 147 | _ -> 148 | {LastMsg,_} = utils:last_msg_rest(Mail), 149 | {_,Time} = LastMsg, 150 | TopRec = utils:topmost_rec(Hist), 151 | case TopRec of 152 | no_rec -> {?RULE_SCHED, Time}; 153 | {rec,_,_,OldMsg,OldMail} -> 154 | case utils:is_queue_minus_msg(OldMail, OldMsg, Mail) of 155 | false -> {?RULE_SCHED, Time}; 156 | true -> ?NULL_RULE 157 | end 158 | end 159 | end, 160 | case Rule of 161 | ?NULL_RULE -> ?NULL_OPT; 162 | {OtherRule, Id} -> 163 | #opt{sem = ?MODULE, type = ?TYPE_MSG, id = Id, rule = OtherRule} 164 | end. 165 | -------------------------------------------------------------------------------- /src/roll.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Rollback operator for the reversible semantics for Erlang 3 | %%% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(roll). 7 | -export([can_roll/2, can_roll_send/2, can_roll_spawn/2, 8 | can_roll_rec/2, can_roll_var/2, 9 | eval_step/2, eval_roll_send/2, eval_roll_spawn/2, 10 | eval_roll_rec/2, eval_roll_var/2]). 11 | 12 | -include("cauder.hrl"). 13 | 14 | can_roll(#sys{procs = Procs}, Pid) -> 15 | case utils:pid_exists(Procs, Pid) of 16 | false -> false; 17 | true -> 18 | {Proc, _} = utils:select_proc(Procs, Pid), 19 | Hist = Proc#proc.hist, 20 | Mail = Proc#proc.mail, 21 | case {Hist, Mail} of 22 | {[], []} -> false; 23 | _ -> true 24 | end 25 | end. 26 | 27 | eval_step(System, Pid) -> 28 | Procs = System#sys.procs, 29 | {Proc, _} = utils:select_proc(Procs, Pid), 30 | [CurHist|_]= Proc#proc.hist, 31 | case CurHist of 32 | {send, _, _, DestPid, {MsgValue, Time}} -> 33 | NewLog = System#sys.roll ++ utils:gen_log_send(Pid, DestPid, MsgValue, Time), 34 | LogSystem = System#sys{roll = NewLog}, 35 | ?LOG("ROLLing back SEND from " ++ ?TO_STRING(cerl:concrete(Pid)) ++ " to " ++ ?TO_STRING(cerl:concrete(DestPid))), 36 | roll_send(LogSystem, Pid, DestPid, Time); 37 | {spawn, _, _, SpawnPid} -> 38 | NewLog = System#sys.roll ++ utils:gen_log_spawn(Pid, SpawnPid), 39 | LogSystem = System#sys{roll = NewLog}, 40 | ?LOG("ROLLing back SPAWN of " ++ ?TO_STRING(cerl:concrete(SpawnPid))), 41 | roll_spawn(LogSystem, Pid, SpawnPid); 42 | _ -> 43 | RollOpts = roll_opts(System, Pid), 44 | cauder:eval_step(System, hd(RollOpts)) 45 | end. 46 | 47 | roll_send(System, Pid, OtherPid, Time) -> 48 | SendOpts = lists:filter(fun (X) -> X#opt.rule == ?RULE_SEND end, 49 | roll_opts(System, Pid)), 50 | case SendOpts of 51 | [] -> 52 | SchedOpts = [ X || X <- roll_sched_opts(System, OtherPid), 53 | X#opt.id == Time], 54 | case SchedOpts of 55 | [] -> 56 | NewSystem = eval_step(System, OtherPid), 57 | roll_send(NewSystem, Pid, OtherPid, Time); 58 | _ -> 59 | NewSystem = cauder:eval_step(System, hd(SchedOpts)), 60 | roll_send(NewSystem, Pid, OtherPid, Time) 61 | end; 62 | _ -> 63 | cauder:eval_step(System, hd(SendOpts)) 64 | end. 65 | 66 | roll_spawn(System, Pid, OtherPid) -> 67 | SpawnOpts = lists:filter(fun (X) -> X#opt.rule == ?RULE_SPAWN end, 68 | roll_opts(System, Pid)), 69 | case SpawnOpts of 70 | [] -> 71 | NewSystem = eval_step(System, OtherPid), 72 | roll_spawn(NewSystem, Pid, OtherPid); 73 | _ -> 74 | cauder:eval_step(System, hd(SpawnOpts)) 75 | end. 76 | 77 | can_roll_send(System, Id) -> 78 | Procs = System#sys.procs, 79 | ProcsWithSend = utils:select_proc_with_send(Procs, Id), 80 | case length(ProcsWithSend) of 81 | 0 -> false; 82 | _ -> true 83 | end. 84 | 85 | can_roll_spawn(System, SpawnPid) -> 86 | Procs = System#sys.procs, 87 | ProcsWithSpawn = utils:select_proc_with_spawn(Procs, SpawnPid), 88 | case length(ProcsWithSpawn) of 89 | 0 -> false; 90 | _ -> true 91 | end. 92 | 93 | can_roll_rec(System, Id) -> 94 | Procs = System#sys.procs, 95 | ProcsWithRec = utils:select_proc_with_rec(Procs, Id), 96 | case length(ProcsWithRec) of 97 | 0 -> false; 98 | _ -> true 99 | end. 100 | 101 | can_roll_var(System, Id) -> 102 | Procs = System#sys.procs, 103 | ProcsWithVar = utils:select_proc_with_var(Procs, Id), 104 | case length(ProcsWithVar) of 105 | 0 -> false; 106 | _ -> true 107 | end. 108 | 109 | eval_roll_send(System, Id) -> 110 | Procs = System#sys.procs, 111 | ProcsWithSend = utils:select_proc_with_send(Procs, Id), 112 | Proc = hd(ProcsWithSend), 113 | Pid = Proc#proc.pid, 114 | eval_roll_until_send(System, Pid, Id). 115 | 116 | eval_roll_spawn(System, SpawnPid) -> 117 | Procs = System#sys.procs, 118 | ProcsWithSpawn = utils:select_proc_with_spawn(Procs, SpawnPid), 119 | Proc = hd(ProcsWithSpawn), 120 | Pid = Proc#proc.pid, 121 | eval_roll_until_spawn(System, Pid, SpawnPid). 122 | 123 | eval_roll_rec(System, Id) -> 124 | Procs = System#sys.procs, 125 | ProcsWithRec = utils:select_proc_with_rec(Procs, Id), 126 | Proc = hd(ProcsWithRec), 127 | Pid = Proc#proc.pid, 128 | eval_roll_until_rec(System, Pid, Id). 129 | 130 | eval_roll_var(System, Id) -> 131 | Procs = System#sys.procs, 132 | ProcsWithVar = utils:select_proc_with_var(Procs, Id), 133 | Proc = hd(ProcsWithVar), 134 | Pid = Proc#proc.pid, 135 | eval_roll_until_var(System, Pid, Id). 136 | 137 | eval_roll_until_send(System, Pid, Id) -> 138 | Procs = System#sys.procs, 139 | {Proc, _} = utils:select_proc(Procs, Pid), 140 | [CurHist|_]= Proc#proc.hist, 141 | case CurHist of 142 | {send,_,_,_,{_, Id}} -> 143 | eval_step(System, Pid); 144 | _ -> 145 | NewSystem = eval_step(System, Pid), 146 | eval_roll_until_send(NewSystem, Pid, Id) 147 | end. 148 | 149 | eval_roll_until_spawn(System, Pid, SpawnPid) -> 150 | Procs = System#sys.procs, 151 | {Proc, _} = utils:select_proc(Procs, Pid), 152 | [CurHist|_]= Proc#proc.hist, 153 | case CurHist of 154 | {spawn,_,_,SpawnPid} -> 155 | eval_step(System, Pid); 156 | _ -> 157 | NewSystem = eval_step(System, Pid), 158 | eval_roll_until_spawn(NewSystem, Pid, SpawnPid) 159 | end. 160 | 161 | eval_roll_until_rec(System, Pid, Id) -> 162 | Procs = System#sys.procs, 163 | {Proc, _} = utils:select_proc(Procs, Pid), 164 | [CurHist|_]= Proc#proc.hist, 165 | case CurHist of 166 | {rec,_,_, {_, Id},_} -> 167 | eval_roll_after_rec(System, Pid, Id); 168 | _ -> 169 | NewSystem = eval_step(System, Pid), 170 | eval_roll_until_rec(NewSystem, Pid, Id) 171 | end. 172 | 173 | eval_roll_after_rec(System, Pid, Id) -> 174 | NewSystem = eval_step(System, Pid), 175 | Procs = NewSystem#sys.procs, 176 | {Proc, _} = utils:select_proc(Procs, Pid), 177 | [CurHist|_]= Proc#proc.hist, 178 | case CurHist of 179 | {rec,_,_, {_, Id},_} -> 180 | eval_roll_after_rec(NewSystem, Pid, Id); 181 | _ -> 182 | NewSystem 183 | end. 184 | 185 | eval_roll_until_var(System, Pid, Id) -> 186 | Procs = System#sys.procs, 187 | {Proc, _} = utils:select_proc(Procs, Pid), 188 | Env = Proc#proc.env, 189 | case utils:has_var(Env, Id) of 190 | false -> 191 | System; 192 | true -> 193 | NewSystem = eval_step(System, Pid), 194 | eval_roll_until_var(NewSystem, Pid, Id) 195 | end. 196 | 197 | roll_opts(System, Pid) -> 198 | ProcOpts = roll_procs_opts(System, Pid), 199 | SchedOpts = roll_sched_opts(System, Pid), 200 | SchedOpts ++ ProcOpts. 201 | 202 | roll_procs_opts(System, Pid) -> 203 | ProcOpts = bwd_sem:eval_procs_opts(System), 204 | utils:filter_options(ProcOpts, cerl:concrete(Pid)). 205 | 206 | roll_sched_opts(System, Pid) -> 207 | #sys{procs = Procs} = System, 208 | {Proc, _} = utils:select_proc(Procs, Pid), 209 | SingleProcSys = #sys{procs = [Proc]}, 210 | bwd_sem:eval_sched_opts(SingleProcSys). 211 | -------------------------------------------------------------------------------- /src/utils_gui.erl: -------------------------------------------------------------------------------- 1 | -module(utils_gui). 2 | -export([is_app_loaded/0, is_app_running/0, 3 | option_to_button_label/1, button_to_option/1, 4 | disable_rule_buttons/1, set_button_label_if/2, set_ref_button_if/2, 5 | set_choices/1, disable_all_buttons/0, enable_perm_buttons/0, 6 | clear_texts/0, stop_refs/0, update_status_text/1, 7 | sttext_single/1, sttext_mult/2, sttext_norm/1, 8 | sttext_roll/2, sttext_roll_send/2, sttext_roll_spawn/2, 9 | sttext_roll_rec/2, sttext_roll_var/2, sttext_comp/0, 10 | prev_font_size/1, next_font_size/1, sort_opts/1, toggle_opts/0, 11 | pp_marked_text/2, sched_opt/0]). 12 | 13 | -include("cauder.hrl"). 14 | -include("cauder_gui.hrl"). 15 | -include_lib("wx/include/wx.hrl"). 16 | 17 | is_app_loaded() -> 18 | Status = ref_lookup(?STATUS), 19 | #status{loaded = LoadedStatus} = Status, 20 | case LoadedStatus of 21 | {true, _} -> true; 22 | _Other -> false 23 | end. 24 | 25 | is_app_running() -> 26 | Status = ref_lookup(?STATUS), 27 | #status{running = RunningStatus} = Status, 28 | RunningStatus. 29 | 30 | get_label_from_option(Option) -> 31 | case Option of 32 | #opt{rule = ?RULE_SEQ} -> "Seq"; 33 | #opt{rule = ?RULE_SEND} -> "Send"; 34 | #opt{rule = ?RULE_RECEIVE} -> "Receive"; 35 | #opt{rule = ?RULE_SPAWN} -> "Spawn"; 36 | #opt{rule = ?RULE_SELF} -> "Self"; 37 | #opt{rule = ?RULE_SCHED} -> ?NULL_LABEL 38 | end. 39 | 40 | get_rule_from_button(Button) -> 41 | Label = wxButton:getLabel(ref_lookup(Button)), 42 | case Label of 43 | "Seq" -> ?RULE_SEQ; 44 | "Send" -> ?RULE_SEND; 45 | "Receive" -> ?RULE_RECEIVE; 46 | "Spawn" -> ?RULE_SPAWN; 47 | "Self" -> ?RULE_SELF 48 | end. 49 | 50 | button_to_option(Button) -> 51 | case Button of 52 | ?FORW_INT_BUTTON -> 53 | Rule = get_rule_from_button(Button), 54 | #opt{sem = ?FWD_SEM, type = ?TYPE_PROC, rule = Rule}; 55 | ?FORW_SCH_BUTTON -> 56 | #opt{sem = ?FWD_SEM, type = ?TYPE_MSG, rule = ?RULE_SCHED}; 57 | ?BACK_INT_BUTTON -> 58 | Rule = get_rule_from_button(Button), 59 | #opt{sem = ?BWD_SEM, type = ?TYPE_PROC, rule = Rule}; 60 | ?BACK_SCH_BUTTON -> 61 | #opt{sem = ?BWD_SEM, type = ?TYPE_MSG, rule = ?RULE_SCHED} 62 | end. 63 | 64 | option_to_button_label(Option) -> 65 | #opt{sem = Sem, type = Type} = Option, 66 | Label = get_label_from_option(Option), 67 | Button = 68 | case Sem of 69 | ?FWD_SEM -> 70 | case Type of 71 | ?TYPE_MSG -> ?FORW_SCH_BUTTON; 72 | ?TYPE_PROC -> ?FORW_INT_BUTTON 73 | end; 74 | ?BWD_SEM -> 75 | case Type of 76 | ?TYPE_MSG -> ?BACK_SCH_BUTTON; 77 | ?TYPE_PROC -> ?BACK_INT_BUTTON 78 | end 79 | end, 80 | {Button, Label}. 81 | 82 | disable_rule_buttons(Buttons) -> 83 | [wxButton:disable(ref_lookup(Button)) || Button <- Buttons]. 84 | 85 | set_button_label_if(Button, EnabledButtonLabels) -> 86 | RefButton = ref_lookup(Button), 87 | case lists:keyfind(Button, 1, EnabledButtonLabels) of 88 | false -> 89 | wxButton:disable(RefButton), 90 | case Button of 91 | ?FORW_INT_BUTTON -> wxButton:setLabel(RefButton, "Seq"); 92 | ?BACK_INT_BUTTON -> wxButton:setLabel(RefButton, "Seq"); 93 | _Other -> ok 94 | end; 95 | {Button, Label} -> 96 | wxButton:enable(RefButton), 97 | case Label of 98 | ?NULL_LABEL -> ok; 99 | Label -> wxButton:setLabel(RefButton, Label) 100 | end 101 | end. 102 | 103 | set_ref_button_if(Ref, Cond) -> 104 | RefButton = ref_lookup(Ref), 105 | case Cond of 106 | true -> 107 | wxButton:enable(RefButton); 108 | false -> 109 | wxButton:disable(RefButton) 110 | end. 111 | 112 | disable_all_buttons() -> 113 | ForwIntButton = ref_lookup(?FORW_INT_BUTTON), 114 | ForwSchButton = ref_lookup(?FORW_SCH_BUTTON), 115 | BackIntButton = ref_lookup(?BACK_INT_BUTTON), 116 | BackSchButton = ref_lookup(?BACK_SCH_BUTTON), 117 | ForwardButton = ref_lookup(?FORWARD_BUTTON), 118 | BackwardButton = ref_lookup(?BACKWARD_BUTTON), 119 | NormalizeButton = ref_lookup(?NORMALIZE_BUTTON), 120 | RollButton = ref_lookup(?ROLL_BUTTON), 121 | RollSpawnButton = ref_lookup(?ROLL_SPAWN_BUTTON), 122 | RollSendButton = ref_lookup(?ROLL_SEND_BUTTON), 123 | RollRecButton = ref_lookup(?ROLL_REC_BUTTON), 124 | RollVarButton = ref_lookup(?ROLL_VAR_BUTTON), 125 | wxButton:disable(ForwIntButton), 126 | wxButton:disable(ForwSchButton), 127 | wxButton:disable(BackIntButton), 128 | wxButton:disable(BackSchButton), 129 | wxButton:disable(ForwardButton), 130 | wxButton:disable(BackwardButton), 131 | wxButton:disable(NormalizeButton), 132 | wxButton:disable(RollButton), 133 | wxButton:disable(RollSpawnButton), 134 | wxButton:disable(RollSendButton), 135 | wxButton:disable(RollRecButton), 136 | wxButton:disable(RollVarButton). 137 | 138 | enable_perm_buttons() -> 139 | RollButton = ref_lookup(?ROLL_BUTTON), 140 | RollSpawnButton = ref_lookup(?ROLL_SPAWN_BUTTON), 141 | RollSendButton = ref_lookup(?ROLL_SEND_BUTTON), 142 | RollRecButton = ref_lookup(?ROLL_REC_BUTTON), 143 | RollVarButton = ref_lookup(?ROLL_VAR_BUTTON), 144 | wxButton:enable(RollButton), 145 | wxButton:enable(RollSpawnButton), 146 | wxButton:enable(RollSendButton), 147 | wxButton:enable(RollRecButton), 148 | wxButton:enable(RollVarButton). 149 | 150 | set_choices(Choices) -> 151 | FunChoice = ref_lookup(?FUN_CHOICE), 152 | wxChoice:clear(FunChoice), 153 | [wxChoice:append(FunChoice, Choice) || Choice <- Choices], 154 | wxChoice:setSelection(FunChoice, 0). 155 | 156 | clear_texts() -> 157 | StateText = ref_lookup(?STATE_TEXT), 158 | TraceText = ref_lookup(?TRACE_TEXT), 159 | RollLogText = ref_lookup(?ROLL_LOG_TEXT), 160 | wxTextCtrl:clear(StateText), 161 | wxTextCtrl:clear(TraceText), 162 | wxTextCtrl:clear(RollLogText). 163 | 164 | stop_refs() -> 165 | case is_app_running() of 166 | true -> 167 | cauder:stop_refs(), 168 | ok; 169 | false -> ok 170 | end. 171 | 172 | sttext_single(Button) -> 173 | Option = button_to_option(Button), 174 | #opt{sem = Sem} = Option, 175 | SemStr = 176 | case Sem of 177 | ?FWD_SEM -> " forward "; 178 | ?BWD_SEM -> " backward " 179 | end, 180 | Label = get_label_from_option(Option), 181 | LabelStr = 182 | case Label of 183 | ?NULL_LABEL -> "Sched"; 184 | _Other -> Label 185 | end, 186 | FullStr = "Fired" ++ SemStr ++ LabelStr ++ " rule", 187 | update_status_text(FullStr). 188 | 189 | sttext_norm(Steps) -> 190 | StepsStr = integer_to_list(Steps), 191 | FullStr = StepsStr ++ " steps done", 192 | update_status_text(FullStr). 193 | 194 | sttext_mult(StepsDone, Steps) -> 195 | StepsDoneStr = integer_to_list(StepsDone), 196 | StepsStr = integer_to_list(Steps), 197 | FullStr = StepsDoneStr ++ " of " ++ StepsStr ++ " steps done", 198 | update_status_text(FullStr). 199 | 200 | sttext_roll(StepsDone, Steps) -> 201 | StepsDoneStr = integer_to_list(StepsDone), 202 | StepsStr = integer_to_list(Steps), 203 | FullStr = StepsDoneStr ++ " of " ++ StepsStr ++ " steps rolled back", 204 | update_status_text(FullStr). 205 | 206 | sttext_roll_send(false, _) -> 207 | FullStr = "Could not roll back the sending of that message", 208 | update_status_text(FullStr); 209 | sttext_roll_send(true, Id) -> 210 | FullStr = "Rolled back sending of message with id " ++ Id, 211 | update_status_text(FullStr). 212 | 213 | sttext_roll_spawn(false, _) -> 214 | FullStr = "Could not roll back the spawning of that process", 215 | update_status_text(FullStr); 216 | sttext_roll_spawn(true, Id) -> 217 | FullStr = "Rolled back spawning of process with Pid " ++ Id, 218 | update_status_text(FullStr). 219 | 220 | sttext_roll_rec(false, _) -> 221 | FullStr = "Could not roll back the receiving of that message", 222 | update_status_text(FullStr); 223 | sttext_roll_rec(true, Id) -> 224 | FullStr = "Rolled back receiving of message with id " ++ Id, 225 | update_status_text(FullStr). 226 | 227 | sttext_roll_var(false, _) -> 228 | FullStr = "Could not roll back the binding of that variable", 229 | update_status_text(FullStr); 230 | sttext_roll_var(true, Id) -> 231 | FullStr = "Rolled back binding of variable " ++ Id, 232 | update_status_text(FullStr). 233 | 234 | sttext_comp() -> 235 | FullStr = "Compiler options have changed, open file again to take effect", 236 | update_status_text(FullStr). 237 | 238 | update_status_text(String) -> 239 | Frame = ref_lookup(?FRAME), 240 | wxFrame:setStatusText(Frame, String). 241 | 242 | index_of(Elem, List) -> index_of(Elem, List, 1). 243 | 244 | index_of(_, [], _) -> not_found; 245 | index_of(Elem, [Elem|_], Index) -> Index; 246 | index_of(Elem, [_|Rest], Index) -> index_of(Elem, Rest, Index + 1). 247 | 248 | prev_font_size(CurSize) -> 249 | SizeIdx = index_of(CurSize, ?FONT_SIZES), 250 | case SizeIdx == 1 of 251 | true -> CurSize; 252 | false -> lists:nth(SizeIdx - 1, ?FONT_SIZES) 253 | end. 254 | 255 | next_font_size(CurSize) -> 256 | SizeIdx = index_of(CurSize, ?FONT_SIZES), 257 | SizeLen = length(?FONT_SIZES), 258 | case SizeIdx == SizeLen of 259 | true -> CurSize; 260 | false -> lists:nth(SizeIdx + 1, ?FONT_SIZES) 261 | end. 262 | 263 | sort_opts(Opts) -> 264 | SortOpts = lists:sort(fun(P1, P2) -> P1#opt.id < P2#opt.id end, Opts), 265 | SortOpts. 266 | 267 | toggle_opts() -> 268 | MenuView = ref_lookup(?MENU_VIEW), 269 | MenuComp = ref_lookup(?MENU_COMP), 270 | [{?PRINT_MAIL, wxMenu:isChecked(MenuView, ?TOGGLE_MAIL)}, 271 | {?PRINT_HIST, wxMenu:isChecked(MenuView, ?TOGGLE_HIST)}, 272 | {?PRINT_ENV, wxMenu:isChecked(MenuView, ?TOGGLE_ENV)}, 273 | {?PRINT_EXP, wxMenu:isChecked(MenuView, ?TOGGLE_EXP)}, 274 | %%added toggle for graphic viewer 275 | {?PRINT_VIEWER, wxMenu:isChecked(MenuView, ?TOGGLE_VIEWER)}, 276 | %% 277 | {?PRINT_FULL, wxMenu:isChecked(MenuView, ?RADIO_FULL)}, 278 | {?COMP_OPT, wxMenu:isChecked(MenuComp, ?TOGGLE_COMP)}, 279 | {?PRINT_FULL_ENV, wxMenu:isChecked(MenuView, ?RADIO_FULL_ENV)}]. 280 | 281 | sched_opt() -> 282 | MenuSched = ref_lookup(?MENU_SCHED), 283 | SchedOpts = 284 | [{wxMenu:isChecked(MenuSched, ?RADIO_RAND), ?SCHED_RANDOM}, 285 | {wxMenu:isChecked(MenuSched, ?RADIO_PRIO), ?SCHED_PRIO_RANDOM}], 286 | proplists:get_value(true, SchedOpts). 287 | 288 | marked(Ctrl, [], Acc) -> 289 | wxTextCtrl:appendText(Ctrl, Acc); 290 | marked(Ctrl, [{Attr, Text}|Rest], Acc) -> 291 | wxTextCtrl:appendText(Ctrl, Acc), 292 | TextAttr = wxTextAttr:new(Attr), 293 | Start = wxTextCtrl:getInsertionPoint(Ctrl), 294 | wxTextCtrl:appendText(Ctrl, Text), 295 | End = wxTextCtrl:getInsertionPoint(Ctrl), 296 | wxTextCtrl:setStyle(Ctrl, Start, End, TextAttr), 297 | marked(Ctrl, Rest, ""); 298 | marked(Ctrl, [Text|Rest], Acc) -> 299 | marked(Ctrl, Rest, Acc ++ [Text]). 300 | 301 | pp_marked_text(Ctrl, TextList) -> 302 | % Freeze control when inserting text 303 | wxTextCtrl:freeze(Ctrl), 304 | wxTextCtrl:clear(Ctrl), 305 | marked(Ctrl, TextList, ""), 306 | % Put scroll back at the top 307 | wxTextCtrl:setInsertionPoint(Ctrl, 0), 308 | % Unfreeze control 309 | wxTextCtrl:thaw(Ctrl). 310 | 311 | ref_lookup(Id) -> 312 | ets:lookup_element(?GUI_REF, Id, 2). 313 | -------------------------------------------------------------------------------- /src/License_et_viewer.txt: -------------------------------------------------------------------------------- 1 | PAY ATTENTION: 2 | This license is only referred to files et_viewer.erl and at_wx_viewer.erl,inside the folder of this license 3 | ---------------------------------------------------------------------------------------------------- 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | APPENDIX: How to apply the Apache License to your work. 183 | 184 | To apply the Apache License to your work, attach the following 185 | boilerplate notice, with the fields enclosed by brackets "[]" 186 | replaced with your own identifying information. (Don't include 187 | the brackets!) The text should be enclosed in the appropriate 188 | comment syntax for the file format. We also recommend that a 189 | file or class name and description of purpose be included on the 190 | same "printed page" as the copyright notice for easier 191 | identification within third-party archives. 192 | 193 | Copyright [yyyy] [name of copyright owner] 194 | 195 | Licensed under the Apache License, Version 2.0 (the "License"); 196 | you may not use this file except in compliance with the License. 197 | You may obtain a copy of the License at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 204 | See the License for the specific language governing permissions and 205 | limitations under the License. 206 | -------------------------------------------------------------------------------- /src/fwd_sem.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Some functions that implement the forward (reversible) 3 | %%% semantics for Erlang. These can be divided into functions to get 4 | %%% the evaluation options and functions to perform the evaluation 5 | %%% @end 6 | %%%------------------------------------------------------------------- 7 | 8 | -module(fwd_sem). 9 | -export([eval_step/2, eval_sched/2, 10 | eval_opts/1, eval_procs_opts/1, eval_sched_opts/1]). 11 | 12 | -include("cauder.hrl"). 13 | 14 | eval_seq(Env,Exp) -> 15 | case is_list(Exp) of 16 | true -> eval_list(Env,Exp); 17 | false -> eval_seq_1(Env,Exp) 18 | end. 19 | 20 | eval_seq_1(Env,Exp) -> 21 | case cerl:type(Exp) of 22 | var -> 23 | Value = proplists:get_value(Exp, Env), 24 | {Env,Value,tau}; 25 | cons -> 26 | ConsHdExp = cerl:cons_hd(Exp), 27 | ConsTlExp = cerl:cons_tl(Exp), 28 | case is_exp(cerl:cons_hd(Exp)) of 29 | true -> 30 | {NewEnv,NewConsHdExp,Label} = eval_seq(Env,ConsHdExp), 31 | NewExp = cerl:c_cons_skel(NewConsHdExp, 32 | ConsTlExp); 33 | false -> 34 | {NewEnv,NewConsTlExp,Label} = eval_seq(Env,ConsTlExp), 35 | NewExp = cerl:c_cons_skel(ConsHdExp, 36 | NewConsTlExp) 37 | end, 38 | {NewEnv,NewExp,Label}; 39 | values -> 40 | {NewEnv, NewValuesEs, Label} = eval_list(Env, cerl:values_es(Exp)), 41 | NewExp = cerl:c_values(NewValuesEs), 42 | {NewEnv, NewExp, Label}; 43 | tuple -> 44 | {NewEnv, NewTupleEs, Label} = eval_list(Env, cerl:tuple_es(Exp)), 45 | NewExp = cerl:c_tuple_skel(NewTupleEs), 46 | {NewEnv, NewExp, Label}; 47 | apply -> 48 | ApplyArgs = cerl:apply_args(Exp), 49 | ApplyOp = cerl:apply_op(Exp), 50 | case is_exp(ApplyArgs) of 51 | true -> 52 | {NewEnv,NewApplyArgs,Label} = eval_seq(Env,ApplyArgs), 53 | NewExp = cerl:update_c_apply(Exp, 54 | ApplyOp, 55 | NewApplyArgs), 56 | {NewEnv,NewExp,Label}; 57 | false -> 58 | FunDefs = ref_lookup(?FUN_DEFS), 59 | FunDef = utils:fundef_lookup(ApplyOp, FunDefs), 60 | NewFunDef = utils:fundef_rename(FunDef), 61 | FunBody = cerl:fun_body(NewFunDef), 62 | FunArgs = cerl:fun_vars(NewFunDef), 63 | % standard zip is used here (pretty-printer forces it) 64 | NewEnv = utils:merge_env(Env, lists:zip(FunArgs,ApplyArgs)), 65 | {NewEnv,FunBody,tau} 66 | end; 67 | 'case' -> 68 | CaseArg = cerl:case_arg(Exp), 69 | case is_exp(CaseArg) of 70 | true -> 71 | {NewEnv,NewCaseArg,Label} = eval_seq(Env,CaseArg), 72 | NewExp = cerl:update_c_case(Exp, 73 | NewCaseArg, 74 | cerl:case_clauses(Exp)), 75 | {NewEnv,NewExp,Label}; 76 | false -> 77 | %io:format("Env: ~p\n",[Env]), 78 | %io:format("CaseArg: ~p\n",[CaseArg]), 79 | CaseClauses = cerl:case_clauses(Exp), 80 | %io:format("CaseClauses: ~p\n",[CaseClauses]), 81 | CaseClauses2 = replace_guards(Env,CaseClauses), 82 | %io:format("CaseClauses2: ~p\n",[CaseClauses2]), 83 | %CaseClauses3 = init(CaseClauses2), 84 | CaseArgs = 85 | case cerl:type(CaseArg) of 86 | values -> cerl:values_es(CaseArg); 87 | _ -> [CaseArg] 88 | end, 89 | case cerl_clauses:reduce(CaseClauses2,CaseArgs) of 90 | {true,{Clause,Bindings}} -> 91 | ClauseBody = cerl:clause_body(Clause), 92 | NewEnv = utils:merge_env(Env, Bindings), 93 | {NewEnv,ClauseBody,tau}; 94 | {false,_} -> 95 | io:fwrite("Error: No matching clause~n") 96 | end 97 | end; 98 | 'let' -> 99 | LetArg = cerl:let_arg(Exp), 100 | case is_exp(LetArg) of 101 | true -> 102 | {NewEnv,NewLetArg,Label} = eval_seq(Env,LetArg), 103 | NewExp = cerl:update_c_let(Exp, 104 | cerl:let_vars(Exp), 105 | NewLetArg, 106 | cerl:let_body(Exp)), 107 | {NewEnv,NewExp,Label}; 108 | false -> 109 | LetVars = cerl:let_vars(Exp), 110 | LetEnv = 111 | case cerl:let_arity(Exp) of 112 | 1 -> lists:zip(LetVars,[LetArg]); 113 | _ -> 114 | FlatLetArg = 115 | case cerl:type(LetArg) of 116 | values -> 117 | cerl:values_es(LetArg); 118 | _ -> LetArg 119 | end, 120 | lists:zip(LetVars,FlatLetArg) 121 | end, 122 | NewEnv = utils:merge_env(Env, LetEnv), 123 | NewExp = cerl:let_body(Exp), 124 | {NewEnv,NewExp,tau} 125 | end; 126 | call -> 127 | CallArgs = cerl:call_args(Exp), 128 | CallModule = cerl:call_module(Exp), 129 | CallName = cerl:call_name(Exp), 130 | 131 | case is_exp(CallModule) of 132 | true -> 133 | {NewEnv,NewCallModule,Label} = eval_seq(Env,CallModule), 134 | NewExp = cerl:update_c_call(Exp, 135 | NewCallModule, 136 | CallName, 137 | CallArgs), 138 | {NewEnv,NewExp,Label}; 139 | false -> 140 | case is_exp(CallName) of 141 | true -> 142 | {NewEnv,NewCallName,Label} = eval_seq(Env,CallName), 143 | NewExp = cerl:update_c_call(Exp, 144 | CallModule, 145 | NewCallName, 146 | CallArgs), 147 | {NewEnv,NewExp,Label}; 148 | false -> 149 | case is_exp(CallArgs) of 150 | true -> 151 | {NewEnv,NewCallArgs,Label} = eval_list(Env,CallArgs), 152 | NewExp = cerl:update_c_call(Exp, 153 | CallModule, 154 | CallName, 155 | NewCallArgs), 156 | {NewEnv,NewExp,Label}; 157 | false -> 158 | case {CallModule, CallName} of 159 | {{c_literal,_,'erlang'},{c_literal,_,'spawn'}} -> 160 | VarNum = ref_lookup(?FRESH_VAR), 161 | ref_add(?FRESH_VAR, VarNum + 1), 162 | Var = utils:build_var(VarNum), 163 | FunName = lists:nth(2,CallArgs), 164 | FunArgs = utils:list_from_core(lists:nth(3,CallArgs)), 165 | {Env,Var,{spawn,{Var,FunName,FunArgs}}}; 166 | {{c_literal,_,'erlang'},{c_literal, _, 'self'}} -> 167 | VarNum = ref_lookup(?FRESH_VAR), 168 | ref_add(?FRESH_VAR, VarNum + 1), 169 | Var = utils:build_var(VarNum), 170 | {Env, Var, {self, Var}}; 171 | {{c_literal,_,'erlang'},{c_literal, _, '!'}} -> 172 | DestPid = lists:nth(1, CallArgs), 173 | MsgValue = lists:nth(2, CallArgs), 174 | {Env, MsgValue, {send, DestPid, MsgValue}}; 175 | {{c_literal,_,'timer'},{c_literal,_,'sleep'}} -> 176 | NewExp = cerl:c_atom('ok'), 177 | {Env, NewExp, tau}; 178 | _ -> 179 | ToggleOpts = utils_gui:toggle_opts(), 180 | AddOptimize = proplists:get_value(?COMP_OPT, ToggleOpts), 181 | CompOpts = 182 | case AddOptimize of 183 | true -> [to_core,binary]; 184 | false -> [to_core,binary, no_copt] 185 | end, 186 | Filename = cerl:concrete(CallModule), 187 | Path = ets:lookup_element(?GUI_REF,?LAST_PATH,2), 188 | File = filename:join(Path,Filename), 189 | case compile:file(File, CompOpts) of 190 | {ok, _, CoreForms} -> 191 | NoAttsCoreForms = cerl:update_c_module(CoreForms, 192 | cerl:module_name(CoreForms), 193 | cerl:module_exports(CoreForms), 194 | [], 195 | cerl:module_defs(CoreForms)), 196 | Stripper = fun(Tree) -> cerl:set_ann(Tree, []) end, 197 | CleanCoreForms = cerl_trees:map(Stripper, NoAttsCoreForms), 198 | FunDefs = cerl:module_defs(CleanCoreForms), 199 | ConcName = cerl:concrete(CallName), 200 | %ConcArgs = [utils:toErlang(Arg) || Arg <- CallArgs], 201 | %io:fwrite("---------------~n"), 202 | %io:write(CallName), 203 | %io:fwrite("~n---------------~n"), 204 | %FunDef = utils:fundef_lookup(CallName, FunDefs), 205 | FunDef = utils:fundef_lookup(cerl:c_var({ConcName,cerl:call_arity(Exp)}), FunDefs), 206 | NewFunDef = utils:fundef_rename(FunDef), 207 | FunBody = cerl:fun_body(NewFunDef), 208 | FunArgs = cerl:fun_vars(NewFunDef), 209 | % standard zip is used here (pretty-printer forces it) 210 | NewEnv = utils:merge_env(Env, lists:zip(FunArgs,CallArgs)), %ApplyArgs 211 | {NewEnv,FunBody,tau}; 212 | error -> %for builtin 213 | ConcModule = cerl:concrete(CallModule), 214 | ConcName = cerl:concrete(CallName), 215 | ConcArgs = [utils:toErlang(Arg) || Arg <- CallArgs], 216 | ConcExp = apply(ConcModule, ConcName, ConcArgs), 217 | StrExp = lists:flatten(io_lib:format("~p", ([ConcExp]))) ++ ".", 218 | {ok, ParsedExp, _} = erl_scan:string(StrExp), 219 | {ok, TypedExp} = erl_parse:parse_exprs(ParsedExp), 220 | CoreExp = hd([utils:toCore(Expr) || Expr <- TypedExp]), 221 | NewExp = CoreExp, 222 | {Env, NewExp, tau} 223 | end 224 | end 225 | end 226 | end 227 | end; 228 | seq -> 229 | SeqArg = cerl:seq_arg(Exp), 230 | case is_exp(SeqArg) of 231 | true -> 232 | {NewEnv,NewSeqArg,Label} = eval_seq(Env,SeqArg), 233 | NewExp = cerl:update_c_seq(Exp, 234 | NewSeqArg, 235 | cerl:seq_body(Exp)), 236 | {NewEnv,NewExp,Label}; 237 | false -> 238 | NewExp = cerl:seq_body(Exp), 239 | {Env,NewExp,tau} 240 | end; 241 | 'receive' -> 242 | VarNum = ref_lookup(?FRESH_VAR), 243 | ref_add(?FRESH_VAR, VarNum + 1), 244 | Var = utils:build_var(VarNum), 245 | % SubsExp = utils:substitute(Exp, Env), 246 | % {Env, Var, {rec, Var, cerl:receive_clauses(SubsExp)}} 247 | ReceiveClauses = cerl:receive_clauses(Exp), 248 | %%ReceiveClauses2 = replace_guards(Env,ReceiveClauses), 249 | {Env, Var, {rec, Var, ReceiveClauses}} 250 | end. 251 | 252 | %init([_X]) -> []; 253 | %nit([A|R]) -> [A|init(R)]. 254 | 255 | replace_guards(Bindings,Exps) -> 256 | lists:map(fun({c_clause,L,Pats,Guard,Exp}) -> 257 | Guard2 = utils:replace_all(Bindings,Guard), 258 | Guard3 = eval_guard(Guard2), 259 | {c_clause,L,Pats,Guard3,Exp} 260 | %case ReducedGuard of 261 | % {value,true} -> {c_clause,L,Pats,true,Exp}; 262 | % _Other -> {c_clause,L,Pats,ReducedGuard,Exp} 263 | %end 264 | end, Exps). 265 | 266 | eval_guard(Exp) -> 267 | case cerl:type(Exp) of 268 | call -> 269 | CallArgs = cerl:call_args(Exp), 270 | CallModule = cerl:call_module(Exp), 271 | CallName = cerl:call_name(Exp), 272 | ConcModule = cerl:concrete(CallModule), 273 | ConcName = cerl:concrete(CallName), 274 | ConcArgs = [utils:toErlang(Arg) || Arg <- CallArgs], 275 | ConcExp = apply(ConcModule, ConcName, ConcArgs), 276 | StrExp = lists:flatten(io_lib:format("~p", ([ConcExp]))) ++ ".", 277 | %%io:format("ConcModule: ~p\nConcName: ~p\nConcArgs: ~p\nConcExp: ~p\nStrExp: ~p\n",[ConcModule,ConcName,ConcArgs,ConcExp,StrExp]), 278 | {ok, ParsedExp, _} = erl_scan:string(StrExp), 279 | {ok, TypedExp} = erl_parse:parse_exprs(ParsedExp), 280 | hd([utils:toCore(Expr) || Expr <- TypedExp]); 281 | 'let' -> 282 | %io:format("1)~w~n",[Exp]), 283 | LetArg = cerl:let_arg(Exp), 284 | case is_exp(LetArg) of 285 | true -> 286 | NewLetArg=eval_guard(LetArg), 287 | NewExp = cerl:update_c_let(Exp, 288 | cerl:let_vars(Exp), 289 | NewLetArg, 290 | cerl:let_body(Exp)), 291 | eval_guard(NewExp); 292 | false -> 293 | LetVars = cerl:let_vars(Exp), 294 | LetEnv = 295 | case cerl:let_arity(Exp) of 296 | 1 -> lists:zip(LetVars,[LetArg]); 297 | _ -> 298 | FlatLetArg = 299 | case cerl:type(LetArg) of 300 | values -> 301 | cerl:values_es(LetArg); 302 | _ -> LetArg 303 | end, 304 | lists:zip(LetVars,FlatLetArg) 305 | end, 306 | NewExp = cerl:let_body(Exp), 307 | %io:format("2)~w~n",[NewExp]), 308 | %io:format("2e)~w~n",[LetEnv]), 309 | SubstExp=utils:replace_all(LetEnv,NewExp), 310 | %io:format("3)~w~n",[SubstExp]), 311 | %StrExp = lists:flatten(io_lib:format("~p", ([SubstExp]))) ++ ".", 312 | %%%io:format("ConcModule: ~p\nConcName: ~p\nConcArgs: ~p\nConcExp: ~p\nStrExp: ~p\n",[ConcModule,ConcName,ConcArgs,ConcExp,StrExp]), 313 | %{ok, ParsedExp, _} = erl_scan:string(StrExp), 314 | %{ok, TypedExp} = erl_parse:parse_exprs(ParsedExp), 315 | %TypExpr=hd([utils:toCore(Expr) || Expr <- TypedExp]), 316 | %FinalExp=eval_guard(TypExpr), 317 | %io:format("4)~w~n",[FinalExp]), 318 | eval_guard(SubstExp) 319 | end; 320 | _Other -> Exp 321 | end. 322 | 323 | %%-------------------------------------------------------------------- 324 | %% @doc Performs an evaluation step in process Pid, given System 325 | %% @end 326 | %%-------------------------------------------------------------------- 327 | eval_step(System, Pid) -> 328 | Msgs = System#sys.msgs, 329 | Procs = System#sys.procs, 330 | Trace = System#sys.trace, 331 | {Proc, RestProcs} = utils:select_proc(Procs, Pid), 332 | #proc{pid = Pid, hist = Hist, env = Env, exp = Exp, mail = Mail} = Proc, 333 | {NewEnv, NewExp, Label} = eval_seq(Env, Exp), 334 | NewSystem = 335 | case Label of 336 | tau -> 337 | NewProc = Proc#proc{hist = [{tau,Env,Exp}|Hist], env = NewEnv, exp = NewExp}, 338 | System#sys{msgs = Msgs, procs = [NewProc|RestProcs]}; 339 | {self, Var} -> 340 | NewHist = [{self, Env, Exp}|Hist], 341 | RepExp = utils:replace(Var, Pid, NewExp), 342 | NewProc = Proc#proc{hist = NewHist, env = NewEnv, exp = RepExp}, 343 | System#sys{msgs = Msgs, procs = [NewProc|RestProcs]}; 344 | {send, DestPid, MsgValue} -> 345 | Time = ref_lookup(?FRESH_TIME), 346 | ref_add(?FRESH_TIME, Time + 1), 347 | NewMsg = #msg{dest = DestPid, val = MsgValue, time = Time}, 348 | NewMsgs = [NewMsg|Msgs], 349 | NewHist = [{send, Env, Exp, DestPid, {MsgValue, Time}}|Hist], 350 | NewProc = Proc#proc{hist = NewHist, env = NewEnv, exp = NewExp}, 351 | TraceItem = #trace{type = ?RULE_SEND, from = Pid, to = DestPid, val = MsgValue, time = Time}, 352 | NewTrace = [TraceItem|Trace], 353 | System#sys{msgs = NewMsgs, procs = [NewProc|RestProcs], trace = NewTrace}; 354 | {spawn, {Var, FunName, FunArgs}} -> 355 | PidNum = ref_lookup(?FRESH_PID), 356 | ref_add(?FRESH_PID, PidNum + 1), 357 | SpawnPid = cerl:c_int(PidNum), 358 | ArgsLen = length(FunArgs), 359 | FunCall = cerl:c_var({cerl:concrete(FunName), ArgsLen}), 360 | SpawnProc = #proc{pid = SpawnPid, 361 | env = [], 362 | exp = cerl:c_apply(FunCall,FunArgs), 363 | spf = cerl:var_name(FunCall)}, 364 | NewHist = [{spawn, Env, Exp, SpawnPid}|Hist], 365 | RepExp = utils:replace(Var, SpawnPid, NewExp), 366 | NewProc = Proc#proc{hist = NewHist, env = NewEnv, exp = RepExp}, 367 | TraceItem = #trace{type = ?RULE_SPAWN, from = Pid, to = SpawnPid}, 368 | NewTrace = [TraceItem|Trace], 369 | System#sys{msgs = Msgs, procs = [NewProc|[SpawnProc|RestProcs]], trace = NewTrace}; 370 | {rec, Var, ReceiveClauses} -> 371 | {Bindings, RecExp, ConsMsg, NewMail} = matchrec(ReceiveClauses, Mail, NewEnv), 372 | UpdatedEnv = utils:merge_env(NewEnv, Bindings), 373 | RepExp = utils:replace(Var, RecExp, NewExp), 374 | NewHist = [{rec, Env, Exp, ConsMsg, Mail}|Hist], 375 | NewProc = Proc#proc{hist = NewHist, env = UpdatedEnv, exp = RepExp, mail = NewMail}, 376 | {MsgValue, Time} = ConsMsg, 377 | TraceItem = #trace{type = ?RULE_RECEIVE, from = Pid, val = MsgValue, time = Time}, 378 | NewTrace = [TraceItem|Trace], 379 | System#sys{msgs = Msgs, procs = [NewProc|RestProcs], trace = NewTrace} 380 | end, 381 | NewSystem. 382 | 383 | %%-------------------------------------------------------------------- 384 | %% @doc Performs an evaluation step in message Id, given System 385 | %% @end 386 | %%-------------------------------------------------------------------- 387 | eval_sched(System, Id) -> 388 | Procs = System#sys.procs, 389 | Msgs = System#sys.msgs, 390 | {Msg, RestMsgs} = utils:select_msg(Msgs, Id), 391 | #msg{dest = DestPid, val = Value, time = Id} = Msg, 392 | {Proc, RestProcs} = utils:select_proc(Procs, DestPid), 393 | Mail = Proc#proc.mail, 394 | NewMail = Mail ++ [{Value, Id}], 395 | NewProc = Proc#proc{mail = NewMail}, 396 | System#sys{msgs = RestMsgs, procs = [NewProc|RestProcs]}. 397 | 398 | is_exp([]) -> false; 399 | is_exp(Exp) when is_list(Exp) -> 400 | lists:any(fun is_exp/1, Exp); 401 | is_exp(Exp) -> 402 | case cerl:type(Exp) of 403 | literal -> false; 404 | nil -> false; 405 | cons -> is_exp(cerl:cons_hd(Exp)) or is_exp(cerl:cons_tl(Exp)); 406 | values -> is_exp(cerl:values_es(Exp)); 407 | tuple -> is_exp(cerl:tuple_es(Exp)); 408 | _Other -> true 409 | end. 410 | 411 | eval_list(Env,[Exp|Exps]) -> 412 | case is_exp(Exp) of 413 | true -> 414 | {NewEnv,NewExp,Label} = eval_seq(Env,Exp), 415 | {NewEnv,[NewExp|Exps],Label}; 416 | false -> 417 | {NewEnv,NewExp,Label} = eval_list(Env,Exps), 418 | {NewEnv,[Exp|NewExp],Label} 419 | end. 420 | 421 | matchrec(Clauses, Mail,Env) -> 422 | matchrec(Clauses, Mail, [],Env). 423 | 424 | matchrec(_, [], _, _) -> 425 | no_match; 426 | matchrec(Clauses, [CurMsg|RestMsgs], AccMsgs, Env) -> 427 | {MsgValue, _MsgTime} = CurMsg, 428 | %io:format("matchrec (MsgValue): ~p~n",[MsgValue]), 429 | %io:format("matchrec (Clauses): ~p~n",[Clauses]), 430 | %%preprocessing is used to propagate matching bindings to guards 431 | NewClauses = preprocessing_clauses(Clauses,MsgValue,Env), 432 | %io:format("matchrec (NewClauses): ~p~n",[NewClauses]), 433 | case cerl_clauses:reduce(NewClauses, [MsgValue]) of 434 | {true, {Clause, Bindings}} -> 435 | ClauseBody = cerl:clause_body(Clause), 436 | NewMsgs = AccMsgs ++ RestMsgs, 437 | {Bindings, ClauseBody, CurMsg, NewMsgs}; 438 | {false, []} -> 439 | matchrec(Clauses, RestMsgs, AccMsgs ++ [CurMsg],Env); 440 | {false, [Clause|OtherClauses]} -> io:format("CauDEr: Unsupported pattern, some behaviours may be missed ~n~w~n",[Clause]), 441 | matchrec(Clauses, RestMsgs, AccMsgs ++ [CurMsg],Env) 442 | end. 443 | 444 | preprocessing_clauses(Clauses,Msg,Env) -> 445 | lists:map(fun({c_clause,L,Pats,Guard,Exp}) -> 446 | %io:format("Clauses: ~p~n",[Clauses]), 447 | %io:format("match (Pats/[Msg]) ~p~n~p~n",[Pats,[Msg]]), 448 | %io:format("--result: ~p~n",[cerl_clauses:match_list(Pats,[Msg])]), 449 | case cerl_clauses:match_list(Pats,[Msg]) of 450 | {true,Bindings} -> Guard2 = utils:replace_all(Bindings++Env,Guard), 451 | %io:format("calling eval_guard (Bindings/Guard/Guard2): ~n~p~n~p~n~p~n",[Bindings++Env,Guard,Guard2]), 452 | Guard3 = eval_guard(Guard2), 453 | {c_clause,L,Pats,Guard3,Exp}; 454 | _ -> {c_clause,L,Pats,Guard,Exp} 455 | end 456 | end, Clauses). 457 | 458 | %%-------------------------------------------------------------------- 459 | %% @doc Gets the evaluation options for a given System 460 | %% @end 461 | %%-------------------------------------------------------------------- 462 | eval_opts(System) -> 463 | SchedOpts = eval_sched_opts(System), 464 | ProcsOpts = eval_procs_opts(System), 465 | SchedOpts ++ ProcsOpts. 466 | 467 | eval_sched_opts(#sys{msgs = []}) -> 468 | []; 469 | eval_sched_opts(#sys{msgs = [CurMsg|RestMsgs], procs = Procs}) -> 470 | DestPid = CurMsg#msg.dest, 471 | DestProcs = [ P || P <- Procs, P#proc.pid == DestPid], 472 | case DestProcs of 473 | [] -> 474 | eval_sched_opts(#sys{msgs = RestMsgs, procs = Procs}); 475 | _Other -> 476 | Time = CurMsg#msg.time, 477 | [#opt{sem = ?MODULE, type = ?TYPE_MSG, id = Time, rule = ?RULE_SCHED}|eval_sched_opts(#sys{msgs = RestMsgs, procs = Procs})] 478 | end. 479 | 480 | eval_procs_opts(#sys{procs = []}) -> 481 | []; 482 | eval_procs_opts(#sys{procs = [CurProc|RestProcs]}) -> 483 | Exp = CurProc#proc.exp, 484 | Env = CurProc#proc.env, 485 | Pid = CurProc#proc.pid, 486 | Mail = CurProc#proc.mail, 487 | case eval_exp_opt(Exp, Env, Mail) of 488 | ?NOT_EXP -> 489 | eval_procs_opts(#sys{procs = RestProcs}); 490 | Opt -> 491 | [Opt#opt{sem = ?MODULE, type = ?TYPE_PROC, id = cerl:concrete(Pid)}|eval_procs_opts(#sys{procs = RestProcs})] 492 | end. 493 | 494 | eval_exp_opt(Exp, Env, Mail) -> 495 | case is_exp(Exp) of 496 | false -> 497 | ?NOT_EXP; 498 | true -> 499 | case cerl:type(Exp) of 500 | var -> 501 | #opt{rule = ?RULE_SEQ}; 502 | cons -> 503 | ConsHdExp = cerl:cons_hd(Exp), 504 | ConsTlExp = cerl:cons_tl(Exp), 505 | case is_exp(ConsHdExp) of 506 | true -> 507 | eval_exp_opt(ConsHdExp, Env, Mail); 508 | false -> 509 | case is_exp(ConsTlExp) of 510 | true -> 511 | eval_exp_opt(ConsTlExp, Env, Mail); 512 | false -> 513 | ?NOT_EXP 514 | end 515 | end; 516 | values -> 517 | eval_exp_list_opt(cerl:values_es(Exp), Env, Mail); 518 | tuple -> 519 | eval_exp_list_opt(cerl:tuple_es(Exp), Env, Mail); 520 | apply -> 521 | ApplyArgs = cerl:apply_args(Exp), 522 | case is_exp(ApplyArgs) of 523 | true -> 524 | eval_exp_list_opt(ApplyArgs, Env, Mail); 525 | false -> 526 | #opt{rule = ?RULE_SEQ} 527 | end; 528 | 'let' -> 529 | LetArg = cerl:let_arg(Exp), 530 | case is_exp(LetArg) of 531 | true -> 532 | eval_exp_opt(LetArg, Env, Mail); 533 | false -> 534 | #opt{rule = ?RULE_SEQ} 535 | end; 536 | seq -> 537 | SeqArg = cerl:seq_arg(Exp), 538 | case is_exp(SeqArg) of 539 | true -> 540 | eval_exp_opt(SeqArg, Env, Mail); 541 | false -> 542 | #opt{rule = ?RULE_SEQ} 543 | end; 544 | 'case' -> 545 | CaseArg = cerl:case_arg(Exp), 546 | case is_exp(CaseArg) of 547 | true -> 548 | eval_exp_opt(CaseArg, Env, Mail); 549 | false -> 550 | #opt{rule = ?RULE_SEQ} 551 | end; 552 | call -> 553 | CallModule = cerl:call_module(Exp), 554 | case is_exp(CallModule) of 555 | true -> 556 | eval_exp_opt(CallModule, Env, Mail); 557 | false -> 558 | CallName = cerl:call_name(Exp), 559 | case is_exp(CallName) of 560 | true -> 561 | eval_exp_opt(CallName, Env, Mail); 562 | false -> 563 | CallArgs = cerl:call_args(Exp), 564 | case is_exp(CallArgs) of 565 | true -> 566 | eval_exp_list_opt(CallArgs, Env, Mail); 567 | false -> 568 | case {CallModule, CallName} of 569 | {{c_literal, _, 'erlang'},{c_literal, _, 'spawn'}} -> #opt{rule = ?RULE_SPAWN}; 570 | {{c_literal, _, 'erlang'},{c_literal, _, 'self'}} -> #opt{rule = ?RULE_SELF}; 571 | {{c_literal, _, 'erlang'},{c_literal, _, '!'}} -> #opt{rule = ?RULE_SEND}; 572 | _ -> #opt{rule = ?RULE_SEQ} 573 | end 574 | end 575 | end 576 | end; 577 | 'receive' -> 578 | % SubsExp = utils:substitute(Exp, Env), 579 | % ?LOG("Exp: " ++ ?TO_STRING(Exp) ++ "\n" ++ 580 | % "SUB: " ++ ?TO_STRING(SubsExp)), 581 | % ReceiveClauses = cerl:receive_clauses(SubsExp), 582 | ReceiveClauses = cerl:receive_clauses(Exp), 583 | case matchrec(ReceiveClauses, Mail, Env) of 584 | no_match -> 585 | ?NOT_EXP; 586 | _Other -> 587 | #opt{rule = ?RULE_RECEIVE} 588 | end 589 | end 590 | end. 591 | 592 | eval_exp_list_opt([], _, _) -> 593 | ?NOT_EXP; 594 | eval_exp_list_opt([CurExp|RestExp], Env, Mail) -> 595 | case is_exp(CurExp) of 596 | true -> eval_exp_opt(CurExp, Env, Mail); 597 | false -> eval_exp_list_opt(RestExp, Env, Mail) 598 | end. 599 | 600 | ref_add(Id, Ref) -> 601 | ets:insert(?APP_REF, {Id, Ref}). 602 | 603 | ref_lookup(Id) -> 604 | ets:lookup_element(?APP_REF, Id, 2). 605 | -------------------------------------------------------------------------------- /src/utils.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @doc Utils functions for the reversible semantics for Erlang 3 | %%% @end 4 | %%%------------------------------------------------------------------- 5 | 6 | -module(utils). 7 | -export([fundef_lookup/2, fundef_rename/1, substitute/2, 8 | build_var/1, build_var/2, pid_exists/2, 9 | select_proc/2, select_msg/2, 10 | select_proc_with_time/2, select_proc_with_send/2, 11 | select_proc_with_spawn/2, select_proc_with_rec/2, 12 | select_proc_with_var/2, list_from_core/1, 13 | update_env/2, merge_env/2, 14 | replace/3, replace_all/2, pp_system/2, pp_trace/1, pp_roll_log/1, 15 | moduleNames/1, 16 | stringToFunName/1,stringToCoreArgs/1, toCore/1, toErlang/1, 17 | filter_options/2, filter_procs_opts/1, 18 | has_fwd/1, has_bwd/1, has_norm/1, has_var/2, 19 | is_queue_minus_msg/3, topmost_rec/1, last_msg_rest/1, 20 | gen_log_send/4, gen_log_spawn/2, empty_log/1, must_focus_log/1, 21 | extract_replay_data/1, extract_pid_log_data/2, get_mod_name/1]). 22 | 23 | -include("cauder.hrl"). 24 | -include_lib("wx/include/wx.hrl"). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% @doc Searches a function definition in FunDefs with name FunName 28 | %% @end 29 | %%-------------------------------------------------------------------- 30 | fundef_lookup(FunName, FunDefs) -> 31 | %io:fwrite("---------------~n"), 32 | %io:write(FunName), 33 | %io:fwrite("~n---------------~n"), 34 | %io:write(FunDefs), 35 | %io:fwrite("~n---------------~n"), 36 | case lists:keyfind(FunName, 1, FunDefs) of 37 | {_, FunDef} -> FunDef; 38 | false -> io:fwrite("Funzione non trovata", []) 39 | end. 40 | 41 | %%-------------------------------------------------------------------- 42 | %% @doc Renames all the variables in function definition FunDef 43 | %% @end 44 | %%-------------------------------------------------------------------- 45 | fundef_rename(FunDef) -> 46 | FunVars = cerl:fun_vars(FunDef), 47 | FunBody = cerl:fun_body(FunDef), 48 | RenamedVars = pars_rename(FunVars), 49 | {RenamedExp, _} = 50 | cerl_trees:mapfold(fun (Exp, Acc) -> 51 | case cerl:type(Exp) of 52 | var -> 53 | case cerl:var_name(Exp) of 54 | {_FunName, _FunArity} -> 55 | NewExp = Exp, 56 | NewAcc = Acc; 57 | OtherName -> 58 | case lists:keyfind(Exp, 1, Acc) of 59 | false -> 60 | NewExp = fresh_var(OtherName), 61 | NewAcc = [{Exp,NewExp}] ++ Acc; 62 | {Exp, NewVar} -> 63 | NewExp = NewVar, 64 | NewAcc = Acc 65 | end 66 | end; 67 | _Other -> 68 | NewExp = Exp, 69 | NewAcc = Acc 70 | end, 71 | {NewExp, NewAcc} 72 | end, 73 | RenamedVars, 74 | FunBody), 75 | NewFunDef = cerl:c_fun([NewVar || {_, NewVar} <- RenamedVars], RenamedExp), 76 | NewFunDef. 77 | 78 | pars_rename(Vars) -> 79 | [{Var, fresh_var(cerl:var_name(Var))} || Var <- Vars]. 80 | 81 | substitute(SuperExp, Env) -> 82 | cerl_trees:map( 83 | fun (Exp) -> 84 | case cerl:type(Exp) of 85 | var -> 86 | case proplists:get_value(Exp, Env) of 87 | undefined -> Exp; 88 | Value -> Value 89 | end; 90 | _ -> Exp 91 | end 92 | end, SuperExp). 93 | %%-------------------------------------------------------------------- 94 | %% @doc Builds a variable from a given number Num 95 | %% @end 96 | %%-------------------------------------------------------------------- 97 | build_var(Num) -> 98 | NumAtom = list_to_atom("k_" ++ integer_to_list(Num)), 99 | cerl:c_var(NumAtom). 100 | 101 | %%------------ It is not working in Erlang 22----------------------------- 102 | %%build_var(Name,Num) -> 103 | %% NumAtom = list_to_atom(atom_to_list(Name) ++ "_" ++ integer_to_list(Num)), 104 | %% cerl:c_var(NumAtom). 105 | %%------------------------------------------------------------------------ 106 | build_var(Name,Num) -> 107 | NewName = 108 | case Name of 109 | UserVarName when is_atom(UserVarName) -> 110 | atom_to_list(UserVarName); 111 | % Core variable names are just numbers in the last update 112 | CoreVarName -> 113 | "c" ++ integer_to_list(CoreVarName) 114 | end, 115 | NumAtom = list_to_atom(NewName ++ "_" ++ integer_to_list(Num)), 116 | cerl:c_var(NumAtom). 117 | 118 | pid_exists(Procs, Pid) -> 119 | case [ P || P <- Procs, P#proc.pid == Pid] of 120 | [] -> false; 121 | _ -> true 122 | end. 123 | 124 | %%-------------------------------------------------------------------- 125 | %% @doc Returns a tuple with a process with pid Pid from Procs and 126 | %% the rest of processes from Procs 127 | %% @end 128 | %%-------------------------------------------------------------------- 129 | select_proc(Procs, Pid) -> 130 | [Proc] = [ P || P <- Procs, P#proc.pid == Pid], 131 | RestProcs = [ P || P <- Procs, P#proc.pid /= Pid], 132 | {Proc, RestProcs}. 133 | 134 | %%-------------------------------------------------------------------- 135 | %% @doc Returns a tuple with a message with id Time from Msgs and 136 | %% the rest of messages from Msgs 137 | %% @end 138 | %%-------------------------------------------------------------------- 139 | select_msg(Msgs, Time) -> 140 | [Msg] = [ M || M <- Msgs, M#msg.time == Time], 141 | RestMsgs = [ M || M <- Msgs, M#msg.time /= Time], 142 | {Msg, RestMsgs}. 143 | 144 | %%-------------------------------------------------------------------- 145 | %% @doc Returns the process that contains a message with id Time 146 | %% from Procs 147 | %% @end 148 | %%-------------------------------------------------------------------- 149 | select_proc_with_time(Procs, Time) -> 150 | ProcWithTime = 151 | lists:filter( fun (Proc) -> 152 | Mail = Proc#proc.mail, 153 | length([ ok || {_,MsgTime} <- Mail, MsgTime == Time]) > 0 154 | end, Procs), 155 | hd(ProcWithTime). 156 | 157 | %%-------------------------------------------------------------------- 158 | %% @doc Returns the processes that contain a send item in history 159 | %% with time Time 160 | %% @end 161 | %%-------------------------------------------------------------------- 162 | select_proc_with_send(Procs, Time) -> 163 | ProcWithSend = 164 | lists:filter( fun (Proc) -> 165 | Hist = Proc#proc.hist, 166 | has_send(Hist, Time) 167 | end, Procs), 168 | ProcWithSend. 169 | 170 | %%-------------------------------------------------------------------- 171 | %% @doc Returns the processes that contain a spawn item in history 172 | %% with pid Pid 173 | %% @end 174 | %%-------------------------------------------------------------------- 175 | select_proc_with_spawn(Procs, Pid) -> 176 | ProcWithSpawn = 177 | lists:filter( fun (Proc) -> 178 | Hist = Proc#proc.hist, 179 | has_spawn(Hist, Pid) 180 | end, Procs), 181 | ProcWithSpawn. 182 | 183 | %%-------------------------------------------------------------------- 184 | %% @doc Returns the processes that contain a spawn item in history 185 | %% with pid Pid 186 | %% @end 187 | %%-------------------------------------------------------------------- 188 | select_proc_with_rec(Procs, Time) -> 189 | ProcWithRec = 190 | lists:filter( fun (Proc) -> 191 | Hist = Proc#proc.hist, 192 | has_rec(Hist, Time) 193 | end, Procs), 194 | ProcWithRec. 195 | 196 | %%-------------------------------------------------------------------- 197 | %% @doc Returns the processes that contain a binding for Var in 198 | %% its environment Env 199 | %% @end 200 | %%-------------------------------------------------------------------- 201 | select_proc_with_var(Procs, Var) -> 202 | ProcWithRec = 203 | lists:filter( fun (Proc) -> 204 | Env = Proc#proc.env, 205 | has_var(Env, Var) 206 | end, Procs), 207 | ProcWithRec. 208 | 209 | %%-------------------------------------------------------------------- 210 | %% @doc Transforms a Core Erlang list to a regular list 211 | %% @end 212 | %%-------------------------------------------------------------------- 213 | list_from_core(Exp) -> 214 | case cerl:is_c_nil(Exp) of 215 | true -> []; 216 | false -> [cerl:cons_hd(Exp)|list_from_core(cerl:cons_tl(Exp))] 217 | end. 218 | 219 | %%-------------------------------------------------------------------- 220 | %% @doc Update the environment Env with a single binding 221 | %% @end 222 | %%-------------------------------------------------------------------- 223 | update_env({Key, Value}, Env) -> 224 | DelEnv = proplists:delete(Key, Env), 225 | DelEnv ++ [{Key, Value}]. 226 | 227 | %%-------------------------------------------------------------------- 228 | %% @doc Update the environment Env with multiple bindings 229 | %% @end 230 | %%-------------------------------------------------------------------- 231 | merge_env(Env, []) -> Env; 232 | merge_env(Env, [CurBind|RestBind]) -> 233 | NewEnv = update_env(CurBind, Env), 234 | merge_env(NewEnv, RestBind). 235 | 236 | %%-------------------------------------------------------------------- 237 | %% @doc A typical substitution application 238 | %% @end 239 | %%-------------------------------------------------------------------- 240 | 241 | replace_all([],Exp) -> Exp; 242 | replace_all([{Var,Val}|R],Exp) -> 243 | %io:format("replace: ~p~n~p~n~p~n",[Var,Val,Exp]), 244 | NewExp = utils:replace(Var,Val,Exp), 245 | %io:format("--result: p~n",[NewExp]), 246 | replace_all(R,NewExp). 247 | 248 | 249 | %%-------------------------------------------------------------------- 250 | %% @doc Replaces a variable Var by SubExp (subexpression) in SuperExp 251 | %% (expression) 252 | %% @end 253 | %%-------------------------------------------------------------------- 254 | %replace(Var, SubExp, SuperExp) -> 255 | % VarName = cerl:var_name(Var), 256 | % case cerl:type(SuperExp) of 257 | % var -> case cerl:var_name(SuperExp) of 258 | % VarName -> SubExp; 259 | % _Other -> SuperExp 260 | % end; 261 | % call -> NewArgs = lists:map(fun (E) -> replace(Var,SubExp,E) end, cerl:call_args(SuperExp)), 262 | % CallModule = cerl:call_module(SuperExp), 263 | % CallName = cerl:call_name(SuperExp), 264 | % cerl:c_call(CallModule,CallName,NewArgs); 265 | % %_Other -> SuperExp 266 | % _Other -> cerl_trees:map( 267 | % fun (Exp) -> 268 | % case cerl:type(Exp) of 269 | % var -> 270 | % case cerl:var_name(Exp) of 271 | % VarName -> SubExp; 272 | % _Other -> Exp 273 | % end; 274 | % _Other -> Exp 275 | % end 276 | % end, SuperExp) 277 | % end. 278 | 279 | %%-------------------------------------------------------------------- 280 | %% @doc Replaces a variable Var by SubExp (subexpression) in SuperExp 281 | %% (expression) 282 | %% @end 283 | %%-------------------------------------------------------------------- 284 | replace(Var, SubExp, SuperExp) -> 285 | VarName = cerl:var_name(Var), 286 | cerl_trees:map( 287 | fun (Exp) -> 288 | case cerl:type(Exp) of 289 | var -> 290 | case cerl:var_name(Exp) of 291 | VarName -> SubExp; 292 | _Other -> Exp 293 | end; 294 | _Other -> Exp 295 | end 296 | end, SuperExp). 297 | 298 | %%-------------------------------------------------------------------- 299 | %% @doc Pretty-prints a given System 300 | %% @end 301 | %%-------------------------------------------------------------------- 302 | pp_system(#sys{msgs = Msgs, procs = Procs}, Opts) -> 303 | pp_msgs(Msgs) ++ "\n" ++ pp_procs(Procs, Opts). 304 | 305 | pp_msgs(Msgs) -> 306 | MsgsList = [pp_msg(Msg) || Msg <- Msgs], 307 | "GM: [" ++ string:join(MsgsList,",") ++ "]\n". 308 | 309 | pp_msg(#msg{dest = DestPid, val = MsgValue, time = Time}) -> 310 | "(" ++ pp(DestPid) ++ ",{" ++ pp(MsgValue) ++ "," ++ [{?wxRED, integer_to_list(Time)}] ++ "})". 311 | 312 | pp_procs(Procs, Opts) -> 313 | SortProcs = lists:sort(fun(P1, P2) -> P1#proc.pid < P2#proc.pid end, Procs), 314 | ProcsList = [pp_proc(Proc, Opts) || Proc <- SortProcs], 315 | string:join(ProcsList,"\n"). 316 | 317 | pp_proc(#proc{pid = Pid, hist = Hist, env = Env, exp = Exp, mail = Mail, spf = Fun}, Opts) -> 318 | pp_pre(Pid, Fun) ++ 319 | pp_mail(Mail, Opts) ++ 320 | pp_hist(Hist, Opts) ++ 321 | pp_env(Env, Exp, Opts)++ 322 | pp(Exp, Opts). 323 | 324 | pp_pre(Pid, Fun) -> 325 | "=============== " ++ pp_pid(Pid) ++ ": " ++ pp_fun(Fun)++ " ===============\n". 326 | 327 | pp_pid(Pid) -> 328 | "Proc. " ++ pp(Pid). 329 | 330 | pp_fun(undef) -> 331 | ""; 332 | pp_fun({Name, Arity}) -> 333 | atom_to_list(Name) ++ "/" ++ integer_to_list(Arity). 334 | 335 | 336 | pp_env(Env, Exp, Opts) -> 337 | case proplists:get_value(?PRINT_ENV, Opts) of 338 | false -> ""; 339 | true -> "ENV: " ++ pp_env_1(Env, Exp, Opts) ++ "\n" 340 | end. 341 | 342 | pp_env_1(Env, Exp, Opts) -> 343 | PrintEnv = 344 | case proplists:get_value(?PRINT_FULL_ENV, Opts) of 345 | true -> Env; 346 | false -> rel_binds(Env, Exp) 347 | end, 348 | PairsList = [pp_pair(Var,Val) || {Var,Val} <- PrintEnv], 349 | "{" ++ string:join(PairsList,", ") ++ "}". 350 | 351 | pp_pair(Var,Val) -> 352 | pp(Var) ++ " -> " ++ pp(Val). 353 | 354 | is_conc_item({spawn,_,_,_}) -> true; 355 | is_conc_item({send,_,_,_,_}) -> true; 356 | is_conc_item({rec,_,_,_,_}) -> true; 357 | is_conc_item(_) -> false. 358 | 359 | pp_hist(Hist, Opts) -> 360 | case proplists:get_value(?PRINT_HIST, Opts) of 361 | false -> ""; 362 | true -> pp_hist_1(Hist, Opts) ++ "\n" 363 | end. 364 | 365 | pp_hist_1(Hist, Opts) -> 366 | FiltHist = 367 | case proplists:get_value(?PRINT_FULL, Opts) of 368 | false -> lists:filter(fun is_conc_item/1, Hist); 369 | true -> Hist 370 | end, 371 | StrItems = [pp_hist_2(Item) || Item <- FiltHist], 372 | "H : [" ++ string:join(StrItems, ",") ++ "]". 373 | 374 | pp_hist_2({tau,_,_}) -> 375 | "seq"; 376 | pp_hist_2({self,_,_}) -> 377 | "self"; 378 | pp_hist_2({spawn,_,_,Pid}) -> 379 | "spawn(" ++ [{?CAUDER_GREEN, pp(Pid)}] ++ ")"; 380 | pp_hist_2({send,_,_,_,{Value,Time}}) -> 381 | "send(" ++ pp(Value) ++ "," ++ [{?wxRED, integer_to_list(Time)}] ++ ")"; 382 | pp_hist_2({rec,_,_,{Value,Time},_}) -> 383 | "rec(" ++ pp(Value) ++ "," ++ [{?wxBLUE, integer_to_list(Time)}] ++ ")". 384 | 385 | pp_mail(Mail, Opts) -> 386 | case proplists:get_value(?PRINT_MAIL, Opts) of 387 | false -> ""; 388 | true -> "LM: " ++ pp_mail_1(Mail) ++ "\n" 389 | end. 390 | 391 | pp_mail_1([]) -> "[]"; 392 | pp_mail_1(Mail) -> 393 | MailList = [pp_msg_mail(Val, Time) || {Val, Time} <- Mail], 394 | "[" ++ string:join(MailList,",") ++ "]". 395 | 396 | pp_msg_mail(Val, Time) -> 397 | "{" ++ pp(Val) ++ "," ++ [{?CAUDER_GREEN, integer_to_list(Time)}] ++ "}". 398 | 399 | 400 | pp(CoreForm, Opts) -> 401 | case proplists:get_value(?PRINT_EXP, Opts) of 402 | false -> ""; 403 | true -> "EXP: " ++ pp(CoreForm) ++ "\n" 404 | end. 405 | 406 | pp(CoreForm) -> lists:flatten(core_pp:format(CoreForm)). 407 | 408 | %%-------------------------------------------------------------------- 409 | %% @doc Pretty-prints a given system trace 410 | %% @end 411 | %%-------------------------------------------------------------------- 412 | pp_trace(#sys{trace = Trace}) -> 413 | % Trace is built as a stack (newest item is first) 414 | % and we must reverse it to print it 415 | RevTrace = lists:reverse(Trace), 416 | %%%TEST POINT OF LUCA'S TOOL INTEGRATION 417 | case whereis(tracer) of 418 | undefined->ok; 419 | TracerPid-> 420 | TracerPid !{show,RevTrace} 421 | end, 422 | %%% 423 | TraceStr = [pp_trace_item(Item) || Item <- RevTrace], 424 | string:join(TraceStr,"\n"). 425 | 426 | pp_trace_item(#trace{type = Type, 427 | from = From, 428 | to = To, 429 | val = Val, 430 | time = Time}) -> 431 | case Type of 432 | ?RULE_SEND -> pp_trace_send(From, To, Val, Time); 433 | ?RULE_SPAWN -> pp_trace_spawn(From, To); 434 | ?RULE_RECEIVE -> pp_trace_receive(From, Val, Time) 435 | end. 436 | 437 | pp_trace_send(From, To, Val, Time) -> 438 | [pp_pid(From)," sends ",pp(Val)," to ",pp_pid(To)," (",integer_to_list(Time),")"]. 439 | 440 | pp_trace_spawn(From, To) -> 441 | [pp_pid(From)," spawns ",pp_pid(To)]. 442 | 443 | pp_trace_receive(From, Val, Time) -> 444 | [pp_pid(From)," receives ",pp(Val)," (",integer_to_list(Time),")"]. 445 | 446 | %%-------------------------------------------------------------------- 447 | %% @doc Prints a given system roll log 448 | %% @end 449 | %%-------------------------------------------------------------------- 450 | pp_roll_log(#sys{roll = RollLog}) -> 451 | string:join(RollLog,"\n"). 452 | 453 | %%-------------------------------------------------------------------- 454 | %% @doc Returns the module names from Forms 455 | %% @end 456 | %%-------------------------------------------------------------------- 457 | moduleNames(Forms) -> 458 | FunDefs = cerl:module_defs(Forms), 459 | FunNames = [cerl:var_name(Var) || {Var,_Fun} <- FunDefs], 460 | FunNameStrings = [funNameToString({Name,Arity}) || {Name,Arity} <- FunNames, Name =/= 'module_info'], 461 | FunNameStrings. 462 | 463 | funNameToString({Name,Arity}) -> 464 | atom_to_list(Name) ++ "/" ++ integer_to_list(Arity). 465 | 466 | %%-------------------------------------------------------------------- 467 | %% @doc Converts a string String into a Core Erlang function name 468 | %% @end 469 | %%-------------------------------------------------------------------- 470 | stringToFunName(String) -> 471 | FunParts = string:tokens(String, "/"), 472 | Name = list_to_atom(lists:nth(1,FunParts)), 473 | Arity = list_to_integer(lists:nth(2,FunParts)), 474 | cerl:c_var({Name,Arity}). 475 | 476 | %%-------------------------------------------------------------------- 477 | %% @doc Parses a string Str that represents a list of arguments 478 | %% and transforms these arguments to their equivalent in Core Erlang 479 | %% @end 480 | %%-------------------------------------------------------------------- 481 | stringToCoreArgs([]) -> 482 | []; 483 | stringToCoreArgs(Str) -> 484 | StrDot = Str ++ ".", 485 | {ok, ParsedStr, _} = erl_scan:string(StrDot), 486 | {ok, Exprs} = erl_parse:parse_exprs(ParsedStr), 487 | CoreExprs = [toCore(Expr) || Expr <- Exprs], 488 | CoreExprs. 489 | 490 | %%-------------------------------------------------------------------- 491 | %% @doc Transforms an Erlang expression Expr to its equivalent in 492 | %% Core Erlang 493 | %% @end 494 | %%-------------------------------------------------------------------- 495 | toCore(Expr) -> 496 | case Expr of 497 | {atom, _, Atom} -> 498 | cerl:c_atom(Atom); 499 | {integer, _, Int} -> 500 | cerl:c_int(Int); 501 | {op, _, '-',{integer, _, Int}} -> 502 | cerl:c_int(-Int); 503 | {float, _, Float} -> 504 | cerl:c_float(Float); 505 | {string, _, String} -> 506 | cerl:c_string(String); 507 | {tuple, _, TupleEs} -> 508 | cerl:c_tuple_skel([toCore(E) || E <- TupleEs]); 509 | {cons, _, Head, Tail} -> 510 | cerl:c_cons_skel(toCore(Head), toCore(Tail)); 511 | {nil, _} -> 512 | cerl:c_nil() 513 | end. 514 | 515 | toErlang(Expr) -> 516 | LitExpr = 517 | case cerl:is_literal(Expr) of 518 | true -> Expr; 519 | false -> cerl:fold_literal(Expr) 520 | end, 521 | cerl:concrete(LitExpr). 522 | 523 | %%-------------------------------------------------------------------- 524 | %% @doc Filters the options with identifier Id 525 | %% @end 526 | %%-------------------------------------------------------------------- 527 | filter_options([], _) -> []; 528 | filter_options([CurOpt|RestOpts], Id) -> 529 | #opt{id = OptId} = CurOpt, 530 | case (OptId == Id) of 531 | true -> [CurOpt|filter_options(RestOpts,Id)]; 532 | false -> filter_options(RestOpts,Id) 533 | end. 534 | 535 | %%-------------------------------------------------------------------- 536 | %% @doc Filters the process options from a list of Options 537 | %% @end 538 | %%-------------------------------------------------------------------- 539 | filter_procs_opts([]) -> []; 540 | filter_procs_opts([CurOpt|RestOpts]) -> 541 | #opt{type = Type} = CurOpt, 542 | case Type of 543 | ?TYPE_MSG -> filter_procs_opts(RestOpts); 544 | ?TYPE_PROC -> [CurOpt|filter_procs_opts(RestOpts)] 545 | end. 546 | 547 | %%-------------------------------------------------------------------- 548 | %% @doc Returns true if a list of Options has a forward option, 549 | %% and false otherwise 550 | %% @end 551 | %%-------------------------------------------------------------------- 552 | has_fwd([]) -> false; 553 | has_fwd([#opt{sem = ?FWD_SEM}|_RestOpts]) -> true; 554 | has_fwd([_CurOpt|RestOpts]) -> has_fwd(RestOpts). 555 | 556 | %%-------------------------------------------------------------------- 557 | %% @doc Returns true if a list of Options has a backward option, 558 | %% and false otherwise 559 | %% @end 560 | %%-------------------------------------------------------------------- 561 | has_bwd([]) -> false; 562 | has_bwd([#opt{sem = ?BWD_SEM}|_RestOpts]) -> true; 563 | has_bwd([_CurOpt|RestOpts]) -> has_bwd(RestOpts). 564 | 565 | %%-------------------------------------------------------------------- 566 | %% @doc Returns true if a list of Options has a normalizing option, 567 | %% and false otherwise 568 | %% @end 569 | %%-------------------------------------------------------------------- 570 | has_norm([]) -> false; 571 | has_norm([#opt{sem = ?FWD_SEM, rule = Rule}|RestOpts]) -> 572 | case Rule of 573 | ?RULE_SCHED -> has_norm(RestOpts); 574 | _OtherRule -> true 575 | end; 576 | has_norm([_CurOpt|RestOpts]) -> has_norm(RestOpts). 577 | 578 | %%-------------------------------------------------------------------- 579 | %% @doc Returns true if Queue\Msg == OtherQueue, and false otherwise 580 | %% @end 581 | %%-------------------------------------------------------------------- 582 | is_queue_minus_msg(Queue, Msg, OtherQueue) -> 583 | ThisQueue = lists:delete(Msg, Queue), 584 | ThisQueue == OtherQueue. 585 | 586 | %%-------------------------------------------------------------------- 587 | %% @doc Retrieves the topmost item in a history 588 | %% @end 589 | %%-------------------------------------------------------------------- 590 | topmost_rec([]) -> no_rec; 591 | topmost_rec([CurHist|RestHist]) -> 592 | case CurHist of 593 | {rec,_,_,_,_} -> CurHist; 594 | _Other -> topmost_rec(RestHist) 595 | end. 596 | 597 | has_send([], _) -> false; 598 | has_send([{send,_,_,_,{_,Time}}|_], Time) -> true; 599 | has_send([_|RestHist], Time) -> has_send(RestHist, Time). 600 | 601 | has_spawn([], _) -> false; 602 | has_spawn([{spawn,_,_,Pid}|_], Pid) -> true; 603 | has_spawn([_|RestHist], Pid) -> has_spawn(RestHist, Pid). 604 | 605 | has_rec([], _) -> false; 606 | has_rec([{rec,_,_, {_, Time},_}|_], Time) -> true; 607 | has_rec([_|RestHist], Time) -> has_rec(RestHist, Time). 608 | 609 | has_var(Env, Var) -> 610 | case proplists:get_value(Var, Env) of 611 | undefined -> false; 612 | _ -> true 613 | end. 614 | 615 | fresh_var(Name) -> 616 | VarNum = ref_lookup(?FRESH_VAR), 617 | ref_add(?FRESH_VAR, VarNum + 1), 618 | utils:build_var(Name,VarNum). 619 | 620 | last_msg_rest(Mail) -> 621 | LastMsg = lists:last(Mail), 622 | LenMail = length(Mail), 623 | RestMail = lists:sublist(Mail,LenMail-1), 624 | {LastMsg, RestMail}. 625 | 626 | rel_binds(Env, Exp) -> 627 | RelVars = cerl_trees:variables(Exp), 628 | lists:filter( fun ({Var,_}) -> 629 | VarName = cerl:var_name(Var), 630 | lists:member(VarName,RelVars) 631 | end, Env). 632 | 633 | gen_log_send(Pid, OtherPid, MsgValue, Time) -> 634 | [["Roll send from ",pp_pid(Pid), " of ",pp(MsgValue), " to ",pp_pid(OtherPid), " (",integer_to_list(Time),")"]]. 635 | 636 | gen_log_spawn(_Pid, OtherPid) -> 637 | % [["Roll SPAWN of ",pp_pid(OtherPid)," from ",pp_pid(Pid)]]. 638 | [["Roll spawn of ",pp_pid(OtherPid)]]. 639 | 640 | empty_log(System) -> 641 | System#sys{roll = []}. 642 | 643 | must_focus_log(System) -> 644 | Trace = System#sys.roll, 645 | case Trace of 646 | [] -> false; 647 | _ -> true 648 | end. 649 | 650 | parse_replay_info(Line) -> 651 | Words = string:split(Line, " "), 652 | case hd(Words) of 653 | "call" -> 654 | {call, lists:nth(2, Words)}; 655 | "main_pid" -> 656 | {pid, lists:nth(2, Words)}; 657 | _ -> 658 | none 659 | end. 660 | 661 | add_replay_info({pid, Pid}, Data) -> 662 | Data#replay{main_pid = Pid}; 663 | add_replay_info({call, Call}, Data) -> 664 | NCall = lists:flatten(string:replace(Call, "\n", "")), 665 | ECall = lists:flatten(string:replace(NCall, "\"", "", all)), 666 | Data#replay{call = ECall}; 667 | add_replay_info(_, Data) -> 668 | Data. 669 | 670 | read_replay_data(File, Data) -> 671 | case file:read_line(File) of 672 | eof -> 673 | Data; 674 | {ok, Line} -> 675 | ReplayInfo = parse_replay_info(Line), 676 | NData = add_replay_info(ReplayInfo, Data), 677 | read_replay_data(File, NData) 678 | end. 679 | 680 | extract_replay_data(Path) -> 681 | ReplayData = #replay{log_path = Path}, 682 | ResPath = Path ++ "/trace_result.log", 683 | {ok, FileHandler} = file:open(ResPath, [read]), 684 | NReplayData = read_replay_data(FileHandler, ReplayData), 685 | put(replay_data, NReplayData), 686 | % io:format("~p~n", [NReplayData]), 687 | file:close(FileHandler). 688 | 689 | parse_proc_data(Line) -> 690 | Line. 691 | 692 | read_replay_proc_data(File, Data) -> 693 | case file:read_line(File) of 694 | eof -> 695 | lists:reverse(Data); 696 | {ok, Line} -> 697 | ProcData = parse_proc_data(Line), 698 | NData = [ProcData | Data], 699 | read_replay_proc_data(File, NData) 700 | end. 701 | 702 | extract_pid_log_data(Path, Pid) -> 703 | PidPath = Path ++ "/trace_" ++ Pid ++ ".log", 704 | {ok, FileHandler} = file:open(PidPath, [read]), 705 | ReplayProcData = read_replay_proc_data(FileHandler, []), 706 | file:close(FileHandler), 707 | ReplayProcData. 708 | 709 | get_mod_name(Call) -> 710 | AExpr = 711 | case is_list(Call) of 712 | true -> 713 | hd(parse_expr(Call++".")); 714 | false -> 715 | Call 716 | end, 717 | {call,_,{remote,_,{atom,_,ModName},{atom,_,FunName}},Args} = AExpr, 718 | {ModName,FunName,Args}. 719 | 720 | parse_expr(Func) -> 721 | case erl_scan:string(Func) of 722 | {ok, Toks, _} -> 723 | case erl_parse:parse_exprs(Toks) of 724 | {ok, _Term} -> 725 | _Term; 726 | _Err -> 727 | {error, parse_error} 728 | end; 729 | _Err -> 730 | {error, parse_error} 731 | end. 732 | 733 | ref_add(Id, Ref) -> 734 | ets:insert(?APP_REF, {Id, Ref}). 735 | 736 | ref_lookup(Id) -> 737 | ets:lookup_element(?APP_REF, Id, 2). 738 | -------------------------------------------------------------------------------- /src/cauder_gui.erl: -------------------------------------------------------------------------------- 1 | -module(cauder_gui). 2 | -export([setup_gui/0]). 3 | 4 | -include("cauder.hrl"). 5 | -include("cauder_gui.hrl"). 6 | -include_lib("wx/include/wx.hrl"). 7 | 8 | setup_gui() -> 9 | Server = wx:new(), 10 | Frame = wxFrame:new(Server, -1, ?APP_STRING, [{size, ?FRAME_SIZE_INIT}]), 11 | ref_start(), 12 | ref_add(?FILE_PATH, "."), 13 | ref_add(?STATUS, #status{}), 14 | ref_add(?FRAME, Frame), 15 | setupMenu(), 16 | wxFrame:createStatusBar(Frame, [{id, ?STATUS_BAR}]), 17 | wxEvtHandler:connect(Frame, close_window), 18 | wxEvtHandler:connect(Frame, command_button_clicked), 19 | wxEvtHandler:connect(Frame, command_menu_selected), 20 | wxEvtHandler:connect(Frame, command_text_updated), 21 | setupMainPanel(Frame), 22 | wxFrame:show(Frame), 23 | loop(), 24 | utils_gui:stop_refs(), 25 | ref_stop(). 26 | 27 | setupMainPanel(Parent) -> 28 | MainPanel = wxPanel:new(Parent), 29 | MainSizer = wxBoxSizer:new(?wxHORIZONTAL), 30 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 31 | 32 | LeftPanel = wxPanel:new(MainPanel), 33 | LeftSizer = setupLeftSizer(LeftPanel), 34 | wxWindow:setSizerAndFit(LeftPanel, LeftSizer), 35 | 36 | RightPanel = wxPanel:new(MainPanel), 37 | RightSizer = setupRightSizer(RightPanel), 38 | wxWindow:setSizerAndFit(RightPanel, RightSizer), 39 | 40 | wxSizer:add(MainSizer, LeftPanel, SizerFlags), 41 | wxSizer:add(MainSizer, RightPanel, SizerFlags), 42 | wxWindow:setSizer(MainPanel, MainSizer), 43 | MainPanel. 44 | 45 | setupLeftSizer(Parent) -> 46 | Notebook = wxNotebook:new(Parent, ?LEFT_NOTEBOOK), 47 | ref_add(?LEFT_NOTEBOOK, Notebook), 48 | CodePanel = setupCodePanel(Notebook), 49 | StatePanel = setupStatePanel(Notebook), 50 | wxNotebook:addPage(Notebook, CodePanel, "Code"), 51 | wxNotebook:addPage(Notebook, StatePanel, "State"), 52 | % wxNotebook:layout(Notebook), 53 | LeftSizer = wxBoxSizer:new(?wxVERTICAL), 54 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 55 | wxSizer:add(LeftSizer, Notebook, SizerFlags), 56 | LeftSizer. 57 | 58 | setupCodePanel(Parent) -> 59 | CodePanel = wxPanel:new(Parent), 60 | CodeText = wxTextCtrl:new(CodePanel, ?CODE_TEXT, 61 | [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), 62 | ref_add(?CODE_TEXT,CodeText), 63 | 64 | 65 | FundefStaticText = wxStaticText:new(CodePanel, ?wxID_ANY, "Funs: "), 66 | FunChoice = wxChoice:new(CodePanel, ?wxID_ANY), 67 | ref_add(?FUN_CHOICE,FunChoice), 68 | InputStaticText = wxStaticText:new(CodePanel, ?wxID_ANY, "Input args: "), 69 | InputTextCtrl = wxTextCtrl:new(CodePanel, ?INPUT_TEXT, 70 | [{style, ?wxBOTTOM}, 71 | {value, ""}]), 72 | ref_add(?INPUT_TEXT,InputTextCtrl), 73 | StartButton = wxButton:new(CodePanel, ?START_BUTTON, 74 | [{label, "START"}]), 75 | ref_add(?START_BUTTON,StartButton), 76 | wxButton:disable(StartButton), 77 | 78 | CodeSizer = wxBoxSizer:new(?wxVERTICAL), 79 | InputSizer = wxBoxSizer:new(?wxHORIZONTAL), 80 | ref_add(?INPUT_SIZER, InputSizer), 81 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 82 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 83 | 84 | wxSizer:add(CodeSizer, CodeText, SizerFlags), 85 | wxSizer:addSpacer(CodeSizer, 10), 86 | wxSizer:add(CodeSizer, InputSizer, [{proportion, 0}, {flag, ?wxEXPAND}]), 87 | 88 | wxSizer:add(InputSizer, FundefStaticText), 89 | wxSizer:add(InputSizer, FunChoice), 90 | wxSizer:addSpacer(InputSizer, 10), 91 | wxSizer:add(InputSizer, InputStaticText), 92 | wxSizer:add(InputSizer, InputTextCtrl, SizerFlags), 93 | wxSizer:addSpacer(InputSizer, 10), 94 | wxSizer:add(InputSizer, StartButton, [{flag, ?wxALIGN_RIGHT}]), 95 | 96 | wxSizer:add(BorderSizer, CodeSizer, [{flag, ?wxALL bor ?wxEXPAND}, 97 | {proportion, 1}, {border, 10}]), 98 | wxWindow:setSizer(CodePanel, BorderSizer), 99 | CodePanel. 100 | 101 | setupStatePanel(Parent) -> 102 | StatePanel = wxPanel:new(Parent), 103 | StateText = wxTextCtrl:new(StatePanel, ?STATE_TEXT, 104 | [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), 105 | ref_add(?STATE_TEXT, StateText), 106 | StateSizer = wxBoxSizer:new(?wxVERTICAL), 107 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 108 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 109 | wxSizer:add(StateSizer, StateText, SizerFlags), 110 | wxSizer:add(BorderSizer, StateSizer, [{flag, ?wxALL bor ?wxEXPAND}, 111 | {proportion, 1}, {border, 10}]), 112 | wxWindow:setSizer(StatePanel, BorderSizer), 113 | StatePanel. 114 | 115 | setupTracePanel(Parent) -> 116 | TracePanel = wxPanel:new(Parent), 117 | TraceText = wxTextCtrl:new(TracePanel, ?TRACE_TEXT, 118 | [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), 119 | ref_add(?TRACE_TEXT, TraceText), 120 | TraceSizer = wxBoxSizer:new(?wxVERTICAL), 121 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 122 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 123 | wxSizer:add(TraceSizer, TraceText, SizerFlags), 124 | wxSizer:add(BorderSizer, TraceSizer, [{flag, ?wxALL bor ?wxEXPAND}, 125 | {proportion, 1}, {border, 10}]), 126 | wxWindow:setSizer(TracePanel, BorderSizer), 127 | TracePanel. 128 | 129 | setupRollLogPanel(Parent) -> 130 | RollLogPanel = wxPanel:new(Parent), 131 | RollLogText = wxTextCtrl:new(RollLogPanel, ?ROLL_LOG_TEXT, 132 | [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]), 133 | ref_add(?ROLL_LOG_TEXT, RollLogText), 134 | RollLogSizer = wxBoxSizer:new(?wxVERTICAL), 135 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 136 | SizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 137 | wxSizer:add(RollLogSizer, RollLogText, SizerFlags), 138 | wxSizer:add(BorderSizer, RollLogSizer, [{flag, ?wxALL bor ?wxEXPAND}, 139 | {proportion, 1}, {border, 10}]), 140 | wxWindow:setSizer(RollLogPanel, BorderSizer), 141 | RollLogPanel. 142 | 143 | setupRightSizer(Parent) -> 144 | Notebook = wxNotebook:new(Parent, ?RIGHT_NOTEBOOK), 145 | BottomNotebook = wxNotebook:new(Parent, ?RBOT_NOTEBOOK), 146 | ref_add(?RIGHT_NOTEBOOK, Notebook), 147 | ref_add(?RBOT_NOTEBOOK, BottomNotebook), 148 | ManuPanel = setupManualPanel(Notebook), 149 | AutoPanel = setupAutoPanel(Notebook), 150 | RollPanel = setupRollPanel(Notebook), 151 | wxNotebook:addPage(Notebook, ManuPanel, "Manual"), 152 | wxNotebook:addPage(Notebook, AutoPanel, "Automatic"), 153 | wxNotebook:addPage(Notebook, RollPanel, "Rollback"), 154 | % wxNotebook:layout(Notebook), 155 | TracePanel = setupTracePanel(BottomNotebook), 156 | RollLogPanel = setupRollLogPanel(BottomNotebook), 157 | wxNotebook:addPage(BottomNotebook, TracePanel, "Trace"), 158 | wxNotebook:addPage(BottomNotebook, RollLogPanel, "Roll Log"), 159 | RightSizer = wxBoxSizer:new(?wxVERTICAL), 160 | SizerFlags = [{proportion, 0}, {flag, ?wxEXPAND}], 161 | BottomSizerFlags = [{proportion, 1}, {flag, ?wxEXPAND}], 162 | wxSizer:add(RightSizer, Notebook, SizerFlags), 163 | wxSizer:add(RightSizer, BottomNotebook, BottomSizerFlags), 164 | RightSizer. 165 | 166 | setupManualPanel(Parent) -> 167 | ManuPanel = wxPanel:new(Parent), 168 | PidStaticText = wxStaticText:new(ManuPanel, ?wxID_ANY, "Pid/MsgId:"), 169 | PidTextCtrl = wxTextCtrl:new(ManuPanel, ?PID_TEXT, [{style, ?wxBOTTOM}]), 170 | ref_add(?PID_TEXT, PidTextCtrl), 171 | 172 | ForwIntButton = wxButton:new(ManuPanel, ?FORW_INT_BUTTON, 173 | [{label, "Seq"}]), 174 | ForwSchButton = wxButton:new(ManuPanel, ?FORW_SCH_BUTTON, 175 | [{label, "Sched"}]), 176 | BackIntButton = wxButton:new(ManuPanel, ?BACK_INT_BUTTON, 177 | [{label, "Seq"}]), 178 | BackSchButton = wxButton:new(ManuPanel, ?BACK_SCH_BUTTON, 179 | [{label, "Sched"}]), 180 | wxButton:disable(ForwIntButton), 181 | wxButton:disable(ForwSchButton), 182 | wxButton:disable(BackIntButton), 183 | wxButton:disable(BackSchButton), 184 | ref_add(?FORW_INT_BUTTON, ForwIntButton), 185 | ref_add(?FORW_SCH_BUTTON, ForwSchButton), 186 | ref_add(?BACK_INT_BUTTON, BackIntButton), 187 | ref_add(?BACK_SCH_BUTTON, BackSchButton), 188 | 189 | ManuSizer = wxBoxSizer:new(?wxVERTICAL), 190 | ProcSizer = wxBoxSizer:new(?wxHORIZONTAL), 191 | ForwardSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, ManuPanel, 192 | [{label, "Forward rules"}]), 193 | BackwardSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, ManuPanel, 194 | [{label, "Backward rules"}]), 195 | ButtonSizer = wxBoxSizer:new(?wxVERTICAL), 196 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 197 | 198 | wxSizer:add(ManuSizer, ProcSizer), 199 | wxSizer:addSpacer(ManuSizer, 10), 200 | wxSizer:add(ManuSizer, ButtonSizer), 201 | 202 | wxSizer:add(ProcSizer, PidStaticText, [{flag, ?wxCENTRE}]), 203 | wxSizer:add(ProcSizer, PidTextCtrl, [{flag, ?wxCENTRE}]), 204 | 205 | wxSizer:add(ForwardSizer, ForwIntButton), 206 | wxSizer:addSpacer(ForwardSizer, 5), 207 | wxSizer:add(ForwardSizer, ForwSchButton), 208 | wxSizer:add(BackwardSizer, BackIntButton), 209 | wxSizer:addSpacer(BackwardSizer, 5), 210 | wxSizer:add(BackwardSizer, BackSchButton), 211 | 212 | wxSizer:add(ButtonSizer, ForwardSizer, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), 213 | wxSizer:addSpacer(ButtonSizer, 5), 214 | wxSizer:add(ButtonSizer, BackwardSizer, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), 215 | 216 | wxSizer:add(BorderSizer, ManuSizer, [{flag, ?wxALL bor ?wxALIGN_CENTER_HORIZONTAL}, {border, 10}]), 217 | wxWindow:setSizer(ManuPanel, BorderSizer), 218 | ManuPanel. 219 | 220 | setupAutoPanel(Parent) -> 221 | AutoPanel = wxPanel:new(Parent), 222 | StepStaticText = wxStaticText:new(AutoPanel, ?wxID_ANY, "Steps:"), 223 | StepTextCtrl = wxTextCtrl:new(AutoPanel, ?STEP_TEXT, [{style,?wxBOTTOM}]), 224 | ref_add(?STEP_TEXT, StepTextCtrl), 225 | HorizontalLine = wxStaticLine:new(AutoPanel, [{style, ?wxLI_HORIZONTAL}, 226 | {size, {200, -1}}]), 227 | ForwardButton = wxButton:new(AutoPanel, ?FORWARD_BUTTON, 228 | [{label, "Forward"}]), 229 | BackwardButton = wxButton:new(AutoPanel, ?BACKWARD_BUTTON, 230 | [{label, "Backward"}]), 231 | NormalizeButton = wxButton:new(AutoPanel, ?NORMALIZE_BUTTON, 232 | [{label, "Normalize"}]), 233 | 234 | wxButton:disable(ForwardButton), 235 | wxButton:disable(BackwardButton), 236 | wxButton:disable(NormalizeButton), 237 | ref_add(?FORWARD_BUTTON, ForwardButton), 238 | ref_add(?BACKWARD_BUTTON, BackwardButton), 239 | ref_add(?NORMALIZE_BUTTON, NormalizeButton), 240 | 241 | AutoSizer = wxBoxSizer:new(?wxVERTICAL), 242 | StepSizer = wxBoxSizer:new(?wxHORIZONTAL), 243 | StepButtonSizer = wxBoxSizer:new(?wxHORIZONTAL), 244 | SchedButtonSizer = wxBoxSizer:new(?wxHORIZONTAL), 245 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 246 | 247 | wxSizer:add(AutoSizer, StepSizer, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), 248 | wxSizer:addSpacer(AutoSizer, 15), 249 | wxSizer:add(AutoSizer, StepButtonSizer, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), 250 | wxSizer:add(AutoSizer, HorizontalLine, [{flag, ?wxTOP bor ?wxBOTTOM}, 251 | {border, 10}]), 252 | wxSizer:add(AutoSizer, SchedButtonSizer, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), 253 | 254 | wxSizer:add(StepSizer, StepStaticText), 255 | wxSizer:add(StepSizer, StepTextCtrl), 256 | 257 | wxSizer:add(StepButtonSizer, ForwardButton), 258 | wxSizer:addSpacer(StepButtonSizer, 5), 259 | wxSizer:add(StepButtonSizer, BackwardButton), 260 | 261 | wxSizer:add(SchedButtonSizer, NormalizeButton), 262 | 263 | 264 | 265 | wxSizer:add(BorderSizer, AutoSizer, [{flag, ?wxALL bor ?wxALIGN_CENTER_HORIZONTAL}, {border, 10}]), 266 | wxWindow:setSizer(AutoPanel, BorderSizer), 267 | AutoPanel. 268 | 269 | setupRollPanel(Parent) -> 270 | RollPanel = wxPanel:new(Parent), 271 | RollPidStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "Pid:"), 272 | RollStepStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "Steps:"), 273 | RollSpawnIdStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "Pid:"), 274 | RollSendIdStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "MsgId:"), 275 | RollRecIdStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "MsgId:"), 276 | RollVarIdStaticText = wxStaticText:new(RollPanel, ?wxID_ANY, "Name:"), 277 | 278 | RollPidTextCtrl = wxTextCtrl:new(RollPanel, ?ROLL_PID_TEXT, [{style,?wxBOTTOM}, 279 | {size, {40, -1}}]), 280 | RollStepTextCtrl = wxTextCtrl:new(RollPanel, ?ROLL_STEP_TEXT, [{style,?wxBOTTOM}, 281 | {size, {40, -1}}]), 282 | RollSpawnIdText = wxTextCtrl:new(RollPanel, ?ROLL_SPAWN_ID_TEXT, [{style, ?wxBOTTOM}, 283 | {size, {40, -1}}]), 284 | RollSendIdText = wxTextCtrl:new(RollPanel, ?ROLL_SEND_ID_TEXT, [{style, ?wxBOTTOM}, 285 | {size, {40, -1}}]), 286 | RollRecIdText = wxTextCtrl:new(RollPanel, ?ROLL_REC_ID_TEXT, [{style, ?wxBOTTOM}, 287 | {size, {40, -1}}]), 288 | RollVarIdText = wxTextCtrl:new(RollPanel, ?ROLL_VAR_ID_TEXT, [{style, ?wxBOTTOM}, 289 | {size, {80, -1}}]), 290 | 291 | ref_add(?ROLL_PID_TEXT, RollPidTextCtrl), 292 | ref_add(?ROLL_STEP_TEXT, RollStepTextCtrl), 293 | ref_add(?ROLL_SPAWN_ID_TEXT, RollSpawnIdText), 294 | ref_add(?ROLL_SEND_ID_TEXT, RollSendIdText), 295 | ref_add(?ROLL_REC_ID_TEXT, RollRecIdText), 296 | ref_add(?ROLL_VAR_ID_TEXT, RollVarIdText), 297 | 298 | RollButton = wxButton:new(RollPanel, ?ROLL_BUTTON, 299 | [{label, "Roll"}, 300 | {size, {85, -1}}]), 301 | RollSpawnButton = wxButton:new(RollPanel, ?ROLL_SPAWN_BUTTON, 302 | [{label, "Roll spawn"}, 303 | {size, {85, -1}}]), 304 | RollSendButton = wxButton:new(RollPanel, ?ROLL_SEND_BUTTON, 305 | [{label, "Roll send"}, 306 | {size, {85, -1}}]), 307 | RollRecButton = wxButton:new(RollPanel, ?ROLL_REC_BUTTON, 308 | [{label, "Roll rec"}, 309 | {size, {85, -1}}]), 310 | RollVarButton = wxButton:new(RollPanel, ?ROLL_VAR_BUTTON, 311 | [{label, "Roll var"}, 312 | {size, {85, -1}}]), 313 | wxButton:disable(RollButton), 314 | wxButton:disable(RollSpawnButton), 315 | wxButton:disable(RollSendButton), 316 | wxButton:disable(RollRecButton), 317 | wxButton:disable(RollVarButton), 318 | ref_add(?ROLL_BUTTON, RollButton), 319 | ref_add(?ROLL_SPAWN_BUTTON, RollSpawnButton), 320 | ref_add(?ROLL_SEND_BUTTON, RollSendButton), 321 | ref_add(?ROLL_REC_BUTTON, RollRecButton), 322 | ref_add(?ROLL_VAR_BUTTON, RollVarButton), 323 | 324 | RollSizer = wxBoxSizer:new(?wxVERTICAL), 325 | RollNSizer = wxBoxSizer:new(?wxHORIZONTAL), 326 | RollSpawnSizer = wxBoxSizer:new(?wxHORIZONTAL), 327 | RollSendSizer = wxBoxSizer:new(?wxHORIZONTAL), 328 | RollRecSizer = wxBoxSizer:new(?wxHORIZONTAL), 329 | RollVarSizer = wxBoxSizer:new(?wxHORIZONTAL), 330 | BorderSizer = wxBoxSizer:new(?wxVERTICAL), 331 | 332 | wxSizer:add(RollNSizer, RollPidStaticText), 333 | wxSizer:add(RollNSizer, RollPidTextCtrl), 334 | wxSizer:addSpacer(RollNSizer, 5), 335 | wxSizer:add(RollNSizer, RollStepStaticText), 336 | wxSizer:add(RollNSizer, RollStepTextCtrl), 337 | wxSizer:addSpacer(RollNSizer, 5), 338 | wxSizer:add(RollNSizer, RollButton), 339 | 340 | wxSizer:add(RollSizer, RollNSizer, [{flag, ?wxALIGN_RIGHT}]), 341 | wxSizer:addSpacer(RollSizer, 10), 342 | wxSizer:add(RollSizer, RollSpawnSizer, [{flag, ?wxALIGN_RIGHT}]), 343 | wxSizer:addSpacer(RollSizer, 10), 344 | wxSizer:add(RollSizer, RollSendSizer, [{flag, ?wxALIGN_RIGHT}]), 345 | wxSizer:addSpacer(RollSizer, 10), 346 | wxSizer:add(RollSizer, RollRecSizer, [{flag, ?wxALIGN_RIGHT}]), 347 | wxSizer:addSpacer(RollSizer, 10), 348 | wxSizer:add(RollSizer, RollVarSizer, [{flag, ?wxALIGN_RIGHT}]), 349 | 350 | wxSizer:add(RollSpawnSizer, RollSpawnIdStaticText), 351 | wxSizer:add(RollSpawnSizer, RollSpawnIdText), 352 | wxSizer:addSpacer(RollSpawnSizer, 5), 353 | wxSizer:add(RollSpawnSizer, RollSpawnButton), 354 | 355 | wxSizer:add(RollSendSizer, RollSendIdStaticText), 356 | wxSizer:add(RollSendSizer, RollSendIdText), 357 | wxSizer:addSpacer(RollSendSizer, 5), 358 | wxSizer:add(RollSendSizer, RollSendButton), 359 | 360 | wxSizer:add(RollRecSizer, RollRecIdStaticText), 361 | wxSizer:add(RollRecSizer, RollRecIdText), 362 | wxSizer:addSpacer(RollRecSizer, 5), 363 | wxSizer:add(RollRecSizer, RollRecButton), 364 | 365 | wxSizer:add(RollVarSizer, RollVarIdStaticText), 366 | wxSizer:add(RollVarSizer, RollVarIdText), 367 | wxSizer:addSpacer(RollVarSizer, 5), 368 | wxSizer:add(RollVarSizer, RollVarButton), 369 | 370 | wxSizer:add(BorderSizer, RollSizer, [{flag, ?wxALL bor ?wxALIGN_CENTER_HORIZONTAL}, {border, 10}]), 371 | wxWindow:setSizer(RollPanel, BorderSizer), 372 | RollPanel. 373 | 374 | setupMenu() -> 375 | MenuBar = wxMenuBar:new(), 376 | File = wxMenu:new(), 377 | View = wxMenu:new(), 378 | Compile = wxMenu:new(), 379 | Sched = wxMenu:new(), 380 | Help = wxMenu:new(), 381 | ref_add(?MENU_VIEW, View), 382 | ref_add(?MENU_COMP, Compile), 383 | ref_add(?MENU_SCHED, Sched), 384 | wxMenuBar:append(MenuBar, File, "&File"), 385 | wxMenuBar:append(MenuBar, View, "&View"), 386 | wxMenuBar:append(MenuBar, Compile, "&Compiler"), 387 | wxMenuBar:append(MenuBar, Sched, "&Scheduler"), 388 | wxMenuBar:append(MenuBar, Help, "&Help"), 389 | OpenItem = wxMenu:append(File, ?OPEN, "Open\tCtrl-O"), 390 | %ReplayItem = wxMenu:append(File, ?REPLAY, "Replay\tCtrl-R"), 391 | QuitItem = wxMenu:append(File, ?EXIT, "Quit\tCtrl-Q"), 392 | ZoomInItem = wxMenu:append(View, ?ZOOM_IN, "Zoom In\tCtrl-+"), 393 | ZoomOutItem = wxMenu:append(View, ?ZOOM_OUT, "Zoom Out\tCtrl--"), 394 | wxMenu:appendSeparator(View), 395 | ToggleMail = wxMenu:appendCheckItem(View, ?TOGGLE_MAIL, "Toggle Mailboxes"), 396 | ToggleHist = wxMenu:appendCheckItem(View, ?TOGGLE_HIST, "Toggle Histories"), 397 | ToggleEnv = wxMenu:appendCheckItem(View, ?TOGGLE_ENV, "Toggle Environments"), 398 | ToggleExp = wxMenu:appendCheckItem(View, ?TOGGLE_EXP, "Toggle Expressions"), 399 | %%Insert toggle for graphic trace view 400 | ToggleViewer=wxMenu:appendCheckItem(View,?TOGGLE_VIEWER,"Toggle Graphic Viewer"), 401 | %% 402 | wxMenu:appendSeparator(View), 403 | RadioConc = wxMenu:appendRadioItem(View, ?RADIO_CONC, "Conc. History"), 404 | RadioFull = wxMenu:appendRadioItem(View, ?RADIO_FULL, "Full History"), 405 | wxMenu:appendSeparator(View), 406 | RadioRelEnv = wxMenu:appendRadioItem(View, ?RADIO_REL_ENV, "Relevant Environment"), 407 | RadioFullEnv = wxMenu:appendRadioItem(View, ?RADIO_FULL_ENV, "Full Environment"), 408 | wxMenu:appendSeparator(View), 409 | wxMenuItem:check(ToggleMail), 410 | wxMenuItem:check(ToggleHist), 411 | wxMenuItem:check(ToggleEnv), 412 | wxMenuItem:check(ToggleExp), 413 | wxMenuItem:check(RadioConc), 414 | wxMenuItem:check(RadioRelEnv), 415 | ToggleComp = wxMenu:appendCheckItem(Compile, ?TOGGLE_COMP, "Compiler Optimizations"), 416 | wxMenuItem:check(ToggleComp), 417 | RadioRand = wxMenu:appendRadioItem(Sched, ?RADIO_RAND, "Random"), 418 | RadioPrio = wxMenu:appendRadioItem(Sched, ?RADIO_PRIO, "Random (Prio. Proc.)"), 419 | wxMenuItem:check(RadioPrio), 420 | wxMenu:append(Help, ?ABOUT, "About"), 421 | wxMenuItem:setHelp(OpenItem, ?HELP_OPEN_ITEM), 422 | %wxMenuItem:setHelp(ReplayItem, ?HELP_REPLAY_ITEM), 423 | wxMenuItem:setHelp(QuitItem, ?HELP_QUIT_ITEM), 424 | wxMenuItem:setHelp(ZoomInItem, ?HELP_ZOOM_IN_ITEM), 425 | wxMenuItem:setHelp(ZoomOutItem, ?HELP_ZOOM_OUT_ITEM), 426 | wxMenuItem:setHelp(ToggleMail, ?HELP_TOGGLE_MAIL), 427 | wxMenuItem:setHelp(ToggleHist, ?HELP_TOGGLE_HIST), 428 | wxMenuItem:setHelp(ToggleEnv, ?HELP_TOGGLE_ENV), 429 | wxMenuItem:setHelp(ToggleExp, ?HELP_TOGGLE_EXP), 430 | %%set helper for the toggle viewer 431 | wxMenuItem:setHelp(ToggleViewer, ?HELP_TOGGLE_VIEWER), 432 | %% 433 | wxMenuItem:setHelp(RadioConc, ?HELP_RADIO_CONC), 434 | wxMenuItem:setHelp(RadioFull, ?HELP_RADIO_FULL), 435 | wxMenuItem:setHelp(RadioRelEnv, ?HELP_RADIO_REN_ENV), 436 | wxMenuItem:setHelp(RadioFullEnv, ?HELP_RADIO_FULL_ENV), 437 | wxMenuItem:setHelp(ToggleComp, ?HELP_TOGGLE_COMP), 438 | wxMenuItem:setHelp(RadioRand, ?HELP_RADIO_RAND), 439 | wxMenuItem:setHelp(RadioPrio, ?HELP_RADIO_PRIO), 440 | Frame = ref_lookup(?FRAME), 441 | wxFrame:setMenuBar(Frame, MenuBar). 442 | 443 | loadFile(File) -> 444 | Frame = ref_lookup(?FRAME), 445 | ToggleOpts = utils_gui:toggle_opts(), 446 | AddOptimize = proplists:get_value(?COMP_OPT, ToggleOpts), 447 | CompOpts = 448 | case AddOptimize of 449 | true -> [to_core,binary]; 450 | false -> [to_core,binary, no_copt] 451 | end, 452 | case compile:file(File, CompOpts) of 453 | {ok, _, CoreForms} -> 454 | NoAttsCoreForms = cerl:update_c_module(CoreForms, 455 | cerl:module_name(CoreForms), 456 | cerl:module_exports(CoreForms), 457 | [], 458 | cerl:module_defs(CoreForms)), 459 | Stripper = fun(Tree) -> cerl:set_ann(Tree, []) end, 460 | CleanCoreForms = cerl_trees:map(Stripper, NoAttsCoreForms), 461 | FunDefs = cerl:module_defs(CleanCoreForms), 462 | CodeText = ref_lookup(?CODE_TEXT), 463 | wxTextCtrl:setValue(CodeText, core_pp:format(CleanCoreForms)), 464 | Status = ref_lookup(?STATUS), 465 | ref_add(?STATUS, Status#status{loaded = {true, FunDefs}}), 466 | LeftNotebook = ref_lookup(?LEFT_NOTEBOOK), 467 | wxNotebook:setSelection(LeftNotebook, ?PAGEPOS_CODE), 468 | utils_gui:set_choices(utils:moduleNames(CleanCoreForms)), 469 | utils_gui:disable_all_buttons(), 470 | utils_gui:clear_texts(), 471 | InputSizer = ref_lookup(?INPUT_SIZER), 472 | wxSizer:layout(InputSizer), 473 | StartButton = ref_lookup(?START_BUTTON), 474 | wxButton:enable(StartButton), 475 | wxFrame:setStatusText(Frame, "Loaded file " ++ File); 476 | _Other -> 477 | wxFrame:setStatusText(Frame, "Error: Could not compile file " ++ File) 478 | end. 479 | 480 | %%loadReplayData(Path) -> 481 | %% utils:extract_replay_data(Path), 482 | %% ReplayData = get(replay_data), 483 | %% {_Mod, Fun, Args} = utils:get_mod_name(ReplayData#replay.call), 484 | %% MainPid = ReplayData#replay.main_pid, 485 | %% MainLog = utils:extract_pid_log_data(Path, MainPid), 486 | %% start(cerl:c_var({Fun,length(Args)}), Args, MainPid, MainLog), 487 | %% cauder:eval_replay(). 488 | 489 | openDialog(Parent) -> 490 | Caption = "Select an Erlang file", 491 | Wildcard = "Erlang source|*.erl| All files|*", 492 | DefaultDir = ref_lookup(?FILE_PATH), 493 | DefaultFile = "", 494 | Dialog = wxFileDialog:new(Parent, [{message, Caption}, 495 | {defaultDir, DefaultDir}, 496 | {defaultFile, DefaultFile}, 497 | {wildCard, Wildcard}, 498 | {style, ?wxFD_OPEN bor 499 | ?wxFD_FILE_MUST_EXIST}]), 500 | case wxDialog:showModal(Dialog) of 501 | ?wxID_OK -> 502 | File = wxFileDialog:getPath(Dialog), 503 | Path = wxFileDialog:getDirectory(Dialog), 504 | ref_add(?LAST_PATH,Path), 505 | loadFile(File); 506 | _Other -> continue 507 | end, 508 | wxDialog:destroy(Dialog). 509 | 510 | %%openReplayDialog(Parent) -> 511 | %% Caption = "Select a log folder", 512 | %% DefaultPath = ref_lookup(?FILE_PATH), 513 | %% Dialog = wxDirDialog:new(Parent, [{title, Caption}, 514 | %% {defaultPath, DefaultPath}, 515 | %% {style, ?wxDD_DIR_MUST_EXIST}]), 516 | %% case wxDialog:showModal(Dialog) of 517 | %% ?wxID_OK -> 518 | %% Path = wxDirDialog:getPath(Dialog), 519 | %% loadReplayData(Path); 520 | %% _Other -> continue 521 | %% end, 522 | %% wxDialog:destroy(Dialog). 523 | 524 | zoomIn() -> 525 | CodeText = ref_lookup(?CODE_TEXT), 526 | StateText = ref_lookup(?STATE_TEXT), 527 | Font = wxTextCtrl:getFont(CodeText), 528 | CurFontSize = wxFont:getPointSize(Font), 529 | NewFontSize = utils_gui:next_font_size(CurFontSize), 530 | NewFont = wxFont:new(), 531 | wxFont:setPointSize(NewFont, NewFontSize), 532 | wxTextCtrl:setFont(CodeText, NewFont), 533 | wxTextCtrl:setFont(StateText, NewFont). 534 | 535 | zoomOut() -> 536 | CodeText = ref_lookup(?CODE_TEXT), 537 | StateText = ref_lookup(?STATE_TEXT), 538 | Font = wxTextCtrl:getFont(CodeText), 539 | CurFontSize = wxFont:getPointSize(Font), 540 | NewFontSize = utils_gui:prev_font_size(CurFontSize), 541 | NewFont = wxFont:new(), 542 | wxFont:setPointSize(NewFont, NewFontSize), 543 | wxTextCtrl:setFont(CodeText, NewFont), 544 | wxTextCtrl:setFont(StateText, NewFont). 545 | 546 | init_system(Fun, Args, Pid, Log) -> 547 | Proc = #proc{pid = cerl:c_int(Pid), 548 | log = Log, 549 | exp = cerl:c_apply(Fun, Args), 550 | spf = cerl:var_name(Fun)}, 551 | Procs = [Proc], 552 | Sched = utils_gui:sched_opt(), 553 | System = #sys{sched = Sched, procs = Procs}, 554 | ref_add(?SYSTEM, System), 555 | Status = ref_lookup(?STATUS), 556 | NewStatus = Status#status{running = true}, 557 | ref_add(?STATUS, NewStatus). 558 | 559 | start(Fun,Args) -> 560 | start(Fun, Args, 1, []). 561 | 562 | start(Fun,Args, Pid, Log) -> 563 | Status = ref_lookup(?STATUS), 564 | #status{loaded = {true, FunDefs}} = Status, 565 | utils_gui:stop_refs(), 566 | cauder:start_refs(FunDefs), 567 | init_system(Fun, Args, Pid, Log), 568 | refresh(true), 569 | LeftNotebook = ref_lookup(?LEFT_NOTEBOOK), 570 | wxNotebook:setSelection(LeftNotebook, ?PAGEPOS_STATE), 571 | {FunName, FunArity} = cerl:var_name(Fun), 572 | StartString = "Started system with " ++ 573 | atom_to_list(FunName) ++ "/" ++ 574 | integer_to_list(FunArity) ++ " fun application!", 575 | utils_gui:update_status_text(StartString). 576 | 577 | refresh_buttons(Options) -> 578 | PidTextCtrl = ref_lookup(?PID_TEXT), 579 | PidText = wxTextCtrl:getValue(PidTextCtrl), 580 | ManualButtons = lists:seq(?FORW_INT_BUTTON, ?BACK_SCH_BUTTON), 581 | ?LOG("full options: " ++ ?TO_STRING(utils_gui:sort_opts(Options))), 582 | case string:to_integer(PidText) of 583 | {error, _} -> 584 | utils_gui:disable_rule_buttons(ManualButtons); 585 | {PidInt, _} -> 586 | FiltOpts = utils:filter_options(Options, PidInt), 587 | FiltButtons = lists:map(fun utils_gui:option_to_button_label/1, FiltOpts), 588 | [utils_gui:set_button_label_if(Button, FiltButtons) || 589 | Button <- ManualButtons] 590 | end, 591 | HasFwdOptions = utils:has_fwd(Options), 592 | HasBwdOptions = utils:has_bwd(Options), 593 | HasNormOptions = utils:has_norm(Options), 594 | utils_gui:set_ref_button_if(?FORWARD_BUTTON, HasFwdOptions), 595 | utils_gui:set_ref_button_if(?BACKWARD_BUTTON, HasBwdOptions), 596 | utils_gui:set_ref_button_if(?NORMALIZE_BUTTON, HasNormOptions). 597 | 598 | refresh(RefState) -> 599 | case utils_gui:is_app_running() of 600 | false -> ok; 601 | true -> 602 | System = ref_lookup(?SYSTEM), 603 | Options = cauder:eval_opts(System), 604 | case RefState of 605 | false -> ok; 606 | true -> 607 | ToggleOpts = utils_gui:toggle_opts(), 608 | StateText = ref_lookup(?STATE_TEXT), 609 | TraceText = ref_lookup(?TRACE_TEXT), 610 | RollLogText = ref_lookup(?ROLL_LOG_TEXT), 611 | MarkedText = utils:pp_system(System, ToggleOpts), 612 | utils_gui:pp_marked_text(StateText, MarkedText), 613 | wxTextCtrl:setValue(TraceText,utils:pp_trace(System)), 614 | wxTextCtrl:setValue(RollLogText,utils:pp_roll_log(System)) 615 | end, 616 | refresh_buttons(Options), 617 | utils_gui:enable_perm_buttons() 618 | end. 619 | 620 | start() -> 621 | InputTextCtrl = ref_lookup(?INPUT_TEXT), 622 | InputText = wxTextCtrl:getValue(InputTextCtrl), 623 | FunChoice = ref_lookup(?FUN_CHOICE), 624 | NumChoice = wxChoice:getSelection(FunChoice), 625 | StringChoice = wxChoice:getString(FunChoice, NumChoice), 626 | Fun = utils:stringToFunName(StringChoice), 627 | Args = utils:stringToCoreArgs(InputText), 628 | {_, FunArity} = cerl:var_name(Fun), 629 | case FunArity == length(Args) of 630 | true -> 631 | start(Fun, Args), 632 | ?LOG("start fun " ++ StringChoice ++ " with args " ++ InputText); 633 | false -> 634 | utils_gui:update_status_text(?ERROR_NUM_ARGS), 635 | error 636 | end. 637 | 638 | exec_with(Button) -> 639 | System = ref_lookup(?SYSTEM), 640 | PidTextCtrl = ref_lookup(?PID_TEXT), 641 | PidText = wxTextCtrl:getValue(PidTextCtrl), 642 | case string:to_integer(PidText) of 643 | {error, _} -> 644 | ok; 645 | {PidInt, _} -> 646 | PartOption = utils_gui:button_to_option(Button), 647 | Option = PartOption#opt{id = PidInt}, 648 | NewSystem = cauder:eval_step(System, Option), 649 | ref_add(?SYSTEM, NewSystem) 650 | end. 651 | 652 | eval_mult(Button) -> 653 | System = ref_lookup(?SYSTEM), 654 | StepTextCtrl = ref_lookup(?STEP_TEXT), 655 | StepText = wxTextCtrl:getValue(StepTextCtrl), 656 | case string:to_integer(StepText) of 657 | {error, _} -> 658 | error; 659 | {Steps, _} -> 660 | Option = 661 | case Button of 662 | ?FORWARD_BUTTON -> ?MULT_FWD; 663 | ?BACKWARD_BUTTON -> ?MULT_BWD 664 | end, 665 | {NewSystem, StepsDone} = cauder:eval_mult(System, Option, Steps), 666 | ref_add(?SYSTEM, NewSystem), 667 | {StepsDone, Steps} 668 | end. 669 | 670 | eval_norm() -> 671 | System = ref_lookup(?SYSTEM), 672 | {NewSystem, StepsDone} = cauder:eval_norm(System), 673 | ref_add(?SYSTEM, NewSystem), 674 | StepsDone. 675 | 676 | eval_roll() -> 677 | System = ref_lookup(?SYSTEM), 678 | PidTextCtrl = ref_lookup(?ROLL_PID_TEXT), 679 | PidText = wxTextCtrl:getValue(PidTextCtrl), 680 | StepTextCtrl = ref_lookup(?ROLL_STEP_TEXT), 681 | StepText = wxTextCtrl:getValue(StepTextCtrl), 682 | {Pid, _} = string:to_integer(PidText), 683 | {Steps, _} = string:to_integer(StepText), 684 | case {Pid, Steps} of 685 | {error, _} -> {false, 0, 0}; 686 | {_, error} -> {false, 0, 0}; 687 | _ -> 688 | CorePid = cerl:c_int(Pid), 689 | {FocusLog, NewSystem, StepsDone} = cauder:eval_roll(System, CorePid, Steps), 690 | ref_add(?SYSTEM, NewSystem), 691 | {FocusLog, StepsDone, Steps} 692 | end. 693 | 694 | eval_roll_send() -> 695 | System = ref_lookup(?SYSTEM), 696 | IdTextCtrl = ref_lookup(?ROLL_SEND_ID_TEXT), 697 | IdText = wxTextCtrl:getValue(IdTextCtrl), 698 | {Id, _} = string:to_integer(IdText), 699 | case Id of 700 | error -> {false, ok, false}; 701 | _ -> 702 | {CanRoll, FocusLog, NewSystem} = cauder:eval_roll_send(System, Id), 703 | ref_add(?SYSTEM, NewSystem), 704 | {CanRoll, IdText, FocusLog} 705 | end. 706 | 707 | eval_roll_spawn() -> 708 | System = ref_lookup(?SYSTEM), 709 | IdTextCtrl = ref_lookup(?ROLL_SPAWN_ID_TEXT), 710 | IdText = wxTextCtrl:getValue(IdTextCtrl), 711 | {Id, _} = string:to_integer(IdText), 712 | case Id of 713 | error -> {false, ok, false}; 714 | _ -> 715 | {CanRoll, FocusLog, NewSystem} = cauder:eval_roll_spawn(System, cerl:c_int(Id)), 716 | ref_add(?SYSTEM, NewSystem), 717 | {CanRoll, IdText, FocusLog} 718 | end. 719 | 720 | eval_roll_rec() -> 721 | System = ref_lookup(?SYSTEM), 722 | IdTextCtrl = ref_lookup(?ROLL_REC_ID_TEXT), 723 | IdText = wxTextCtrl:getValue(IdTextCtrl), 724 | {Id, _} = string:to_integer(IdText), 725 | case Id of 726 | error -> {false, ok, false}; 727 | _ -> 728 | {CanRoll, FocusLog, NewSystem} = cauder:eval_roll_rec(System, Id), 729 | ref_add(?SYSTEM, NewSystem), 730 | {CanRoll, IdText, FocusLog} 731 | end. 732 | 733 | eval_roll_var() -> 734 | System = ref_lookup(?SYSTEM), 735 | IdTextCtrl = ref_lookup(?ROLL_VAR_ID_TEXT), 736 | IdText = wxTextCtrl:getValue(IdTextCtrl), 737 | case IdText of 738 | "" -> {false, ok, false}; 739 | _ -> 740 | % Variables such as '@c1_X' appear as '_@c1_X' 741 | % This case removes the "_" from the variable 742 | % name if it is the first character 743 | VarName = 744 | case string:find(IdText, "_") of 745 | no_match -> IdText; 746 | Match when length(IdText) =:= length(Match) -> 747 | string:slice(IdText, 1); 748 | _ -> 749 | IdText 750 | end, 751 | Var = cerl:c_var(list_to_atom(VarName)), 752 | {CanRoll, FocusLog, NewSystem} = cauder:eval_roll_var(System, Var), 753 | ref_add(?SYSTEM, NewSystem), 754 | {CanRoll, VarName, FocusLog} 755 | end. 756 | 757 | set_sched() -> 758 | Status = ref_lookup(?STATUS), 759 | Running = Status#status.running, 760 | Sched = utils_gui:sched_opt(), 761 | case Running of 762 | true -> 763 | System = ref_lookup(?SYSTEM), 764 | NewSystem = System#sys{sched = Sched}, 765 | ref_add(?SYSTEM, NewSystem); 766 | false -> ok 767 | end. 768 | 769 | focus_roll_log(false) -> ok; 770 | focus_roll_log(true) -> 771 | RBotNotebook = ref_lookup(?RBOT_NOTEBOOK), 772 | wxNotebook:setSelection(RBotNotebook, ?PAGEPOS_ROLL). 773 | 774 | loop() -> 775 | receive 776 | %% ------------------- Button handlers ------------------- %% 777 | #wx{id = ?START_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 778 | start(), 779 | loop(); 780 | #wx{id = ?NORMALIZE_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 781 | utils_gui:disable_all_buttons(), 782 | StepsDone = eval_norm(), 783 | utils_gui:sttext_norm(StepsDone), 784 | refresh(true), 785 | loop(); 786 | #wx{id = ?ROLL_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 787 | utils_gui:disable_all_buttons(), 788 | {MustFocus, StepsDone, TotalSteps} = eval_roll(), 789 | utils_gui:sttext_roll(StepsDone, TotalSteps), 790 | focus_roll_log(MustFocus), 791 | refresh(true), 792 | loop(); 793 | #wx{id = RuleButton, event = #wxCommand{type = command_button_clicked}} 794 | when (RuleButton >= ?FORW_INT_BUTTON) and (RuleButton =< ?BACK_SCH_BUTTON) -> 795 | utils_gui:disable_all_buttons(), 796 | exec_with(RuleButton), 797 | utils_gui:sttext_single(RuleButton), 798 | refresh(true), 799 | loop(); 800 | #wx{id = RuleButton, event = #wxCommand{type = command_button_clicked}} 801 | when (RuleButton == ?FORWARD_BUTTON) or (RuleButton == ?BACKWARD_BUTTON) -> 802 | utils_gui:disable_all_buttons(), 803 | case eval_mult(RuleButton) of 804 | error -> 805 | utils_gui:update_status_text(?ERROR_NUM_STEP); 806 | {StepsDone, TotalSteps} -> 807 | utils_gui:sttext_mult(StepsDone, TotalSteps) 808 | end, 809 | refresh(true), 810 | loop(); 811 | #wx{id = ?ROLL_SEND_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 812 | utils_gui:disable_all_buttons(), 813 | {HasRolled, SendId, MustFocus} = eval_roll_send(), 814 | utils_gui:sttext_roll_send(HasRolled, SendId), 815 | focus_roll_log(MustFocus), 816 | refresh(HasRolled), 817 | loop(); 818 | #wx{id = ?ROLL_SPAWN_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 819 | utils_gui:disable_all_buttons(), 820 | {HasRolled, SpawnId, MustFocus} = eval_roll_spawn(), 821 | utils_gui:sttext_roll_spawn(HasRolled, SpawnId), 822 | focus_roll_log(MustFocus), 823 | refresh(HasRolled), 824 | loop(); 825 | #wx{id = ?ROLL_REC_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 826 | utils_gui:disable_all_buttons(), 827 | {HasRolled, RecId, MustFocus} = eval_roll_rec(), 828 | utils_gui:sttext_roll_rec(HasRolled, RecId), 829 | focus_roll_log(MustFocus), 830 | refresh(HasRolled), 831 | loop(); 832 | #wx{id = ?ROLL_VAR_BUTTON, event = #wxCommand{type = command_button_clicked}} -> 833 | utils_gui:disable_all_buttons(), 834 | {HasRolled, VarId, MustFocus} = eval_roll_var(), 835 | utils_gui:sttext_roll_var(HasRolled, VarId), 836 | focus_roll_log(MustFocus), 837 | refresh(HasRolled), 838 | loop(); 839 | %% -------------------- Text handlers -------------------- %% 840 | #wx{id = ?PID_TEXT, event = #wxCommand{type = command_text_updated}} -> 841 | refresh(false), 842 | loop(); 843 | #wx{id = ?STEP_TEXT, event = #wxCommand{type = command_text_updated}} -> 844 | refresh(false), 845 | loop(); 846 | #wx{id = _RestIds, event = #wxCommand{type = command_text_updated}} -> 847 | loop(); 848 | %% -------------------- Menu handlers -------------------- %% 849 | #wx{id = ?ABOUT, event = #wxCommand{type = command_menu_selected}} -> 850 | Caption = "About " ++ ?APP_STRING, 851 | Frame = ref_lookup(?FRAME), 852 | Dialog = wxMessageDialog:new(Frame, ?INFO_TEXT, 853 | [{style, ?wxOK}, 854 | {caption, Caption}]), 855 | wxDialog:showModal(Dialog), 856 | wxWindow:destroy(Dialog), 857 | loop(); 858 | #wx{id = ?OPEN, event = #wxCommand{type = command_menu_selected}} -> 859 | Frame = ref_lookup(?FRAME), 860 | openDialog(Frame), 861 | loop(); 862 | %#wx{id = ?REPLAY, event = #wxCommand{type = command_menu_selected}} -> 863 | % Frame = ref_lookup(?FRAME), 864 | % openReplayDialog(Frame), 865 | % loop(); 866 | #wx{id = ?ZOOM_IN, event = #wxCommand{type = command_menu_selected}} -> 867 | zoomIn(), 868 | loop(); 869 | #wx{id = ?ZOOM_OUT, event = #wxCommand{type = command_menu_selected}} -> 870 | zoomOut(), 871 | loop(); 872 | #wx{id = ?TOGGLE_MAIL, event = #wxCommand{type = command_menu_selected}} -> 873 | refresh(true), 874 | loop(); 875 | #wx{id = ?TOGGLE_HIST, event = #wxCommand{type = command_menu_selected}} -> 876 | refresh(true), 877 | loop(); 878 | #wx{id = ?TOGGLE_ENV, event = #wxCommand{type = command_menu_selected}} -> 879 | refresh(true), 880 | loop(); 881 | #wx{id = ?TOGGLE_EXP, event = #wxCommand{type = command_menu_selected}} -> 882 | refresh(true), 883 | loop(); 884 | %%implements the backend behaviuor when press toggle viewer 885 | #wx{id = ?TOGGLE_VIEWER, event = #wxCommand{type = command_menu_selected}} -> 886 | case whereis(tracer) of% check if the tracer is already active 887 | undefined->%if not 888 | Pid=spawn(tracer,init,[]),%%spawn it 889 | register(tracer,Pid),%and register it 890 | case ets:member(?GUI_REF,?SYSTEM) of %check if the trace store exists 891 | false->%% if there isn't no trace info stored,do nothing 892 | ok; 893 | true->%else send the reversed trace infos to the graphic tracer 894 | System=ref_lookup(?SYSTEM), 895 | Pid ! {show,lists:reverse(System#sys.trace)} 896 | end; 897 | TracerPid->%if yes 898 | TracerPid ! close% close it 899 | end, 900 | loop(); 901 | %% 902 | #wx{id = ?RADIO_CONC, event = #wxCommand{type = command_menu_selected}} -> 903 | refresh(true), 904 | loop(); 905 | #wx{id = ?RADIO_FULL, event = #wxCommand{type = command_menu_selected}} -> 906 | refresh(true), 907 | loop(); 908 | #wx{id = ?RADIO_REL_ENV, event = #wxCommand{type = command_menu_selected}} -> 909 | refresh(true), 910 | loop(); 911 | #wx{id = ?RADIO_FULL_ENV, event = #wxCommand{type = command_menu_selected}} -> 912 | refresh(true), 913 | loop(); 914 | #wx{id = ?TOGGLE_COMP, event = #wxCommand{type = command_menu_selected}} -> 915 | utils_gui:sttext_comp(), 916 | loop(); 917 | #wx{id = ?RADIO_RAND, event = #wxCommand{type = command_menu_selected}} -> 918 | set_sched(), 919 | loop(); 920 | #wx{id = ?RADIO_PRIO, event = #wxCommand{type = command_menu_selected}} -> 921 | set_sched(), 922 | loop(); 923 | #wx{id = ?EXIT, event = #wxCommand{type = command_menu_selected}} -> 924 | Frame = ref_lookup(?FRAME), 925 | wxFrame:destroy(Frame); 926 | %% ------------------- Other handlers -------------------- %% 927 | #wx{event = #wxClose{type = close_window}} -> 928 | Frame = ref_lookup(?FRAME), 929 | wxFrame:destroy(Frame); 930 | %% ---------------- Non-supported events ----------------- %% 931 | Other -> 932 | io:format("main loop does not implement ~p~n", [Other]), 933 | loop() 934 | end. 935 | 936 | ref_add(Id, Ref) -> 937 | ets:insert(?GUI_REF, {Id, Ref}). 938 | 939 | ref_lookup(Id) -> 940 | ets:lookup_element(?GUI_REF, Id, 2). 941 | 942 | ref_start() -> 943 | ?GUI_REF = ets:new(?GUI_REF, [set, public, named_table]), 944 | ok. 945 | 946 | ref_stop() -> 947 | ets:delete(?GUI_REF). 948 | --------------------------------------------------------------------------------