(*
 * Generates some integer sequences.  Each generator is a process that
 * yields integers to the main process.  ACK switches processes by
 * saving and restoring the stack.  It uses _lor_ and _str_ to save
 * and restore the local base and frame pointer.
 *)
MODULE SemaTest;
FROM Semaphores IMPORT Sema, NewSema, Down, Up, StartProcess;
FROM Storage IMPORT ALLOCATE;
FROM Test IMPORT fail, finished;

TYPE
  Generator = POINTER TO GeneratorRecord;
  GeneratorRecord = RECORD
    resume: Sema;       (* up when resuming generator *)
    yield: Sema;        (* up when yielding value *)
    value: INTEGER;
  END;
VAR
  curgen: Generator;    (* current generator *)
  startLock: Sema;      (* down when booting generator *)
  startProc: PROC;
  startSelf: Generator;

PROCEDURE BootGenerator;
  VAR pr: PROC; self: Generator;
BEGIN
  pr := startProc;
  self := startSelf;
  Up(startLock);
  Down(self^.resume);   (* wait for first Resume *)
  pr();
END BootGenerator;

PROCEDURE StartGenerator(gen: Generator; pr: PROC);
BEGIN
  gen^.resume := NewSema(0);
  gen^.yield := NewSema(0);
  Down(startLock);
  startProc := pr;
  startSelf := gen;
  StartProcess(BootGenerator, 8192);
END StartGenerator;

PROCEDURE Resume(gen: Generator): INTEGER;
  VAR self: Generator;
BEGIN
  self := curgen;
  curgen := gen;
  Up(gen^.resume);
  Down(gen^.yield);     (* wait for Yield *)
  curgen := self;
  RETURN gen^.value
END Resume;

PROCEDURE Yield(i: INTEGER);
  VAR self: Generator;
BEGIN
  self := curgen;
  self^.value := i;
  Up(self^.yield);      (* curgen becomes invalid *)
  Down(self^.resume);   (* wait for Resume *)
END Yield;

PROCEDURE YieldHalfOf(i: INTEGER);
BEGIN
  Yield(i DIV 2);
END YieldHalfOf;

PROCEDURE Triangular;
  (* Yields the triangular numbers, http://oeis.org/A000217 *)
  VAR n: INTEGER;
BEGIN
  n := 0;
  LOOP
    YieldHalfOf(n * (n + 1));
    INC(n);
  END;
END Triangular;

PROCEDURE Pentagonal;
  (* Yields the pentagonal numbers, http://oeis.org/A000326 *)
  VAR n: INTEGER;
BEGIN
  n := 0;
  LOOP
    YieldHalfOf(n * (3 * n - 1));
    INC(n);
  END;
END Pentagonal;

PROCEDURE Odious;
  (* Yields the odius numbers, http://oeis.org/A000069 *)
  VAR b, i, n: INTEGER;
BEGIN
  n := 1;
  LOOP
    (* b := count bits in n *)
    b := 0;
    i := n;
    WHILE i # 0 DO
      INC(b, i MOD 2);
      i := i DIV 2;
    END;

    IF (b MOD 2) = 1 THEN
      Yield(n);
    END;
    INC(n);
  END;
END Odious;

TYPE
  Triple = ARRAY[1..3] OF INTEGER;
PROCEDURE T(i1, i2, i3: INTEGER): Triple;
  VAR t: Triple;
BEGIN
  t[1] := i1; t[2] := i2; t[3] := i3; RETURN t
END T;

CONST
  two28 = 268435456D;   (* 0x1000_0000 *)
VAR
  a: ARRAY [0..9] OF Triple;
  tri, pen, odi: Generator;
  i, g1, g2, g3: INTEGER;
BEGIN
  startLock := NewSema(1);

  ALLOCATE(tri, SIZE(GeneratorRecord));
  ALLOCATE(pen, SIZE(GeneratorRecord));
  ALLOCATE(odi, SIZE(GeneratorRecord));
  StartGenerator(tri, Triangular);
  StartGenerator(pen, Pentagonal);
  StartGenerator(odi, Odious);

  a[0] := T( 0,   0,  1);
  a[1] := T( 1,   1,  2);
  a[2] := T( 3,   5,  4);
  a[3] := T( 6,  12,  7);
  a[4] := T(10,  22,  8);
  a[5] := T(15,  35, 11);
  a[6] := T(21,  51, 13);
  a[7] := T(28,  70, 14);
  a[8] := T(36,  92, 16);
  a[9] := T(45, 117, 19);

  FOR i := 0 TO INTEGER(9) DO
    g1 := Resume(tri);
    g2 := Resume(pen);
    g3 := Resume(odi);
    IF g1 # a[i][1] THEN fail(1D * two28 + LONG(a[i][1])) END;
    IF g2 # a[i][2] THEN fail(2D * two28 + LONG(a[i][2])) END;
    IF g3 # a[i][3] THEN fail(3D * two28 + LONG(a[i][3])) END;
  END;
  finished;
END SemaTest.