-- File: CountCommands.mesa
-- Last edited by Sandman on October 21, 1980  3:00 PM

DIRECTORY
  Ascii USING [CR, SP],
  CountDefs USING [
    condition, groups, loadmatrix, mode, modePos, module, mon, monitorOn,
    printmodule, printsorted, printtables, process, processPos, setProcess,
    sortClass, window, zero],
  CountPrivate USING [
    ControlRecord, GroupHandle, GroupIndex, MaxGroup, Mode, PCR, TableHandle,
    VersionID],
  DebugUsefulDefs USING [
    Frame, GFHandle, Name, ShortCopyREAD, ShortCopyWRITE, ShortREAD, ShortWRITE],
  Event USING [AddNotifier, Item, Masks, Notifier],
  Format USING [NumberFormat],
  FormSW USING [DisplayItem, EnumeratedNotifyProcType, NotifyProcType],
  Gf USING [GFI, Handle, Validate],
  Lookup USING [Fail],
  MachineDefs USING [GFHandle, GfiToFrame, GfiToOffset, NullGF],
  MsgSW USING [Clear, Post],
  PerfCommonOps USING [
    cmdSW, ConditionBreaks, logSW, msgSW, Number, WriteConvertedTicksToMs,
    WriteLongNumber, WritePercent, Zero],
  PerfStructures USING [IndexToHandle, NullPsbHandle, PsbIndex],
  Put USING [Blanks, Char, CR, Decimal, Line, Number, Text],
  SDDefs USING [sBreak, SD, sXferTrapMonitor],
  Selection USING [Convert, Number],
  Storage USING [FreeString, FreeWords, Words],
  Streams USING [Destroy, End, GetByte, Handle, NewStream],
  String USING [
    AppendChar, AppendOctal, AppendString, InvalidNumber, StringToNumber],
  Time USING [Append, Current, Unpack],
  ToolWindow USING [GetName],
  UserInput USING [userAbort];

