-- BreakPoint.Mesa, edited by Bruce  October 25, 1980  7:59 PM

DIRECTORY
  BP USING [BBHandle, BytePC, Error, FreeUserBB, GFHandle],
  DebugFormat USING [BBHandle, BreakBlock, BreakType, EXOI, Foo],
  DebugOps USING [
    Foo, GFHandle, Numeric, ReadCodeByte, StringExpToDecimal, StringExpToOctal,
    WriteCodeByte, CommandNotAllowed],
  DI USING [DerefProcDesc, Foo, GetControlLink, NotAProcedure],
  DOutput USING [Char, Decimal, EOL, Line, Octal, Text],
  Drum USING [Free],
  DSyms USING [GFHandle, GFrameMdi, NoFGT],
  Dump USING [HashVal],
  Gf USING [DisplayInMsg, Frame, FrameGfi, GFI, Original],
  DHeap USING [Zero],
  Lookup USING [Fail, HTIndex, OnStack, StringToHti],
  MachineDefs USING [BYTE, GFHandle, UserBreakBlock],
  Mopcodes USING [op, zBRK, zLINKB, zNOOP, zPORTI, zRET],
  Pc USING [BytePC, CBti, EntryPC, EpToCBti, EVRange, EVSize, ExitPC, LinkToCbti],
  PrincOps USING [BytePC, ControlLink],
  Source USING [BytePC, Display, FileMissing],
  Storage USING [CopyString, Free, FreeString, Node],
  String USING [AppendString],
  Symbols USING [bodyType, CBTIndex, CBTNull, HTIndex, MDIndex],
  SymbolSegment USING [bodyType],
  SymbolTable USING [Missing],
  Table USING [Base, Bounds];
  
BreakPoint: PROGRAM
  IMPORTS BP, DebugOps, DI, DOutput, Drum, DSyms, Dump, Gf, DHeap, Lookup,
    Pc, Source, Storage, String, SymbolTable, Table
  EXPORTS BP =
  BEGIN OPEN BP;
  
