pcp_rsrc {Semaphore : TYPE; 
          NumberOfTasks : NATURAL, 
          task_descriptors : pcp_task{Semaphore; NumberOfTasks}!TaskDescriptors
         } : CONTEXT =
BEGIN

  taskCtx : CONTEXT = pcp_task{Semaphore; NumberOfTasks};

  semSetCtx : CONTEXT = set{Semaphore;};
  jobSetCtx : CONTEXT = set{taskCtx!JobIdx;};

  Job2SetOfSemaphores : TYPE = ARRAY taskCtx!JobIdx OF semSetCtx!Set;

  % r.alloc[j] is the set of semaphores owned by "j" in "r"
  % r.request[j] is the set of semaphores that "j" requested but has not obtained.
  RSRC : TYPE = [# alloc : Job2SetOfSemaphores, request : Job2SetOfSemaphores #];

  initial_rsrc : RSRC = LET empty_mapping : Job2SetOfSemaphores = [ [j : taskCtx!JobIdx] semSetCtx!empty_set] 
                        IN (# alloc := empty_mapping, request := empty_mapping #);

  % the following array "caches" the ceiling associated with each semaphore.
  ceiling_map : ARRAY Semaphore OF NATURAL =
	  [ [s : Semaphore] taskCtx!ceiling(s, task_descriptors) ];  

  ceiling(s : Semaphore) : NATURAL = ceiling_map[s];
 
  JobIdx : TYPE = taskCtx!JobIdx;

  priority(j : JobIdx) : NATURAL = taskCtx!priority(j, task_descriptors);

  uses_sem?(j : JobIdx, s : Semaphore) : BOOLEAN = taskCtx!uses_sem?(j, s, task_descriptors);

  % returns the set of jobs other than "j" that own a semaphore of ceiling as high as "j"'s priority
  blk(r : RSRC, j : JobIdx) : jobSetCtx!Set =
    LAMBDA (k : JobIdx) : k /= j AND (EXISTS (s : Semaphore): semSetCtx!contains?(r.alloc[k],s) AND ceiling(s) >= priority(j));

  % A job "j" is blocked in "r" if both "blk(r,j)" and "r.request[j]" are nonempty.
  blocked?(r : RSRC, j : JobIdx) : BOOLEAN =
    NOT jobSetCtx!empty?(blk(r, j)) AND NOT semSetCtx!empty?(r.request[j]);

  % alloc_step(r, j, s) executes command "P(s)" on behalf of "j". The semaphore
  % "s" is allocated to "j" if "blk(r, j)" is empty; otherwise, "s" is stored in
  % "r.request[j]" and "j" becomes blocked.
  alloc_step(r : RSRC, j : JobIdx, s : Semaphore) : RSRC =
    IF jobSetCtx!empty?(blk(r, j)) THEN
      r WITH .alloc[j] := semSetCtx!insert(r.alloc[j], s)
    ELSE
      r WITH .request[j] := semSetCtx!insert(r.request[j], s)
    ENDIF;

  % release_step(r, j, s) executes command "V(s)" on behalf of "j". It simply removes
  % "s" from the set of semaphores allocated to "j".
  release_step(r : RSRC, j : JobIdx, s : Semaphore) : RSRC =
    r WITH .alloc[j] := semSetCtx!remove(r.alloc[j], s);

  release_all_step(r : RSRC, j : JobIdx) : RSRC =
    (r WITH .alloc[j] := semSetCtx!empty_set) WITH .request[j] := semSetCtx!empty_set;

  % wakeup(r, j) allocates to "j" all the semaphores "j" requested. This operation is intended
  % for a job "j" that is reactivated after having been blocked.
  wakeup(r : RSRC, j : JobIdx) : RSRC =
    IF r.request[j] /= semSetCtx!empty_set 
    THEN (r WITH .request[j] := semSetCtx!empty_set) WITH .alloc[j] := semSetCtx!union(r.alloc[j], r.request[j])
    ELSE r ENDIF;

END