CountCommands: PROGRAM
  IMPORTS
    CountDefs, DebugUsefulDefs, Event, FormSW, Gf, Lookup, MachineDefs, MsgSW,
    PerfCommonOps, Put, Selection, Streams, String, Storage, Time, ToolWindow,
    UserInput, PerfStructures
  EXPORTS CountDefs =

  PUBLIC

  BEGIN OPEN CountDefs, CountPrivate, DebugUsefulDefs, PerfCommonOps;

  cr: ControlRecord;
  counts, times: TableHandle;
  groups: PRIVATE GroupHandle;
  counterOn: BOOLEAN ← FALSE;
  haveTable, dirtyTable: PACKED ARRAY {counts, times, cr, groups} OF BOOLEAN ←
    [FALSE, FALSE, FALSE, FALSE];

  GetCounts: PROCEDURE [mode: {read, write}] RETURNS [TableHandle] =
    BEGIN OPEN DebugUsefulDefs;
    IF ~haveTable[counts] THEN
      BEGIN
      length: CARDINAL;
      [] ← GetCR[read];
      length ← cr.length*SIZE[LONG CARDINAL];
      counts ← Storage.Words[length];
      ShortCopyREAD[to: counts, from: cr.counts, nwords: length];
      haveTable[counts] ← TRUE;
      END;
    dirtyTable[counts] ← dirtyTable[counts] OR mode = write;
    RETURN[counts];
    END;

  GetTimes: PROCEDURE [mode: {read, write}] RETURNS [TableHandle] =
    BEGIN OPEN DebugUsefulDefs;
    IF ~haveTable[times] THEN
      BEGIN
      length: CARDINAL;
      [] ← GetCR[read];
      length ← cr.length*SIZE[LONG CARDINAL];
      times ← Storage.Words[length];
      ShortCopyREAD[to: times, from: cr.times, nwords: length];
      haveTable[times] ← TRUE;
      END;
    dirtyTable[times] ← dirtyTable[times] OR mode = write;
    RETURN[times];
    END;

  GetCR: PROCEDURE [mode: {read, write}] RETURNS [POINTER TO ControlRecord] =
    BEGIN OPEN DebugUsefulDefs;
    IF ~haveTable[cr] THEN
      BEGIN
      rec: PCR ← ShortREAD[@SDDefs.SD[SDDefs.sXferTrapMonitor]];
      ShortCopyREAD[to: @cr, from: rec, nwords: SIZE[ControlRecord]];
      haveTable[cr] ← TRUE;
      END;
    dirtyTable[cr] ← dirtyTable[cr] OR mode = write;
    RETURN[@cr];
    END;

  GetGroups: PROCEDURE [mode: {read, write}] RETURNS [GroupHandle] =
    BEGIN OPEN DebugUsefulDefs;
    IF ~haveTable[groups] THEN
      BEGIN
      length: CARDINAL;
      [] ← GetCR[read];
      length ← cr.length*SIZE[GroupIndex];
      groups ← Storage.Words[length];
      ShortCopyREAD[to: groups, from: cr.groups, nwords: length];
      haveTable[groups] ← TRUE;
      END;
    dirtyTable[groups] ← dirtyTable[groups] OR mode = write;
    RETURN[groups];
    END;

  TablesExist: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN
    RETURN[DebugUsefulDefs.ShortREAD[@SDDefs.SD[SDDefs.sXferTrapMonitor]] # NIL];
    END;

  TablesCorrectVersion: PROCEDURE RETURNS [BOOLEAN] =
    BEGIN RETURN[GetCR[read].version = VersionID]; END;

  PutTables: PUBLIC PROCEDURE [flush: BOOLEAN] =
    BEGIN OPEN DebugUsefulDefs;
    rec: PCR ← ShortREAD[@SDDefs.SD[SDDefs.sXferTrapMonitor]];
    length: CARDINAL = cr.length*SIZE[LONG CARDINAL];
    IF flush AND haveTable[cr] AND dirtyTable[cr] THEN
      ShortCopyWRITE[to: rec, from: @cr, nwords: SIZE[ControlRecord]];
    IF haveTable[counts] THEN
      BEGIN
      IF flush AND dirtyTable[counts] THEN
	ShortCopyWRITE[from: counts, to: cr.counts, nwords: length];
      Storage.FreeWords[counts];
      END;
    IF haveTable[times] THEN
      BEGIN
      IF flush AND dirtyTable[times] THEN
	ShortCopyWRITE[from: times, to: cr.times, nwords: length];
      Storage.FreeWords[times];
      END;
    IF haveTable[groups] THEN
      BEGIN
      IF flush AND dirtyTable[groups] THEN
	ShortCopyWRITE[from: groups, to: cr.groups, nwords: cr.length*SIZE[GroupIndex]];
      Storage.FreeWords[groups];
      END;
    haveTable ← dirtyTable ← [FALSE, FALSE, FALSE, FALSE];
    END;

  TurnOn: PROCEDURE =
    BEGIN OPEN SDDefs;
    cr.saveBreakHandler ← DebugUsefulDefs.ShortREAD[@SD[sBreak]];
    DebugUsefulDefs.ShortWRITE[@SD[sBreak], cr.self];
    END;

  TurnOff: PROCEDURE RETURNS [BOOLEAN] =
    -- this procedure turns off the measurements
    BEGIN OPEN SDDefs;
    IF cr.saveBreakHandler # NIL AND cr.self = DebugUsefulDefs.ShortREAD[
      @SD[sBreak]] THEN
      BEGIN
      DebugUsefulDefs.ShortWRITE[@SD[sBreak], cr.saveBreakHandler];
      RETURN[TRUE]
      END;
    RETURN[FALSE];
    END;

  ZeroCounts: PROCEDURE =
    BEGIN
    table: TableHandle ← GetCounts[write];
    length: CARDINAL = GetCR[read].length*SIZE[LONG CARDINAL];
    PerfCommonOps.Zero[table, length];
    table ← GetTimes[write];
    PerfCommonOps.Zero[table, length];
    GetCR[write].gfi ← 0;
    Put.Line[logSW, "Tables zeroed"L];
    GetCR[write].newMeasurement ← TRUE;
    RETURN
    END;

  SetProcess: PROCEDURE =
    BEGIN OPEN PerfStructures;
    pCR: PCR ← GetCR[write];
    index: PsbIndex;
    IF process = NIL OR process.length = 0 THEN
      BEGIN pCR.process ← NullPsbHandle; Put.Line[logSW, "All processes now tracked"L]; END
    ELSE
      BEGIN
      index ← String.StringToNumber[
	process, 8 ! String.InvalidNumber => GOTO badProcess];
      Put.Text[logSW, "Track process: "L];
      Put.Line[logSW, process];
      pCR.process ← IndexToHandle[index];
      EXITS badProcess => PostError[badProcess];
      END;
    END;

  PrintTables: PROCEDURE =
    BEGIN
    sort: Sorting ← GetSorting[];
    PrintSorted[sort ! UNWIND => ReleaseSorting[sort]];
    ReleaseSorting[sort];
    END;

  DisplaySorted: PROCEDURE =
    BEGIN
    sort: Sorting;
    IF mode = matrix THEN {PostError[badMode]; RETURN};
    sort ← GetSorting[];
    SortTable[
      table: IF sortClass = count THEN GetCounts[read] ELSE GetTimes[read],
      sorting: sort];
    PrintSorted[sort ! UNWIND => ReleaseSorting[sort]];
    ReleaseSorting[sort];
    END;

  DisplayModule: PROCEDURE =
    BEGIN
    gfi: CARDINAL;
    IF mode = matrix THEN {PostError[badMode]; RETURN};
    IF module = NIL OR module.length = 0 THEN
      BEGIN PostError[badModule]; RETURN END;
    IF module[0] IN ['0..'9] THEN gfi ← String.StringToNumber[module, 8]
    ELSE gfi ← LOOPHOLE[DebugUsefulDefs.Frame[module]];
    IF gfi >= GetCR[read].length THEN gfi ← Gf.GFI[LOOPHOLE[gfi]];
    Put.CR[logSW];
    DisplayLine[
      gfi, GetCount[gfi], TotalCounts[plain], GetTime[gfi], TotalTimes[plain]];
    Put.CR[logSW];
    RETURN
    END;

  PrintSorted: PROCEDURE [sort: Sorting] =
    BEGIN
    i, gfi: CARDINAL;
    count, time, totalCounts, totalTime: Number;
    [totalCounts, totalTime] ← PrintHeader[];
    FOR i IN [0..GetCR[read].length) DO
      IF (gfi ← sort[i]) = 0 THEN LOOP;
      IF MachineDefs.GfiToOffset[gfi] # 0 THEN LOOP;
      count ← GetCount[gfi];
      IF count = 0 THEN LOOP;
      Put.CR[logSW];
      time ← GetTime[gfi];
      DisplayLine[gfi, count, totalCounts, time, totalTime];
      ENDLOOP;
    Put.CR[logSW];
    PrintTrailer[plain];
    Put.CR[logSW];
    END;

  DisplayLine: PROCEDURE [
    gfi: CARDINAL, count, totalCounts, time, totalTime: Number] =
    BEGIN
    f: Format.NumberFormat =
      [base: 8, unsigned: TRUE, zerofill: FALSE, columns: 7];
    g: Format.NumberFormat =
      [base: 8, unsigned: TRUE, zerofill: FALSE, columns: 3];
    module: STRING ← [40];
    frame: MachineDefs.GFHandle;
    IF UserInput.userAbort THEN SIGNAL Aborted;
    frame ← MachineDefs.GfiToFrame[gfi];
    CheckFrame[frame ! Aborted => GOTO bailout];
    Put.Number[logSW, gfi, g];
    Put.Char[logSW, 'B];
    Put.Number[logSW, frame, f];
    Put.Text[logSW, "B "L];
    WriteFrameName[frame, 18];
    WriteLongNumber[count, 12];
    WritePercent[count, totalCounts, 8];
    WriteConvertedTicksToMs[time, GetCR[read].pulseConversion, 12];
    WritePercent[time, totalTime, 7];
    RETURN;
    EXITS bailout => RETURN
    END;

  CheckFrame: PROCEDURE [frame: MachineDefs.GFHandle] =
    BEGIN
    s: STRING ← [24];
    IF frame # NIL AND Gf.Validate[frame] THEN RETURN;
    String.AppendString[s, "! Invalid frame "L];
    String.AppendOctal[s, frame];
    MsgSW.Post[sw: msgSW, string: s, endOfMsg: TRUE];
    Put.Text[logSW, s];
    SIGNAL Aborted;
    RETURN
    END;

  Aborted: SIGNAL = CODE;

  PrintTotals: PROCEDURE [mode: Mode] RETURNS [totalCounts, totalTime: Number] =
    BEGIN
    Put.CR[logSW];
    totalCounts ← TotalCounts[mode];
    totalTime ← TotalTimes[mode];
    Put.Text[logSW, "Total Xfers"L];
    WriteLongNumber[totalCounts, 14];
    Put.CR[logSW];
    Put.Text[logSW, "Total Time "L];
    WriteConvertedTicksToMs[totalTime, GetCR[read].pulseConversion, 14];
    END;

  PrintHeader: PROCEDURE RETURNS [totalCounts, totalTime: Number] =
    BEGIN
    [totalCounts: totalCounts, totalTime: totalTime] ← PrintTotals[plain];
    Put.Text[
      logSW,
      "
Gfi Frame   Module                  #Xfers  %Xfers        Time  %Time
---- ------- ------------------ ----------- ------- ----------- ------"L];
    END;

  PrintTrailer: PROCEDURE [mode: Mode] =
    BEGIN
    Put.Text[logSW, "Ignored Xfers"L];
    WriteLongNumber[Ignored[GetCounts[read], mode], 14];
    Put.CR[logSW];
    Put.Text[logSW, "Ignored Time "L];
    WriteConvertedTicksToMs[
      Ignored[GetTimes[read], mode], GetCR[read].pulseConversion, 14];
    END;

  WriteFrameName: PROCEDURE [frame: MachineDefs.GFHandle, columns: CARDINAL ← 0] =
    BEGIN
    module: STRING ← [40];
    DebugUsefulDefs.Name[gf: frame, name: module];
    PrintString[module, columns];
    RETURN;
    END;

  TotalCounts: PROCEDURE [mode: CountPrivate.Mode] RETURNS [Number] =
    BEGIN RETURN[Total[GetCounts[read], mode]] END;

  TotalTimes: PROCEDURE [mode: CountPrivate.Mode] RETURNS [Number] =
    BEGIN RETURN[Total[GetTimes[read], mode]] END;

  Total: PROCEDURE [table: TableHandle, mode: CountPrivate.Mode]
    RETURNS [total: Number] =
    BEGIN
    to, from, i: CARDINAL;
    total ← 0;
    IF mode = plain THEN
      FOR i IN [1..GetCR[read].length) DO total ← total + table.plain[i]; ENDLOOP
    ELSE
      FOR to IN [1..MaxGroup] DO
	FOR from IN [1..MaxGroup] DO
	  total ← total + table.matrix[to][from]; ENDLOOP;
	ENDLOOP;
    RETURN
    END;

  Ignored: PROCEDURE [table: TableHandle, mode: CountPrivate.Mode]
    RETURNS [total: Number] =
    BEGIN
    to, from: CARDINAL;
    total ← 0;
    IF mode = plain THEN RETURN[table.plain[0]]
    ELSE {
      FOR to IN [0..MaxGroup] DO total ← total + table.matrix[to][0]; ENDLOOP;
      FOR from IN [1..MaxGroup] DO total ← total + table.matrix[0][from]; ENDLOOP};
    RETURN
    END;

  GetCount: PROCEDURE [gfi: CARDINAL] RETURNS [Number] =
    BEGIN RETURN[GetValue[gfi, GetCounts[read]]] END;

  GetTime: PROCEDURE [gfi: CARDINAL] RETURNS [Number] =
    BEGIN RETURN[GetValue[gfi, GetTimes[read]]] END;

  GetValue: PROCEDURE [gfi: CARDINAL, table: TableHandle]
    RETURNS [count: Number] =
    BEGIN
    count ← 0;
    DO
      count ← count + table.plain[gfi];
      gfi ← gfi + 1;
      IF MachineDefs.GfiToOffset[gfi] = 0 OR GetCR[read].length = gfi THEN EXIT;
      ENDLOOP;
    RETURN
    END;

  SortTable: PROCEDURE [table: TableHandle, sorting: Sorting] =
    BEGIN
    SiftUp: PROCEDURE [low, high: CARDINAL] =
      BEGIN
      t: CARDINAL;
      k, son: CARDINAL;
      k ← low;
      DO
	IF k*2 > high THEN EXIT;
	IF k*2 + 1 > high OR table.plain[sorting[k*2 + 1 - 1]] > table.plain[
	  sorting[k*2 - 1]] THEN son ← k*2
	ELSE son ← k*2 + 1;
	IF table.plain[sorting[son - 1]] > table.plain[sorting[k - 1]] THEN EXIT;
	t ← sorting[son - 1];
	sorting[son - 1] ← sorting[k - 1];
	sorting[k - 1] ← t;
	k ← son;
	ENDLOOP;
      RETURN
      END;
    t, i: CARDINAL;
    length: CARDINAL = GetCR[read].length;
    NormalizeTable[table];
    FOR i DECREASING IN [1..length/2] DO SiftUp[i, length] ENDLOOP;
    FOR i DECREASING IN [1..length) DO
      t ← sorting[1 - 1];
      sorting[1 - 1] ← sorting[i + 1 - 1];
      sorting[i + 1 - 1] ← t;
      SiftUp[1, i];
      ENDLOOP;
    RETURN
    END;

  NormalizeTable: PROCEDURE [table: TableHandle] =
    BEGIN
    count: LONG CARDINAL ← 0;
    gfi: CARDINAL;
    length: CARDINAL = GetCR[read].length;
    FOR gfi DECREASING IN [0..length) DO
      count ← count + table.plain[gfi];
      IF MachineDefs.GfiToOffset[gfi] = 0 THEN
	BEGIN table.plain[gfi] ← count; count ← 0; END
      ELSE table.plain[gfi] ← 0;
      ENDLOOP;
    RETURN
    END;

  GetSorting: PROCEDURE RETURNS [sorting: Sorting] =
    BEGIN
    i: CARDINAL;
    length: CARDINAL = GetCR[read].length;
    sorting ← Storage.Words[length*SIZE[LONG CARDINAL]];
    FOR i IN [0..length) DO sorting[i] ← i; ENDLOOP;
    RETURN
    END;

  ReleaseSorting: PROCEDURE [sorting: Sorting] =
    BEGIN Storage.FreeWords[sorting]; RETURN END;

  Sorting: TYPE = POINTER TO ARRAY [0..0) OF CARDINAL;

  PrintString: PROCEDURE [s: STRING, columns: CARDINAL] =
    BEGIN
    Put.Text[logSW, s];
    IF columns > s.length THEN Put.Blanks[logSW, columns - s.length];
    RETURN
    END;

  PrintMatrix: PROCEDURE =
    BEGIN
    g: Format.NumberFormat =
      [base: 10, unsigned: TRUE, zerofill: FALSE, columns: 3];
    to, from: GroupIndex;
    count, time, totalCounts, totalTime: Number;
    [totalCounts, totalTime] ← PrintTotals[matrix];
    Put.Text[
      logSW,
      "
From -> To          #Xfers   %Xfers           Time   %Time
----   ---     -----------  -------    -----------  ------"L];
    Put.CR[logSW];
    FOR from IN [1..MaxGroup] DO
      FOR to IN [1..MaxGroup] DO
	count ← counts.matrix[to][from];
	time ← times.matrix[to][from];
	IF count = 0 AND time = 0 THEN LOOP;
	Put.Char[logSW, Ascii.SP];
	Put.Number[logSW, from, g];
	Put.Text[logSW, " ->"L];
	Put.Number[logSW, to, g];
	WriteLongNumber[count, 16];
	WritePercent[count, totalCounts, 9];
	WriteConvertedTicksToMs[time, GetCR[read].pulseConversion, 15];
	WritePercent[time, totalTime, 8];
	Put.CR[logSW];
	IF UserInput.userAbort THEN SIGNAL Aborted;
	ENDLOOP;
      ENDLOOP;
    Put.CR[logSW];
    PrintTrailer[matrix];
    Put.CR[logSW];
    RETURN
    END;

  LoadMatrix: PROCEDURE =
    BEGIN
    groups: GroupHandle ← GetGroups[write];
    gfi: CARDINAL;
    group: GroupIndex;
    stream: Streams.Handle ← GetFile[];
    PerfCommonOps.Zero[groups, GetCR[read].length];
    IF stream = NIL THEN RETURN;
    prevGroup ← 0;
    DO
      ENABLE Streams.End[] => EXIT;
      [gfi: gfi, group: group] ← GetNextPair[
	stream ! BadFile => {PostError[badFile]; EXIT}];
      DO
	groups[gfi] ← group;
	gfi ← gfi + 1;
	IF gfi >= cr.length OR MachineDefs.GfiToOffset[gfi] = 0 THEN EXIT;
	ENDLOOP;
      ENDLOOP;
    Streams.Destroy[stream];
    Put.Line[logSW, "Matrix loaded"L];
    RETURN
    END;

  GetFile: PROCEDURE RETURNS [stream: Streams.Handle] =
    BEGIN
    name: STRING ← Selection.Convert[string];
    stream ← NIL;
    IF name # NIL THEN stream ← Streams.NewStream[name ! ANY => CONTINUE];
    IF stream = NIL THEN {
      Storage.FreeString[name]; PostError[noFile]; RETURN[NIL]};
    Storage.FreeString[name];
    RETURN
    END;

  BadFile: SIGNAL = CODE;

  prevGroup: GroupIndex;

  GetNextPair: PROCEDURE [stream: Streams.Handle]
    RETURNS [gfi: CARDINAL, group: GroupIndex] =
    BEGIN OPEN String;
    c: CHARACTER;
    s: STRING ← [60];
    DO
      c ← GetToken[stream, s];
      IF c = ': AND s.length = 3 AND s[0] = 'g AND s[1] = 'f AND s[2] = 'i THEN {
	c ← GetToken[stream, s];
	gfi ← StringToNumber[s, 10 ! InvalidNumber => GOTO badNumber];
	SELECT c FROM
	  Ascii.CR => {group ← prevGroup; RETURN};
	  Ascii.SP => {
	    c ← GetToken[stream, s];
	    prevGroup ← group ← StringToNumber[
	      s, 10 ! InvalidNumber => GOTO badNumber];
	    RETURN};
	  ENDCASE => SIGNAL BadFile};
      ENDLOOP;
    EXITS badNumber => SIGNAL BadFile;
    END;

  GetToken: PROC [stream: Streams.Handle, s: STRING] RETURNS [c: CHARACTER] = {
    s.length ← 0;
    DO
      SELECT c ← Streams.GetByte[stream] FROM
	Ascii.SP, Ascii.CR => IF s.length # 0 THEN RETURN ELSE LOOP;
	',, ': => RETURN;
	ENDCASE => String.AppendChar[s, c];
      ENDLOOP};

  ShowGroups: PROCEDURE =
    BEGIN
    group: CARDINAL ← 177777B;
    gfi: CARDINAL;
    group ← Selection.Number[10 ! String.InvalidNumber => CONTINUE];
    IF group NOT IN GroupIndex THEN GOTO noGood;
    Put.Text[logSW, "Modules for group "L];
    Put.Decimal[logSW, group];
    Put.Char[logSW, ':];
    Put.CR[logSW];
    FOR gfi IN [1..GetCR[read].length) DO
      IF group = GetGroups[read][gfi] THEN
	BEGIN
	name: STRING ← [40];
	frame: MachineDefs.GFHandle ← MachineDefs.GfiToFrame[gfi];
	IF frame = MachineDefs.NullGF THEN LOOP;
	CheckFrame[frame ! Aborted => GOTO noGood];
	WriteFrameName[frame];
	Put.Text[logSW, name];
	Put.Char[logSW, Ascii.SP];
	END;
      IF UserInput.userAbort THEN SIGNAL Aborted;
      ENDLOOP;
    Put.CR[logSW];
    Put.CR[logSW];
    EXITS noGood => PostError[badGroup];
    END;


  Error: TYPE = {
    notOn, goofUp, version, noContext, badModule, badProcess, noFile, badFile,
    badMode, badGroup};

  PostError: PROCEDURE [error: Error] =
    BEGIN
    MsgSW.Post[
      sw: msgSW,
      string:
      SELECT error FROM
	notOn => "!Please Start CountTool and then try again"L,
	goofUp =>
	  "!Goofed up CountTool by use of Worry on/off during measurements"L,
	version => "!CountTool has Incorrect Version"L,
	noContext => "!No Context"L,
	badModule => "!No Module specified"L,
	badProcess => "!Invalid Process specified"L,
	noFile => "!Can't find input file"L,
	badFile => "!Bad matrix input file"L,
	badMode => "!Can't do in matrix mode"L,
	badGroup => "!Bad group specified"L,
	ENDCASE => "?"L];
    END;

  PutHerald: PROCEDURE =
    BEGIN OPEN Time;
    s: STRING ← [22];
    name: STRING ← ToolWindow.GetName[window];
    Put.Line[logSW, name];
    Append[s, Unpack[Current[]]];
    s.length ← s.length - 3;
    Put.Line[logSW, s];
    Put.CR[logSW];
    Storage.FreeString[name];
    RETURN
    END;

  SetDefaults: PROCEDURE [index: CARDINAL] =
    BEGIN
    pcr: PCR ← GetCR[write];
    MakeSureOff[index];
    IF index # setProcess THEN {
      IF process # NIL THEN process.length ← 0;
      [] ← FormSW.DisplayItem[cmdSW, processPos]};
    IF index # modePos THEN {
      mode ← plain; [] ← FormSW.DisplayItem[cmdSW, modePos]};
    pcr.newSession ← FALSE;
    PutHerald[];
    RETURN
    END;

  MakeSureOff: PROCEDURE [index: CARDINAL] = {
    IF index # mon THEN {monitorOn ← FALSE; [] ← FormSW.DisplayItem[cmdSW, mon]};
    RETURN};

  EnumerateNotify: FormSW.EnumeratedNotifyProcType = {
    ParamNotify[sw: sw, item: item, index: index]};

  ParamNotify: FormSW.NotifyProcType =
    BEGIN
    MsgSW.Clear[msgSW];
    IF ~TablesExist[] THEN GOTO noTables;
    IF ~TablesCorrectVersion[] THEN GOTO badVersion;
    IF GetCR[read].newSession THEN SetDefaults[index];
    SELECT index FROM
      mon => IF monitorOn THEN TurnOn[] ELSE IF ~TurnOff[] THEN PostError[goofUp];
      zero => ZeroCounts[];
      condition => ConditionBreaks[];
      printtables =>
	IF mode = plain THEN PrintTables[
	  ! Aborted => {Put.Line[logSW, "... aborted!"L]; CONTINUE}]
	ELSE PrintMatrix[ ! Aborted => {Put.Line[logSW, "... aborted!"L]; CONTINUE}];
      printsorted =>
	DisplaySorted[ ! Aborted => {Put.Line[logSW, "... aborted!"L]; CONTINUE}];
      printmodule =>
	DisplayModule[ !
	  Lookup.Fail => BEGIN CantFind[s]; CONTINUE END;
	  Aborted => {Put.Line[logSW, "... aborted!"L]; CONTINUE}];
      setProcess => SetProcess[];
      modePos => GetCR[write].mode ← mode;
      loadmatrix => LoadMatrix[];
      CountDefs.groups =>
	ShowGroups[ ! Aborted => {Put.Line[logSW, "... aborted!"L]; CONTINUE}];
      ENDCASE;
    EXITS
      noTables => {PostError[notOn]; MakeSureOff[index]};
      badVersion => {PostError[version]; MakeSureOff[index]}
    END;

  CantFind: PROCEDURE [name: STRING] =
    BEGIN
    MsgSW.Post[sw: msgSW, string: "!"L, endOfMsg: FALSE];
    MsgSW.Post[sw: msgSW, string: name];
    RETURN
    END;

  Cleanup: Event.Notifier =
    BEGIN
    PutTables[why = resumeDebuggee];
    SELECT why FROM
      newSession, abortSession =>
	BEGIN monitorOn ← counterOn ← FALSE; FormSW.DisplayItem[cmdSW, mon]; END;
      ENDCASE;
    RETURN
    END;

  -- Mainline code

  cleanupItem: Event.Item ←
    [eventMask:
    Event.Masks[abortSession] + Event.Masks[newSession] + Event.Masks[
      resumeDebuggee], eventProc: Cleanup];

  Event.AddNotifier[@cleanupItem];

  END.