packages feed

alms-0.6.0: examples/session-types-polygons.alms

-- Sutherland-Hodgman (1974) re-entrant polygon clipping

#load "libthread"
#load "libsessiontype"

open SessionType

--
-- We first build a tiny 3-D geometry library
--

-- Points and planes in R^3.
type point = { x, y, z : float }
type plane = [ `Plane of float * float * float * float ]

-- We use the plane `Plane(a, b, c, d) to represent the open half-space
-- { Point(x, y, z) | ax + by + cz + d > 0 }

let string_of_point p =
    "(" ^ string_of p.x ^ ", " ^ string_of p.y ^ ", " ^ string_of p.z ^ ")"

let string_of_plane (`Plane(a, b, c, d): plane) =
    string_of a ^ "x + " ^ string_of b ^ "y + " ^
    string_of c ^ "z + " ^ string_of d ^ " > 0"

(* Some of this should be in the library! *)
let splitWhile : ('a -> bool) -> 'a list -> 'a list * 'a list
  = fun pred ->
      let rec loop (acc: 'a list) (xs: 'a list) : 'a list * 'a list =
                match xs with
                | []         -> (rev acc, [])
                | (x ∷ xs') -> if pred x
                                   then loop (x ∷ acc) xs'
                                   else (rev acc, xs)
       in loop []

let not (b: bool) = if b then false else true

let notp (pred: 'a -> bool): 'a -> bool =
  fun a -> not (pred a)

let isSpace (c: char): bool =
  match c with
  | ' '  -> true
  | '\t' -> true
  | '\n' -> true
  | '\r' -> true
  | _    -> false

let dropSpace (cs : char list) : char list =
  let (_, result) = splitWhile isSpace cs in result

let parsePoint (s : string) : point =
  let foil (x: char list) = float_of_string (implode x) in
    let cs = explode s in
    let ('(' ∷ cs) = dropSpace cs in
    let (x, (_ ∷ cs)) = splitWhile (notp ((==) ',')) (dropSpace cs) in
    let (y, (_ ∷ cs)) = splitWhile (notp ((==) ',')) (dropSpace cs) in
    let (z, (_ ∷ cs)) = splitWhile (notp ((==) ')')) (dropSpace cs) in
      { x = foil x, y = foil y, z = foil z }

let parsePlane (s: string) : plane =
  let foil (x: char list) = float_of_string (implode x) in
    let cs = explode s in
    let (a, (_ ∷ cs)) = splitWhile (notp ((==) 'x')) (dropSpace cs) in
    let ('+' ∷ cs)    = dropSpace cs in
    let (b, (_ ∷ cs)) = splitWhile (notp ((==) 'y')) (dropSpace cs) in
    let ('+' ∷ cs)    = dropSpace cs in
    let (c, (_ ∷ cs)) = splitWhile (notp ((==) 'z')) (dropSpace cs) in
    let ('+' ∷ cs)    = dropSpace cs in
    let (d, (_ ∷ cs)) = splitWhile (notp ((==) '>')) (dropSpace cs) in
    let ('0' ∷ cs)    = dropSpace cs in
      `Plane (foil a, foil b, foil c, foil d)

-- Is the point above the plane?  (i.e., in the semi-space)
let isPointAbovePlane { x, y, z } (`Plane(a, b, c, d)) =
  a *. x +. b *. y +. c *. z +. d >. 0.0

-- Does the line segment between the two points intersect the plane,
-- and if so, where?
let intersect p1 p2 (`Plane(a, b, c, d) as plane) =
 if isPointAbovePlane p1 plane == isPointAbovePlane p2 plane
   then None
   else let t = (a *. p1.x +. b *. p1.y +. c *. p1.z +. d) /.
                (a *. (p1.x -. p2.x) +.
                 b *. (p1.y -. p2.y) +.
                 c *. (p1.z -. p2.z)) in
        let x = p1.x +. (p2.x -. p1.x) *. t in
        let y = p1.y +. (p2.y -. p1.y) *. t in
        let z = p1.z +. (p2.z -. p1.z) *. t in
          Some { x, y, z }

--
-- In sublanguage A, our protocol is to send an unbounded
-- sequence of points:
--

type 'a stream = mu 'x. 1 |&| ?'a; 'x

--
-- Each transducer takes a plane to clip by, and two rendezvous objects,
-- the first on which it expects to receive points, and the second on
-- which it will send points.
--

let clipper (plane: plane)
               (ic: point stream channel)
               (oc: point stream dual channel): unit =
       let finish (oc: point stream dual channel) =
             sel1 oc; () in
       let put (oc: point stream dual channel) (pt: point) =
             send (sel2 oc) pt in
       let putCross (oc: point stream dual channel)
                    (p1: point) (p2: point) =
             match intersect p1 p2 plane with
             | Some pt -> put oc pt
             | None    -> oc in
       let putVisible (oc: point stream dual channel)
                      (pt: point) =
             if isPointAbovePlane pt plane
               then put oc pt
               else oc in
         match follow ic with
         | Left _   -> finish oc
         | Right ic ->
             let (pt0, ic) = recv ic in
             let rec loop (ic: point stream channel)
                          (oc: point stream dual channel)
                          (pt: point) : unit =
                       let oc = putVisible oc pt in
                         match follow ic with
                         | Left _   -> let oc = putCross oc pt pt0 in
                                         finish oc
                         | Right ic -> let (pt', ic) = recv ic in
                                       let oc = putCross oc pt pt' in
                                         loop ic oc pt'
               in loop ic oc pt0

let printer : point stream channel -> unit =
  let rec loop (ic: point stream channel): unit =
            match follow ic with
            | Left _   -> ()
            | Right ic -> let (pt, ic) = recv ic in
                            putStrLn (string_of_point pt);
                            loop ic
   in loop

-- The main protocol for the program, which lets us split our parser
-- from our main loop.
type main_prot = mu 'x. point stream |&| ?plane; 'x

let parser : main_prot dual channel -> unit =
  let rec plane_loop (oc: main_prot dual channel): unit =
            match getLine () with
            | "" -> point_loop (sel1 oc)
            | s  -> let plane = parsePlane s in
                    let oc    = send (sel2 oc) plane in
                      plane_loop oc
      and point_loop (oc: point stream dual channel): unit =
            match getLine () with
            | "" -> sel1 oc; ()
            | s  -> let point = parsePoint s in
                    let oc    = send (sel2 oc) point in
                      point_loop oc
   in plane_loop

let main : unit -> unit =
  let rec get_planes (acc: plane list) (ic: main_prot channel)
                     : plane list * point stream channel =
            match follow ic with
            | Left ic  -> (rev acc, ic)
            | Right ic -> let (plane, ic) = recv ic in
                            get_planes (plane ∷ acc) ic in
  let rec connect (planes: plane list)
                  (ic: point stream channel)
                  : point stream channel =
            match planes with
            | []              -> ic
            | plane ∷ rest ->
                let outrv : point stream rendezvous = newRendezvous () in
                  AThread.fork (fun () ->
                    clipper plane ic (accept outrv));
                  connect rest (request outrv) in
  fun () ->
    let rv           = newRendezvous () : main_prot rendezvous in
    let _            = AThread.fork (fun () -> parser (accept rv)) in
    let (planes, ic) = get_planes [] (request rv) in
    let ic           = connect planes ic in
      printer ic

in
  main ()