--file Sort.mesa
--last modified by Sweet on  April 27, 1980  11:15 PM
-- translated from Ed McCreight's BCPL by Jim Frandeen
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs: FROM "AltoDefs",
  FSPDefs: FROM "fspdefs" USING [NoRoomInZone, NodeOverhead],
  GPsortDefs: FROM "GPsortDefs",
  InlineDefs: FROM "inlinedefs" USING [COPY],
  StreamDefs: FROM "StreamDefs";

Sort: PROGRAM
  IMPORTS FSPDefs, GPsortDefs, InlineDefs, StreamDefs EXPORTS GPsortDefs =
  BEGIN OPEN GPsortDefs;
  
  NFiles: CARDINAL = 3; -- number of scratch files
  
  bufferSize: INTEGER;
  compareProc: CompareProcType;
  files: ARRAY [0..NFiles] OF FdHandle;
  firstFreeEnt: CARDINAL; -- 1 + end of unsorted part of heap vector
  
  getProc: GetProcType;
  heap: DESCRIPTOR FOR ARRAY OF ItemHeaderHandle;
  heapSize: CARDINAL; -- end of heap-sorted part of heap vector
  
  inputFinished: BOOLEAN;
  itemIsLeftOver: BOOLEAN;
  leftoverItem: ItemHandle;
  leftoverItemLen: ItemLength;
  level: CARDINAL;
  maxHeapSize: CARDINAL;
  maxItemWords: CARDINAL;
  occItemWords: CARDINAL;
  putProc: PutProcType;
  recordSize: CARDINAL;
  
  RecordTooLong: PUBLIC ERROR = CODE;
  
  BuildHeap: PROCEDURE =
    BEGIN
    L: CARDINAL;
    heapSize ← 0;
    MaintainHeap[];
    heapSize ← firstFreeEnt - 1;
    L ← (heapSize/2) + 1;
    WHILE L > 1 DO L ← L - 1; SiftDown[L, heap[L]]; ENDLOOP;
    RETURN;
    END;
    
  BuildRuns: PROCEDURE =
    BEGIN
    --Continue reading and sorting, alternating in Fibonacci sequence, until the input is exhausted.
    A: CARDINAL;
    item: ItemHeaderHandle;
    i: CARDINAL;
    j: CARDINAL ← 1;
    LFile: FdHandle;
    NT: CARDINAL;
    level ← 1;
    DO 
      OPEN files[j];
      IF level > 1 THEN dh.put[dh, EOR]; -- end-of-run marker
      FOR item ← GetHeap[], GetHeap[] UNTIL item = NIL DO
	dh.put[dh, item.len];
	[] ← StreamDefs.WriteBlock[dh, @item.rec, item.len];
	occItemWords ←
	  occItemWords - item.len - SIZE[ItemLength] - FSPDefs.NodeOverhead;
	Free[item];
	ENDLOOP;
      dummyRuns ← dummyRuns - 1;
      IF inputFinished AND (firstFreeEnt = 1) THEN EXIT;
      IF dummyRuns < files[j + 1].dummyRuns THEN j ← j + 1
      ELSE
	BEGIN
	j ← 1;
	IF dummyRuns = 0 THEN
	  BEGIN
	  level ← level + 1;
	  A ← files[1].totalRuns;
	  FOR i IN [1..NFiles - 1] DO
	    LFile ← files[i];
	    NT ← A + files[i + 1].totalRuns;
	    LFile.dummyRuns ← NT - LFile.totalRuns;
	    LFile.totalRuns ← NT;
	    ENDLOOP;
	  END;
	END;
      BuildHeap[];
      ENDLOOP;
    FOR i IN [1..NFiles - 1] DO 
      OPEN files[i]; dh.put[dh, EOR]; dh.reset[dh]; ENDLOOP;
    RETURN;
    END;
    
  FreeAllocatedStuff: PROCEDURE =
    BEGIN
    i: CARDINAL;
    FOR i IN [1..NFiles] DO
      IF files[i].buffer # NIL THEN Free[files[i].buffer];
      IF files[i].record # NIL THEN Free[files[i].record];
      Free[files[i]];
      ENDLOOP;
    IF BASE[heap] # NIL THEN Free[BASE[heap]];
    IF leftoverItem # NIL THEN Free[leftoverItem];
    EraseHeap[];
    RETURN;
    END;
    
  GetHeap: PROCEDURE RETURNS [itemHP: ItemHeaderHandle] =
    BEGIN
    IF heapSize = 0 THEN RETURN[NIL];
    MaintainHeap[];
    itemHP ← heap[1];
    SiftDown[1, heap[heapSize]];
    firstFreeEnt ← firstFreeEnt - 1;
    heap[heapSize] ← heap[firstFreeEnt];
    heapSize ← heapSize - 1;
    RETURN;
    END;
    
  Initialize: PROCEDURE [res, expected, max: CARDINAL] =
    BEGIN
    MDSPages: CARDINAL = 400B;
    blockSize: INTEGER;
    heapPages, i: CARDINAL;
    res ← res + 92; -- 82 for mesa(include bitmap), 10 for me
    heapPages ← MIN[MDSPages - res, 100];
    blockSize ← heapPages*AltoDefs.PageSize;
    InitHeap[heapPages];
    FOR i IN [1..NFiles] DO
      files[i] ← Alloc[SIZE[Fd]];
      files[i].buffer ← NIL;
      files[i].record ← NIL;
      ENDLOOP;
    bufferSize ← (blockSize)/NFiles - 100;
    recordSize ← IF bufferSize > LOOPHOLE[max, INTEGER] THEN max ELSE bufferSize;
    maxHeapSize ← (blockSize - recordSize)/(expected + 3); -- this 3 is magic
    maxItemWords ← blockSize - maxHeapSize - recordSize;
    occItemWords ← 0;
    RETURN;
    END;
    
  MaintainHeap: PROCEDURE =
    BEGIN
    -- Fill the heap as full as possible
    itemHP: ItemHeaderHandle;
    IF inputFinished THEN RETURN;
    WHILE firstFreeEnt <= maxHeapSize DO
      -- Try adding another heap element
      IF NOT itemIsLeftOver THEN
	BEGIN
	leftoverItemLen ← getProc[leftoverItem];
	IF LOOPHOLE[leftoverItemLen, CARDINAL] > recordSize THEN
	  ERROR RecordTooLong;
	IF leftoverItemLen = 0 THEN BEGIN inputFinished ← TRUE; EXIT; END;
	END;
      IF occItemWords >= maxItemWords THEN BEGIN itemIsLeftOver ← TRUE; EXIT; END;
      itemHP ← Alloc[
	leftoverItemLen + SIZE[ItemLength] !
	FSPDefs.NoRoomInZone =>
	  BEGIN
	  maxItemWords ← occItemWords - 100;
	  itemIsLeftOver ← TRUE;
	  EXIT;
	  END];
      occItemWords ←
	occItemWords + leftoverItemLen + SIZE[ItemLength] + FSPDefs.NodeOverhead;
      itemHP.len ← leftoverItemLen;
      InlineDefs.COPY[leftoverItem, leftoverItemLen, @itemHP.rec];
      heap[firstFreeEnt] ← heap[heapSize + 1];
      firstFreeEnt ← firstFreeEnt + 1;
      heap[heapSize + 1] ← itemHP;
      itemIsLeftOver ← FALSE;
      IF heapSize > 0 AND compareProc[@itemHP.rec, @heap[1].rec] = GT THEN
	BEGIN heapSize ← heapSize + 1; SiftUp[]; END;
      ENDLOOP;
    RETURN;
    END;
    
  MergePass: PROCEDURE =
    BEGIN
    dummiesThisPass: CARDINAL;
    lastFile: FdHandle;
    OFile: FdHandle;
    runNo: CARDINAL;
    runsThisPass: CARDINAL;
    OFile ← files[NFiles];
    lastFile ← files[NFiles - 1];
    runsThisPass ← lastFile.totalRuns;
    dummiesThisPass ← lastFile.dummyRuns;
    -- FOR i IN[1..NFiles-2]
    dummiesThisPass ← MIN[dummiesThisPass, files[1].dummyRuns];
    OFile.totalRuns ← runsThisPass;
    OFile.dummyRuns ← dummiesThisPass;
    -- FOR i IN[1..NFiles-2]
    files[1].totalRuns ← files[1].totalRuns - runsThisPass;
    files[1].dummyRuns ← files[1].dummyRuns - dummiesThisPass;
    FOR runNo IN [dummiesThisPass + 1..runsThisPass] DO MergeRun[OFile]; ENDLOOP;
    IF level > 1 THEN
      BEGIN
      fd: FdHandle;
      i: CARDINAL;
      FlushBuffer[OFile];
      FOR i IN [NFiles - 1..NFiles] DO 
	OPEN files[i]; dh.reset[dh]; head ← 0; tail ← 0; ENDLOOP;
      fd ← files[NFiles];
      FOR i DECREASING IN (1..NFiles] DO files[i] ← files[i - 1]; ENDLOOP;
      files[1] ← fd;
      END;
    RETURN;
    END;
    
  MergeRun: PROCEDURE [OFile: FdHandle] =
    BEGIN
    -- Process a run.  Fill up the applicable records.
    i: CARDINAL;
    SR: CARDINAL;
    FOR i IN [1..NFiles - 1] DO 
      OPEN files[i];
      IF dummyRuns = 0 THEN [] ← ReadRecord[files[i]]
      ELSE BEGIN dummyRuns ← dummyRuns - 1; endOfRun ← TRUE; END;
      ENDLOOP;
    DO
      SR ← 0; -- selected record (which file is it from)
      FOR i IN [1..NFiles - 1] DO 
	OPEN files[i];
	IF ( NOT endOfRun) AND (SR = 0 OR compareProc[record, files[SR].record] =
	  LT) THEN SR ← i;
	ENDLOOP;
      IF SR = 0 THEN EXIT; -- come back and fix this
      IF level = 1 THEN putProc[files[SR].record, files[SR].len]
      ELSE WriteRecord[OFile, files[SR].len, files[SR].record];
      files[SR].record ← NIL; -- for cleanup guy
      [] ← ReadRecord[files[SR]];
      ENDLOOP;
    IF level > 1 THEN WriteRecord[OFile, -1, NIL]; -- end-of-run marker
    RETURN;
    END;
    
  ReadRecord: PROCEDURE [file: FdHandle] RETURNS [BOOLEAN] =
    BEGIN
    itemLen: ItemLength;
    headIndex: INTEGER;
    IF file.head = LOOPHOLE[file.tail, CARDINAL] THEN
      FillBuffer[file, bufferSize];
    headIndex ← file.head;
    itemLen ← file.buffer↑[headIndex];
    file.head ← headIndex ← headIndex + 1;
    IF itemLen < 0 THEN BEGIN file.endOfRun ← TRUE; RETURN[FALSE]; END;
    IF headIndex + itemLen > file.tail THEN FillBuffer[file, bufferSize];
    headIndex ← file.head;
    file.record ← @file.buffer↑[headIndex];
    file.head ← headIndex + itemLen;
    file.len ← itemLen;
    file.endOfRun ← FALSE;
    RETURN[TRUE];
    END;
    
  SiftDown: PROCEDURE [L: CARDINAL, K: ItemHeaderHandle] =
    BEGIN
    J: CARDINAL ← L;
    I: CARDINAL;
    DO
      I ← J;
      J ← J + J;
      IF J > heapSize THEN EXIT;
      IF J < heapSize THEN
	IF compareProc[@heap[J].rec, @heap[J + 1].rec] > 0 THEN J ← J + 1;
      IF compareProc[@K.rec, @heap[J].rec] <= 0 THEN EXIT;
      heap[I] ← heap[J];
      ENDLOOP;
    heap[I] ← K;
    RETURN;
    END;
    
  SiftUp: PROCEDURE =
    BEGIN
    i: CARDINAL;
    j: CARDINAL ← heapSize;
    k: ItemHeaderHandle ← heap[heapSize];
    i ← j/2;
    WHILE i > 0 DO
      IF compareProc[@heap[i].rec, @k.rec] <= 0 THEN EXIT;
      heap[j] ← heap[i];
      j ← i;
      i ← j/2;
      ENDLOOP;
    heap[j] ← k;
    RETURN;
    END;
    
  WriteRecord: PROCEDURE [
    file: FdHandle, itemLen: ItemLength, itemPtr: ItemHandle] =
    BEGIN
    buffer: ItemHandle ← file.buffer;
    tailIndex: INTEGER ← file.tail;
    IF tailIndex + (IF itemLen < 0 THEN 1 ELSE itemLen + 1) > bufferSize THEN
      BEGIN FlushBuffer[file]; tailIndex ← file.tail; END;
    buffer↑[tailIndex] ← itemLen;
    tailIndex ← tailIndex + 1;
    IF itemLen >= 0 THEN
      BEGIN
      InlineDefs.COPY[itemPtr, itemLen, @buffer↑[tailIndex]];
      tailIndex ← tailIndex + itemLen;
      END;
    file.tail ← tailIndex;
    RETURN;
    END;
    
  Sort: PUBLIC PROCEDURE [
    get: GetProcType, put: PutProcType, compare: CompareProcType,
    expectedItemSize: CARDINAL, maxItemSize: CARDINAL, reservedPages: CARDINAL] =
    BEGIN
    DefaultExpected: CARDINAL = 10; -- words
    DefaultMax: CARDINAL = 1000;
    DefaultReserved: CARDINAL = 10;
    item: ItemHeaderHandle;
    fid: STRING = "SORT.SCRATCH0";
    i: CARDINAL;
    lastChar: CARDINAL ← fid.length - 1;
    Initialize[
      IF reservedPages # 0 THEN reservedPages ELSE DefaultReserved,
      IF expectedItemSize # 0 THEN expectedItemSize ELSE DefaultExpected,
      IF maxItemSize # 0 THEN maxItemSize ELSE DefaultMax];
    getProc ← get;
    compareProc ← compare;
    putProc ← put;
    heap ← DESCRIPTOR[Alloc[maxHeapSize + 1], maxHeapSize + 1];
    firstFreeEnt ← 1;
    -- First, fill up the heap as much as possible and sort it.
    leftoverItem ← Alloc[recordSize];
    itemIsLeftOver ← FALSE;
    inputFinished ← FALSE;
    BuildHeap[];
    IF inputFinished THEN
      THROUGH [1..heapSize] DO item ← GetHeap[]; put[@item.rec, item.len]; ENDLOOP
    ELSE
      BEGIN
      FOR i IN [1..NFiles - 1] DO 
	OPEN StreamDefs, files[i];
	fid[lastChar] ← fid[lastChar] + 1;
	dh ← NewWordStream[fid, Append + Write + Read];
	totalRuns ← 1;
	dummyRuns ← 1;
	ENDLOOP;
      files[NFiles].totalRuns ← 0;
      files[NFiles].dummyRuns ← 0;
      BuildRuns[];
      -- Put runs on input files 1..NFiles-1 so that they have Fibonacci relationship
      Free[leftoverItem];
      leftoverItem ← NIL;
      Free[BASE[heap]];
      heap ← DESCRIPTOR[NIL, 0];
      IF level > 1 THEN
	BEGIN OPEN StreamDefs;
	fid[lastChar] ← fid[lastChar] + 1;
	files[NFiles].dh ← NewWordStream[fid, Append + Write + Read];
	END;
      FOR i IN [1..NFiles] DO 
	OPEN files[i]; buffer ← Alloc[bufferSize]; head ← 0; tail ← 0; ENDLOOP;
      -- Now carry out merge passes until the level has returned to zero.
      UNTIL level = 0 DO
	MergePass[];
	-- also cycles the files afterward if level>1
	level ← level - 1;
	IF level = 1 THEN DeleteFile[files[NFiles].dh];
	-- Output will go to the putItemParam routine
	
	ENDLOOP;
      FOR i IN [1..NFiles - 1] DO DeleteFile[files[i].dh]; ENDLOOP;
      END;
    FreeAllocatedStuff[];
    RETURN;
    END;
    
  
  END...