ocaml-containers/misc/piCalculus.ml
Simon Cruanes 4bc6c8a008 split into package core (no pack, 'CC' prefix, stable)
and misc where oneshot ideas go
2014-05-16 20:58:28 +02:00

287 lines
9.4 KiB
OCaml

(*
copyright (c) 2013, simon cruanes
all rights reserved.
redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. redistributions in binary
form must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with
the distribution.
this software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. in no event shall the copyright holder or contributors be liable
for any direct, indirect, incidental, special, exemplary, or consequential
damages (including, but not limited to, procurement of substitute goods or
services; loss of use, data, or profits; or business interruption) however
caused and on any theory of liability, whether in contract, strict liability,
or tort (including negligence or otherwise) arising in any way out of the use
of this software, even if advised of the possibility of such damage.
*)
(** {1 Pi-calculus model of concurrency} *)
module DList = struct
type 'a t = {
value : 'a wrapper;
mutable prev : 'a t;
mutable next : 'a t;
}
and 'a wrapper =
| First (* first element of the list *)
| Element of 'a
(** New empty list *)
let create () =
let rec node = {
value = First;
prev = node;
next = node;
} in
node
let is_empty l =
let ans = l.prev == l in
(if ans then (assert (l.next == l && l.value == First)));
ans
(** Add element at the end *)
let append l x =
let node = {
value = Element x;
prev = l.prev;
next = l;
} in
l.prev.next <- node;
l.prev <- node;
node
(** Add element at the beginning *)
let prepend l x =
let node = {
value = Element x;
prev = l;
next = l.next;
} in
l.next.prev <- node;
l.next <- node;
node
(* remove the given element *)
let remove x =
assert (not (x.prev == x || x.next == x));
x.prev.next <- x.next;
x.next.prev <- x.prev;
()
(** Pop the first element *)
let pop l =
match l.next.value with
| First -> failwith "DList.pop: empty list"
| Element x ->
remove l.next;
x
let rec remove_list l = match l with
| [] -> ()
| x::l' -> remove x; remove_list l'
(** Iterate on all elements *)
let iter l f =
let rec iter l = match l.value with
| First -> ()
| Element x ->
f x;
iter l.next
in
iter l.next
end
type 'a chan = {
receivers : 'a transition_node DList.t;
senders : 'a transition_node DList.t;
} (** Channel conveying values of type 'a. Invariant: receivers = None || senders = None *)
and 'a transition_node = {
tn_transition : 'a __transition;
mutable tn_hook : unit -> unit; (* hook to call after transition *)
tn_to_replicate : to_replicate ref; (* do we have to replicate a process *)
} (** List of transitions for a channel *)
and to_replicate =
| ReplicateNothing
| ReplicateThis of process
(** Do we have to replicate a process? *)
and process =
| Parallel : process list -> process (** Spawn several processes *)
| Sum : transition list -> process (** Choice point *)
| Replicate : process -> process (** Replication of a process *)
| New : ('a chan -> process) -> process (** New local name *)
| Escape : (unit -> process) -> process (** Run a user function *)
| Stop : process (** Stop this process *)
(** A process of the Pi-calculus *)
and _ __transition =
| Receive : 'a chan * ('a -> process) -> 'a __transition
| Send : 'a chan * 'a * process -> 'a __transition
(** Transition: send or receive a message *)
and transition =
| Transition : 'a __transition -> transition
let parallel l = (assert (l <> []); Parallel l)
let sum l = (assert (l <> []); Sum l)
let replicate p = Replicate p
let new_ f = New f
let escape f = Escape f
let stop = Stop
let send ch x p = Transition (Send (ch, x, p))
let receive ch f = Transition (Receive (ch, f))
let send_one ch x p = sum [send ch x p]
let receive_one ch f = sum [receive ch f]
let (>>) f p =
escape (fun () -> f (); p)
let (|||) a b = parallel [a; b]
let (++) a b = sum [a; b]
(** New channel (name) *)
let mk_chan () =
let ch = {
receivers = DList.create ();
senders = DList.create ();
} in
ch
type run_env = {
tasks : (process * to_replicate ref) Queue.t;
} (** Environment for running processes *)
let mk_env () =
{ tasks = Queue.create (); }
(** Push the process in the queue of processes to eval *)
let push_process ~env p to_restart =
Queue.push (p, to_restart) env.tasks
(** Check whether there is a process to replicate now *)
let check_replicate ~env to_replicate =
match !to_replicate with
| ReplicateNothing -> ()
| ReplicateThis p' ->
(* replicate p' now; it will be useless from now on to replicate it again *)
push_process ~env p' (ref ReplicateNothing);
to_replicate := ReplicateNothing
(** Make a new transition node (linked to nothing) *)
let mk_transition_node transition to_replicate =
let node = {
tn_transition = transition;
tn_hook = (fun () -> ());
tn_to_replicate = to_replicate;
} in
node
(** Perform the given transition (one send, one receive). *)
let perform_transition
: type a. env:run_env -> a transition_node -> a transition_node -> unit =
fun ~env sender receiver ->
(* cleanup alternatives, replicate some processes if needed *)
sender.tn_hook ();
receiver.tn_hook ();
check_replicate ~env sender.tn_to_replicate;
check_replicate ~env receiver.tn_to_replicate;
match sender.tn_transition, receiver.tn_transition with
| Send (ch, x, send_p), Receive (ch', receive_p) ->
assert (ch == ch');
(* receiving channel gets the sent value *)
let receive_p = receive_p x in
(* push the two new processes (with no process to replicate) *)
push_process ~env send_p (ref ReplicateNothing);
push_process ~env receive_p (ref ReplicateNothing);
()
| _ -> assert false
(** Check whether any transition in the list can be performed; otherwise,
register all of them to their respective channels; Returns the
list of corresponding [transition_node] (empty if some
transition fired immediately). *)
let try_transitions ~env transitions to_replicate =
try
let set_hooks, hook = List.fold_left
(fun (set_hooks, hook) transition -> match transition with
| Transition (Receive (ch, _) as transition) ->
let receiver = mk_transition_node transition to_replicate in
if DList.is_empty ch.senders
then (* wait *)
let dlist = DList.append ch.receivers receiver in
(fun hook -> receiver.tn_hook <- hook) :: set_hooks,
(fun () -> DList.remove dlist; hook ())
else begin (* fire *)
let sender = DList.pop ch.senders in
perform_transition ~env sender receiver;
hook (); (* cancel previous sum cases *)
raise Exit
end
| Transition (Send (ch, _, _) as transition) ->
let sender = mk_transition_node transition to_replicate in
if DList.is_empty ch.receivers
then (* wait *)
let dlist = DList.append ch.senders sender in
(fun hook -> sender.tn_hook <- hook) :: set_hooks,
(fun () -> DList.remove dlist; hook ())
else begin (* fire *)
let receiver = DList.pop ch.receivers in
perform_transition ~env sender receiver;
hook (); (* cancel previous sum cases *)
raise Exit
end)
([], fun () -> ()) transitions
in
(* we have a list of transition nodes; save it for when a transition fires *)
List.iter (fun set_hook -> set_hook hook) set_hooks
with Exit -> (* some transition fired immediately *)
()
(** Run the simulation until all processes are stuck, or stopped. *)
let run p =
(* run tasks one by one until none remains *)
let rec run : env:run_env -> unit = fun ~env ->
if not (Queue.is_empty env.tasks) then begin
(* eval next process *)
let p, to_replicate = Queue.pop env.tasks in
eval_process ~env p to_replicate;
run ~env
end
(* evaluate this process *)
and eval_process : env:run_env -> process -> to_replicate ref -> unit
= fun ~env p to_replicate ->
match p with
| Stop -> (* stop, but maybe there is a process to replicate *)
check_replicate ~env to_replicate
| New f ->
(* apply [f] to a new chan *)
let c = mk_chan () in
let p' = f c in
eval_process ~env p' to_replicate
| Parallel l ->
(* evaluate each process *)
List.iter (fun p -> push_process ~env p to_replicate) l
| Replicate p' ->
(* run [p'] within an env where [p] is to be replicated *)
let to_replicate' = ref (ReplicateThis p) in
eval_process ~env p' to_replicate'
| Escape f ->
let p' = f () in
push_process ~env p' to_replicate (* yield before processing the result *)
| Sum transitions ->
try_transitions ~env transitions to_replicate
in
(* initial env *)
let env = mk_env () in
push_process ~env p (ref ReplicateNothing);
run ~env