-- a collection of procedures to set and clear breakpoints

  BreakType: TYPE = DebugFormat.BreakType;
  BreakBlock: TYPE = DebugFormat.BreakBlock;
  UserBreakBlock: TYPE = MachineDefs.UserBreakBlock;
  
  Fail: PUBLIC SIGNAL [type: Error] = CODE;
  AlreadyFree: ERROR = CODE;
  
  SetBlocks: BBHandle ← NIL;
  number: CARDINAL;
  
  Alloc: PUBLIC PROCEDURE RETURNS [bb: BBHandle] = 
    BEGIN
    last: BBHandle ← NIL;
    bb ← Storage.Node[SIZE[BreakBlock]];
    DHeap.Zero[bb, SIZE[BreakBlock]];
    bb.num ← number ← number + 1;
    FOR p: BBHandle ← SetBlocks, p.link UNTIL p = NIL DO last ← p; ENDLOOP;
    IF last # NIL THEN last.link ← bb ELSE SetBlocks ← bb;  
    END;
    
  Free: PUBLIC PROC [bb: BBHandle] =
    BEGIN
    last: BBHandle ← NIL;
    Find: PROC [b: BBHandle] RETURNS [BOOLEAN] = {
      IF b = bb THEN RETURN[TRUE];
      last ← b;
      RETURN[FALSE]};
    IF EnumerateBBs[Find] = NIL THEN ERROR AlreadyFree;
    IF last # NIL THEN last.link ← bb.link ELSE SetBlocks ← bb.link;  -- take out of chain
    FreeOne[bb, FALSE];
    END;
    
  FreeOne: PROC [bb: BBHandle, newSession: BOOLEAN] =
    BEGIN
    IF bb.condition # NIL THEN {
      Storage.FreeString[bb.condition];
      IF ~newSession THEN FreeUserBB[bb.gf, bb.pc]};
    IF bb.exp # NIL THEN Storage.FreeString[bb.exp];
    Storage.Free[bb];
    END;
    
  ResetBBs: PUBLIC PROC =
    BEGIN
    bb, next: BBHandle;
    FOR bb ← SetBlocks, next UNTIL bb = NIL DO
      next ← bb.link; FreeOne[bb, TRUE];
      ENDLOOP;
    SetBlocks ← NIL;
    number ← 0;
    END;
    
  EnumerateBBs: PUBLIC PROC [
    proc: PROC [BBHandle] RETURNS [BOOLEAN], gf: GFHandle ← NIL]
    RETURNS [bb: BBHandle] =
    BEGIN
    FOR bb ← SetBlocks, bb.link UNTIL bb = NIL DO
      IF gf # NIL AND bb.gf # gf THEN LOOP;
      IF proc[bb] THEN RETURN;
      ENDLOOP;
    END;
    
  DeleteBB: PUBLIC PROC [bb: BBHandle] RETURNS [last: BOOLEAN] =
    BEGIN
    Find: PROC [break: BBHandle] RETURNS [BOOLEAN] = 
      {IF bb = break THEN missing ← FALSE; cnt ← cnt + 1; RETURN[FALSE]};
    cnt: CARDINAL ← 0;
    missing: BOOLEAN ← TRUE;
    [] ← EnumerateBBs[Find,bb.gf];
    IF missing THEN WriteError[notFound];
    Free[bb];
    RETURN[cnt=1]
    END;
    
  FindBB: PUBLIC PROC [gf: GFHandle, pc: BytePC] RETURNS [bb: BBHandle] =
    BEGIN
    FindOne: PROC [b: BBHandle] RETURNS [BOOLEAN] = {RETURN[b.pc = pc]};
    bb ← EnumerateBBs[FindOne, Gf.Original[gf]];
    END;
    
  FindBBNum: PUBLIC PROC [c: CARDINAL] RETURNS [bb: BBHandle] =
    BEGIN
    FindOne: PROC [b: BBHandle] RETURNS [BOOLEAN] = {RETURN[b.num = c]};
    bb ← EnumerateBBs[FindOne];
    END;
    
  Insert: PUBLIC PROC [
    gf: GFHandle, pc: BytePC, bt: DebugFormat.BreakType, haveSyms: BOOLEAN]
    RETURNS [bb: BBHandle] =
    BEGIN   --set breakpoint on the specified statement
    op: MachineDefs.BYTE;
    pass: {one, two} ← one;
    bb ← FindBB[gf, pc];
    IF bb # NIL THEN 
      BEGIN
      IF bb.bt.bt = bt.bt THEN ERROR Fail[alreadySet];
      bb.bt.bt ← bt.bt;
      RETURN;
      END;
    gf ← Gf.Original[gf];
    DO
      SELECT (op ← DebugOps.ReadCodeByte[gf,pc]) FROM
	Mopcodes.zPORTI => ERROR Fail[portI];
	Mopcodes.zLINKB => IF pass = one THEN pc ← [pc + 2]; 
	Mopcodes.zNOOP => IF pass = one THEN pc ← [pc + 1]; 
	ENDCASE =>
	  IF bt.ex # exit OR op = Mopcodes.zRET OR op = Mopcodes.zBRK THEN
	    EXIT
	  ELSE ERROR Fail[noReturn];
      IF pass = two THEN ERROR Fail[notExchangable];
      pass ← two;
      ENDLOOP;
    DebugOps.WriteCodeByte[gf, pc, Mopcodes.zBRK !
      DebugOps.CommandNotAllowed => ERROR Fail[spare2]];
    bb ← Alloc[];  -- comes back zeroed, except for num and link
    bb.gf ← gf;
    bb.pc ← pc;
    bb.inst ← op;
    bb.bt ← bt;
    bb.symbolsAvailable ← haveSyms;
    END;
    
  Remove: PUBLIC PROCEDURE [gf: GFHandle, pc: BytePC] =
    BEGIN   --remove breakpoint on the specified statement
    p: BBHandle;
    op: MachineDefs.BYTE;
    pass: {one, two} ← one;
    DO
      SELECT (op ← DebugOps.ReadCodeByte[gf,pc]) FROM
	Mopcodes.zLINKB => IF pass = one THEN pc ← [pc + 2]; 
	Mopcodes.zNOOP => IF pass = one THEN pc ← [pc + 1]; 
	Mopcodes.zBRK => EXIT;
	ENDCASE => ERROR Fail[notFound];
      IF pass = two THEN ERROR Fail[notFound];
      pass ← two;
      ENDLOOP;
    gf ← Gf.Original[gf];
    IF (p ← FindBB[gf,pc]) = NIL THEN ERROR Fail[notFound];
    op ← p.inst;
    IF DeleteBB[p] THEN Drum.Free[gf];
    DebugOps.WriteCodeByte[gf,pc,op];
    RETURN
    END;
    
  GetCbti: PROC [s: STRING]
      RETURNS [cbti: Symbols.CBTIndex, cl: PrincOps.ControlLink] =
    BEGIN
    hti: Symbols.HTIndex = Lookup.StringToHti[s];
    f: DebugOps.Foo ← Lookup.OnStack[id: hti, check: FALSE];
    IF f = NIL THEN ERROR Lookup.Fail[s];
    cl ← DI.DerefProcDesc[DI.GetControlLink[f]];
    cbti ← Pc.LinkToCbti[cl];
    END;
    
  SetUp: PROC [s: STRING, ex: DebugFormat.EXOI, noSyms: BOOLEAN ← FALSE]
      RETURNS [gf: GFHandle, pc: BytePC] =
    BEGIN
    cl: PrincOps.ControlLink;
    cbti: Symbols.CBTIndex;
    [cbti,cl] ← GetCbti[s ! DI.NotAProcedure => GOTO inline];
    gf ← Gf.FrameGfi[cl.gfi];
    IF CheckInline[cbti] THEN GOTO inline;
    IF ex = entry THEN
      BEGIN
      cl.gfi ← cl.gfi-Gf.GFI[gf];
      pc ← Pc.EntryPC[LOOPHOLE[cl,CARDINAL]/2, gf, noSyms !
	SymbolTable.Missing => {noSyms ← TRUE; RETRY}]
      END
    ELSE
      BEGIN
      IF cbti = Symbols.CBTNull THEN AbortWithWrittenError[noSym];
      pc ← Pc.ExitPC[cbti];
      END;
    EXITS inline => AbortWithWrittenError[inInline];
    END;
    
  CheckInline: PROC [cbti: Symbols.CBTIndex] RETURNS [inline: BOOLEAN] =
    BEGIN
    bb: Table.Base;
    IF cbti = Symbols.CBTNull THEN RETURN[FALSE];
    bb ← Table.Bounds[SymbolSegment.bodyType].base;
    RETURN[bb[cbti].inline]
    END;
    
  SetIt: PROC [body: STRING, bt: BreakType] =
    BEGIN
    gf: GFHandle;
    pc: BytePC;
    syms: BOOLEAN ← TRUE;
    [gf,pc] ← SetUp[body,bt.ex];
    [] ← DSyms.GFrameMdi[gf,TRUE !
      DSyms.NoFGT, SymbolTable.Missing => {syms ← FALSE; CONTINUE}];
    DOutput.Char[' ];
    SetOne[gf,pc,bt,syms];
    END;
    
  SetOne: PROC [gf: GFHandle, pc: BytePC, bt: BreakType, syms: BOOLEAN] =
    BEGIN
    bb: BBHandle;
    bb ← Insert[gf,pc,bt,syms ! Fail => WriteError[type]];
    DOutput.Text["Breakpoint #"L]; DOutput.Decimal[bb.num];
    DOutput.Line["."L];
    END;
    
  ClearIt: PROCEDURE [body: STRING, ex: DebugFormat.EXOI] =
    BEGIN
    pc, nosymspc: BytePC;
    gf: GFHandle;
    nosyms: BOOLEAN;
    [gf,pc] ← SetUp[body,ex];
    FOR nosyms IN BOOLEAN DO
      Remove[gf, pc ! Fail =>
	IF nosyms OR type # notFound OR ex # entry THEN WriteError[type] 
	ELSE
	  BEGIN
	  nosymspc ← SetUp[body,ex,TRUE].pc;
	  IF nosymspc = pc THEN WriteError[type] ELSE {pc ← nosymspc; LOOP}
	  END];
      EXIT
      ENDLOOP;
    END;
    
  ClearAllBreaks: PUBLIC PROC =
    BEGIN
    bb, next: BBHandle;
    FOR bb ← SetBlocks, next UNTIL bb = NIL DO
      next ← bb.link;
      Remove[bb.gf, bb.pc ! Fail => 
	BEGIN OPEN DOutput;
	Decimal[bb.num]; Text[" -- "L]; WriteError[type]; EOL[];
	CONTINUE;
	END];
      ENDLOOP;
    END;
    
  GetBreak: PROC [num: STRING] RETURNS [bb: DebugFormat.BBHandle] =
    BEGIN
    i: INTEGER ← DebugOps.StringExpToDecimal[num];
    bb ← FindBBNum[LOOPHOLE[i,CARDINAL]];
    IF bb = NIL THEN {WriteError[notFound]; DOutput.EOL[]};
    END;
    
  ClearBreak: PUBLIC PROC [bbNum: STRING] = {ClearDefault[GetBreak[bbNum]]};
  
  ClearDefault: PUBLIC PROC [bb: BBHandle] = {IF bb # NIL THEN Remove[bb.gf, bb.pc]};
  BreakEntry: PUBLIC PROC [proc: STRING] = {SetIt[proc,[entry,break]]};
  BreakExit: PUBLIC PROC [proc: STRING] = {SetIt[proc,[exit,break]]};
  TraceEntry: PUBLIC PROC [proc: STRING] = {SetIt[proc,[entry,trace]]};
  TraceExit: PUBLIC PROC [proc: STRING] = {SetIt[proc,[exit,trace]]};
  
  ClearEntryBreak, ClearEntryTrace: PUBLIC PROC [proc: STRING] = 
    BEGIN ClearIt[proc,entry] END;
    
  ClearXitBreak, ClearXitTrace: PUBLIC PROC [proc: STRING] = 
    BEGIN ClearIt[proc,exit] END;
    
  BreakAllEntries: PUBLIC PROC [mod, exp: STRING] =
    BEGIN DoAll[mod,[entry,break],exp,TRUE] END;
    
  BreakAllXits: PUBLIC PROC [mod, exp: STRING] =
    BEGIN DoAll[mod,[exit,break],exp,TRUE] END;
    
  TraceAllEntries: PUBLIC PROC [mod, exp: STRING] =
    BEGIN DoAll[mod,[entry,trace],exp,TRUE] END;
    
  TraceAllXits: PUBLIC PROC [mod, exp: STRING] =
    BEGIN DoAll[mod,[exit,trace],exp,TRUE] END;
    
  ClearAllEntries: PUBLIC PROC [mod: STRING] =
    BEGIN DoAll[mod,[entry,break],NIL,FALSE] END;
    
  ClearAllXits: PUBLIC PROC [mod: STRING] =
    BEGIN DoAll[mod,[exit,break],NIL,FALSE] END;
    
  Expressionalize: PUBLIC PROC [bb: BBHandle, exp: STRING] =
    BEGIN
    IF bb = NIL THEN RETURN;
    IF bb.exp # NIL THEN Storage.FreeString[bb.exp];
    bb.exp ← exp;
    END;
    
  AttachExpression: PUBLIC PROC [bbNum, exp: STRING] =
    BEGIN
    bb: BBHandle ← FindBBNum[DebugOps.StringExpToDecimal[bbNum]];
    IF bb # NIL THEN Expressionalize[bb,Storage.CopyString[exp]]
    ELSE AbortWithWrittenError[notFound];
    END;
    
  ClearExpression: PUBLIC PROC [bbNum: STRING] = {AttachExpression[bbNum,NIL]};
  
  DoAll: PROC [mod: STRING, bt: BreakType, exp: STRING, set: BOOLEAN] =
    BEGIN
    mdi: Symbols.MDIndex;
    maxEp: Pc.EVRange;
    pc: BytePC;
    cbti: Symbols.CBTIndex;
    gf: GFHandle ←
      IF DebugOps.Numeric[mod] THEN LOOPHOLE[DebugOps.StringExpToOctal[mod]]
      ELSE Gf.Frame[mod];
    mdi ← DSyms.GFrameMdi[gf ! SymbolTable.Missing => {
      AbortWithWrittenError[noSym]; ERROR}];
    maxEp ← Pc.EVSize[mdi];
    DOutput.EOL[];
    FOR i: Pc.EVRange IN (0..maxEp] DO
      cbti ← Pc.EpToCBti[i,gf];
      SELECT bt.ex FROM
	entry => pc ← Pc.EntryPC[i,gf];
	exit => pc ← Pc.ExitPC[cbti];
	ENDCASE => ERROR;
      PrintName[cbti]; DOutput.Text[" -- "L];
      IF set THEN SetOne[gf,pc,bt,TRUE ! Fail => {DOutput.EOL[]; CONTINUE}]
      ELSE Remove[gf,pc !
	Fail => {WriteError[type]; DOutput.EOL[]; CONTINUE}];
      ENDLOOP;
    END;
    
  PrintName: PROC [cbti: Symbols.CBTIndex] =
    BEGIN
    bb: Table.Base ← Table.Bounds[Symbols.bodyType].base;
    IF cbti = Symbols.CBTNull THEN RETURN;
    Dump.HashVal[bb[cbti].id];
    END;
    
  Convert: PROC [gf,pc: STRING] RETURNS [GFHandle, BytePC] =
    BEGIN
    RETURN[LOOPHOLE[
      DebugOps.StringExpToOctal[gf]],[DebugOps.StringExpToOctal[pc]]]
    END;
    
  OctalBreak: PUBLIC PROCEDURE [gf,pc: STRING] =
    BEGIN
    ogf: GFHandle;
    opc: BytePC;
    [ogf,opc] ← Convert[gf: gf, pc: pc];
    DOutput.Text[" -- "L];
    SetOne[ogf, opc, [octal,break], FALSE];
    RETURN
    END;
    
  ClearOctal: PUBLIC PROC [gf,pc: STRING] =
    BEGIN
    ogf: GFHandle;
    opc: BytePC;
    [ogf,opc] ← Convert[gf: gf, pc: pc];
    Remove[ogf, opc];
    RETURN
    END;
    
  ListBreaks: PUBLIC PROCEDURE = {
    IF SetBlocks = NIL THEN DOutput.Line["None set!"L] 
    ELSE [] ← EnumerateBBs[ListOne]};
    
  ListBreak: PUBLIC PROC [bbNum: STRING] = {List[GetBreak[bbNum]]};
  
  ListOne: PROC [bb: BBHandle] RETURNS [BOOLEAN] = {List[bb]; RETURN[FALSE]};
  
  List: PUBLIC PROCEDURE [b: BBHandle]=
    BEGIN OPEN DOutput;
    IF b = NIL THEN RETURN;
    Decimal[b.num]; Text[" -- "L];
    IF b.bt.ex = octal THEN
      BEGIN
      Text["Octal-break in frame: "L]; Octal[b.gf];
      Text[", byte-pc: "L]; Octal[b.pc]; Line["."L];
      END
    ELSE
      BEGIN
      Text[IF b.bt.bt = break THEN "Break"L ELSE "Trace"L];
      Text[SELECT b.bt.ex FROM
	entry => " at entry to "L,
	exit => " at exit from "L,
	ENDCASE => " in "L];
      PrintName[Pc.CBti[b.pc,b.gf]];
      Gf.DisplayInMsg[b.gf,NIL]; Char['.];
      IF b.condition # NIL THEN {Text["  Condition: "L]; Text[b.condition]};
      IF b.exp # NIL THEN {Text["  Keystrokes: "L]; Text[b.exp]};
      EOL[];
      IF b.bt.ex = in THEN Source.Display[b.gf,b.pc,FALSE
	! Source.FileMissing => {Text[name]; Line[" not found!"L]};
	SymbolTable.Missing => Line["Symboltable not found!"L]];
      END;
    RETURN
    END;
    
  AbortWithError: PUBLIC PROC [err: Error] =
    BEGIN
    IF err IN Error[tooManyConditions..alreadySet] THEN ERROR Fail[err];
    SIGNAL Fail[err];
    END;
    
  AbortWithWrittenError: PUBLIC PROC [err: Error] = {WriteError[err]; AbortWithError[err]};
  
  WriteError: PUBLIC PROCEDURE [err: Error] =
    BEGIN
    msg: STRING ← [60];
    AppendError[msg,err];
    DOutput.Text[msg];
    END;
    
  AppendError: PUBLIC PROCEDURE [s: STRING, err: Error] =
    BEGIN
    String.AppendString[s, SELECT err FROM
      tooManyConditions => " too many conditional breaks!"L,
      invalidRelation => " invalid relation!"L,
      conditionNotFound => " user break block not found!"L,
      size => " variable is larger than a word!"L,
      stack => " rhs on stack not allowed!"L,
      portI => " can't break on port!"L,
      notExchangable => " no exchangable code found!"L,
      notFound => " breakpoint not found!"L,
      noneSet => " no breaks have been set!"L,
      noSym => " symboltable missing!"L,
      spare1 => " Can't dereference or access array to test condition!"L,
      inInline => " not allowed in INLINE!"L,
      alreadySet => " already set!"L,
      noReturn => " does not return!"L,
      worryOn => " conditions not checked in Worry mode!"L,
      spare2 => " ! Patch table full"L,
      ENDCASE => "??"L];
    RETURN
    END;
    
END.