(* M2DebugStack.mod display parameter stack.

Copyright (C) 2011-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Modula-2; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  *)

IMPLEMENTATION MODULE M2DebugStack ;

FROM DynamicStrings IMPORT InitString, KillString, Dup, Index, Slice, char,
                           ConCat, ConCatChar, InitStringCharStar, Length, Mark ;

FROM SymbolTable IMPORT IsConstLit, IsConstSet, IsConstructor, IsConst,
                        IsArray, IsVar, IsEnumeration, IsFieldEnumeration,
                        IsUnbounded, IsProcType, IsProcedure, IsPointer, IsParameter,
                        IsParameterVar, IsType, IsRecord, IsRecordField, IsVarient,
                        IsModule, IsDefImp, IsSet, IsSubrange, GetSymName, NulSym ;

FROM StringConvert IMPORT CardinalToString ;
FROM NameKey IMPORT Name, KeyToCharStar ;
FROM FIO IMPORT File, StdOut ;
FROM SFIO IMPORT WriteS ;
FROM M2Error IMPORT InternalError ;
FROM M2Printf IMPORT printf0, printf1, printf2, printf3, printf4 ;

CONST
   Debugging = FALSE ;

VAR
   OperandTok,
   OperandT,
   OperandF,
   OperandA,
   OperandD,
   OperandRW  : ProcedureWord ;
   OperandAnno: ProcedureString ;


(*
   x - checks to see that a=b.
*)

PROCEDURE x (a, b: String) : String ;
BEGIN
   IF a#b
   THEN
      InternalError ('different string returned')
   END ;
   RETURN( a )
END x ;


(*
   IsWhite - returns TRUE if, ch, is a space.
*)

PROCEDURE IsWhite (ch: CHAR) : BOOLEAN ;
BEGIN
   RETURN( ch=' ' )
END IsWhite ;


(*
   ConCatWord - joins sentances, a, b, together.
*)

PROCEDURE ConCatWord (a, b: String) : String ;
BEGIN
   IF (Length(a)=1) AND (char(a, 0)='a')
   THEN
      a := x(a, ConCatChar(a, 'n'))
   ELSIF (Length(a)>1) AND (char(a, -1)='a') AND IsWhite(char(a, -2))
   THEN
      a := x(a, ConCatChar(a, 'n'))
   END ;
   IF (Length(a)>0) AND (NOT IsWhite(char(a, -1)))
   THEN
      a := x(a, ConCatChar(a, ' '))
   END ;
   RETURN( x(a, ConCat(a, b)) )
END ConCatWord ;


(*
   symDesc -
*)

PROCEDURE symDesc (sym: CARDINAL; o: String) : String ;
BEGIN
   IF sym = NulSym
   THEN
      RETURN( ConCatWord(o, Mark(InitString('NulSym'))) )
   ELSIF IsConstLit(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('constant literal'))) )
   ELSIF IsConstSet(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('constant set'))) )
   ELSIF IsConstructor(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('constructor'))) )
   ELSIF IsConst(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('constant'))) )
   ELSIF IsArray(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('array'))) )
   ELSIF IsVar(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('variable'))) )
   ELSIF IsEnumeration(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('enumeration type'))) )
   ELSIF IsFieldEnumeration(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('enumeration field'))) )
   ELSIF IsUnbounded(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('unbounded parameter'))) )
   ELSIF IsProcType(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('procedure type'))) )
   ELSIF IsProcedure(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('procedure'))) )
   ELSIF IsPointer(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('pointer'))) )
   ELSIF IsParameter(sym)
   THEN
      IF IsParameterVar(sym)
      THEN
         RETURN( ConCatWord(o, Mark(InitString('var parameter'))) )
      ELSE
         RETURN( ConCatWord(o, Mark(InitString('parameter'))) )
      END
   ELSIF IsType(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('type'))) )
   ELSIF IsRecord(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('record'))) )
   ELSIF IsRecordField(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('record field'))) )
   ELSIF IsVarient(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('varient record'))) )
   ELSIF IsModule(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('module'))) )
   ELSIF IsDefImp(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('definition or implementation module'))) )
   ELSIF IsSet(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('set'))) )
   ELSIF IsSubrange(sym)
   THEN
      RETURN( ConCatWord(o, Mark(InitString('subrange'))) )
   ELSE
      RETURN( o )
   END
END symDesc ;


(*
   Output - output string, s, to Stdout.  It also disposes of the string, s.
*)

PROCEDURE Output (s: String) ;
BEGIN
   s := WriteS(StdOut, s) ;
   s := KillString(s)
END Output ;


(*
   GetComment -
*)

PROCEDURE GetComment (s: String) : INTEGER ;
VAR
   c: INTEGER ;
BEGIN
   c := Index(s, '|', 0) ;
   WHILE c>=0 DO
      INC(c) ;
      IF c>=VAL(INTEGER, Length(s))
      THEN
         RETURN -1
      ELSIF char(s, c)='|'
      THEN
         RETURN c+1
      END ;
      c := Index(s, '|', c)
   END ;
   RETURN -1
END GetComment ;


(*
   doName - concatenate namekey, o, to, p.
*)

PROCEDURE doName (p: String; o: WORD) : String ;
BEGIN
   RETURN ConCat(p, InitStringCharStar(KeyToCharStar(o))) ;
END doName ;


(*
   doSymName - concatenate symbol, o, name to, p.
*)

PROCEDURE doSymName (p: String; o: WORD) : String ;
BEGIN
   RETURN ConCat(p, InitStringCharStar(KeyToCharStar(GetSymName(o)))) ;
END doSymName ;


(*
   doNumber - convert, o, to a cardinal and increment the length, l,
              by the number of characters required to represent, o.
*)

PROCEDURE doNumber (p: String; o: WORD) : String ;
BEGIN
   RETURN ConCat(p, CardinalToString(VAL(CARDINAL, o), 0, ' ', 10, TRUE))
END doNumber ;


(*
   doSymbol - handles a symbol indicated by, o.
*)

PROCEDURE doSymbol (p: String; o: WORD) : String ;
BEGIN
   RETURN symDesc(o, p)
END doSymbol ;


(*
   doOperand -
*)

PROCEDURE doOperand (p, s: String; VAR i: INTEGER; e: INTEGER; o: WORD) : String ;
BEGIN
   INC(i) ;
   IF i<e
   THEN
      CASE char(s, i) OF

      's':  (* symbol number *)
            INC(i) ;
            RETURN doSymbol(p, o) |
      'd':  (* decimal number *)
            INC(i) ;
            RETURN doNumber(p, o) |
      'a':  (* symbol name key *)
            INC(i) ;
            RETURN doSymName(p, o) |
      'n':  (* ascii name key *)
            INC(i) ;
            RETURN doName(p, o)

      ELSE
         InternalError ("incorrect format specifier expecting one of 's', 'd' or 'a'")
      END
   END ;
   RETURN p
END doOperand ;


(*
   doPercent -
*)

PROCEDURE doPercent (o, s: String;
                     VAR i: INTEGER; e: INTEGER; n: CARDINAL) : String ;
BEGIN
   INC(i) ;
   IF i<e
   THEN
      CASE char(s, i) OF

      '1':  RETURN doOperand(o, s, i, e, OperandT(n)) |
      '2':  RETURN doOperand(o, s, i, e, OperandF(n)) |
      '3':  RETURN doOperand(o, s, i, e, OperandTok(n))

      ELSE
         InternalError ('unrecognised format specifier - expecting 1, 2 or 3 after the %')
      END
   END ;
   InternalError ('end of field found before format specifier - expecting 1, 2 or 3 after the %')
END doPercent ;


(*
   doNameLength - increment, l, by the ascii length of string determined by, o.
*)

PROCEDURE doNameLength (VAR l: CARDINAL; o: WORD) ;
VAR
   s: String ;
BEGIN
   s := InitStringCharStar(KeyToCharStar(o)) ;
   INC(l, Length(s)) ;
   s := KillString(s)
END doNameLength ;


(*
   doSymNameLength - increment, l, by the ascii length of symbol, o.
*)

PROCEDURE doSymNameLength (VAR l: CARDINAL; o: WORD) ;
VAR
   s: String ;
BEGIN
   s := InitStringCharStar(KeyToCharStar(GetSymName(o))) ;
   INC(l, Length(s)) ;
   s := KillString(s)
END doSymNameLength ;


(*
   doNumberLength - convert, o, to a cardinal and increment the length, l,
                    by the number of characters required to represent, o.
*)

PROCEDURE doNumberLength (VAR l: CARDINAL; o: WORD) ;
VAR
   s: String ;
BEGIN
   s := CardinalToString(VAL(CARDINAL, o), 0, ' ', 10, TRUE) ;
   INC(l, Length(s)) ;
   s := KillString(s)
END doNumberLength ;


(*
   doSymbolLength - handles a symbol indicated by, o.
*)

PROCEDURE doSymbolLength (VAR l: CARDINAL; o: WORD) ;
VAR
   s: String ;
BEGIN
   s := symDesc(o, InitString('')) ;
   INC(l, Length(s)) ;
   s := KillString(s)
END doSymbolLength ;


(*
   doOperandLength -
*)

PROCEDURE doOperandLength (s: String; VAR i: INTEGER; e: INTEGER; VAR l: CARDINAL; o: WORD) ;
BEGIN
   INC(i) ;
   IF i<e
   THEN
      CASE char(s, i) OF

      's':  (* symbol number *)
            INC(i) ;
            doSymbolLength(l, o) |
      'd':  (* decimal number *)
            INC(i) ;
            doNumberLength(l, o) |
      'a':  (* ascii name key *)
            INC(i) ;
            doSymNameLength(l, o) |
      'n':  (* ascii name key *)
            INC(i) ;
            doNameLength(l, o)

      ELSE
         InternalError ("incorrect format specifier expecting one of 's', 'd' or 'a'")
      END
   END
END doOperandLength ;


(*
   doPercentLength -
*)

PROCEDURE doPercentLength (s: String; VAR i: INTEGER; e: INTEGER;
                           VAR l: CARDINAL; n: CARDINAL) ;
BEGIN
   INC(i) ;
   IF i<e
   THEN
      CASE char(s, i) OF

      '1':  doOperandLength(s, i, e, l, OperandT(n)) |
      '2':  doOperandLength(s, i, e, l, OperandF(n)) |
      '3':  doOperandLength(s, i, e, l, OperandTok(n)) |

      ELSE
         InternalError ('unrecognised format specifier - expecting 1, 2 or 3 after the %')
      END
   END
END doPercentLength ;


(*
   doFieldLength - compute the string length given in annotation
                   at position, n, on the stack between characters
                   b and e.

                   The string description between: b..e can contain any
                   of these patterns:

                   %a           ascii name key.
                   %s           symbol number.
                   %d           decimal cardinal number.
                   |            indicates the next field.
*)

PROCEDURE doFieldLength (b, e: INTEGER; n: CARDINAL) : CARDINAL ;
VAR
   l: CARDINAL ;
   i: INTEGER ;
   s: String ;
BEGIN
   IF b=-1
   THEN
      RETURN( 0 )
   END ;
   s := OperandAnno(n) ;
   IF e=-1
   THEN
      e := Length(s)
   END ;
   l := 0 ;
   i := b ;
   WHILE i<e DO
      CASE char(s, i) OF

      '|':  RETURN l |
      '%':  doPercentLength(s, i, e, l, n) ;

      ELSE
         INC(l)
      END ;
      INC(i)
   END ;
   RETURN l
END doFieldLength ;


(*
   stop -
*)

PROCEDURE stop ;
BEGIN
END stop ;


(*
   doMaxCard - returns the maximum of two CARDINALs.
*)

PROCEDURE doMaxCard (a, b: CARDINAL) : CARDINAL ;
BEGIN
   IF (a>100) OR (b>100)
   THEN
      stop
   END ;
   IF a>b
   THEN
      RETURN a
   ELSE
      RETURN b
   END
END doMaxCard ;


(*
   GetAnnotationFieldLength -
*)

PROCEDURE GetAnnotationFieldLength (n: CARDINAL; f: CARDINAL) : CARDINAL ;
VAR
   c, e: INTEGER ;
BEGIN
   c := GetComment(OperandAnno(n)) ;
   IF c>0
   THEN
      IF Debugging
      THEN
         printf0('full anno is: ') ; Output(Dup(OperandAnno(n))) ; printf0('\n') ;
         printf0('comment field is: ') ; Output(Slice(OperandAnno(n), c, 0)) ; printf0('\n')
      END ;
      e := Index(OperandAnno(n), '|', c) ;
      IF f=0
      THEN
         RETURN doFieldLength(c, e, n)
      ELSE
         IF e>=0
         THEN
            INC(e)
         END ;
         RETURN doFieldLength(e, -1, n)
      END
   ELSE
      RETURN 0
   END
END GetAnnotationFieldLength ;


(*
   GetAnnotationLength -
*)

PROCEDURE GetAnnotationLength (n: CARDINAL; f: CARDINAL) : CARDINAL ;
VAR
   l: CARDINAL ;
BEGIN
   IF OperandAnno(n)=NIL
   THEN
      l := 0 ;
      IF f=0
      THEN
         doNumberLength(l, OperandT(n))
      ELSE
         doNumberLength(l, OperandF(n))
      END ;
      RETURN l
   ELSE
      RETURN GetAnnotationFieldLength(n, f)
   END
END GetAnnotationLength ;


(*
   GetFieldLength - returns the number of characters used in field, f,
                    at position, n, on the stack.
*)

PROCEDURE GetFieldLength (n: CARDINAL; f: CARDINAL) : CARDINAL ;
VAR
   c, b, e: INTEGER ;
BEGIN
   c := GetComment(OperandAnno(n)) ;
   IF c>1
   THEN
      e := c-2
   ELSE
      e := Length(OperandAnno(n))
   END ;
   IF f=0
   THEN
      b := 0
   ELSE
      b := Index(OperandAnno(n), '|', 0) ;
      IF b=-1
      THEN
         RETURN 0
      ELSE
         INC(b)
      END
   END ;
   RETURN doFieldLength(b, e, n)
END GetFieldLength ;


(*
   GetMaxFieldAnno - returns the maximum number of characters required
                     by either the annotation or field, f, at position, n,
                     on the stack.
*)

PROCEDURE GetMaxFieldAnno (n: CARDINAL; f: CARDINAL) : CARDINAL ;
BEGIN
   RETURN doMaxCard(GetAnnotationLength(n, f), GetFieldLength(n, f))
END GetMaxFieldAnno ;


(*
   GetStackFieldLengths - assigns, tn, and, fn, with the
                          maximum field width values.
*)

PROCEDURE GetStackFieldLengths (VAR tn, fn, tk: CARDINAL; amount: CARDINAL) ;
VAR
   i: CARDINAL ;
BEGIN
   i := 1 ;
   tn := 0 ;
   fn := 0 ;
   tk := 0 ;
   WHILE i<=amount DO
      tn := doMaxCard(tn, GetMaxFieldAnno(i, 0)) ;
      fn := doMaxCard(fn, GetMaxFieldAnno(i, 1)) ;
      tk := doMaxCard(tk, GetMaxFieldAnno(i, 2)) ;
      INC(i)
   END
END GetStackFieldLengths ;


(*
   DisplayRow -
*)

PROCEDURE DisplayRow (tn, fn, tk: CARDINAL; initOrFinal: BOOLEAN) ;
VAR
   i: CARDINAL ;
BEGIN
   printf0('+-') ;
   FOR i := 1 TO tn DO
      printf0('-')
   END ;
   IF (fn=0) AND (tk=0)
   THEN
      IF initOrFinal
      THEN
         printf0('-+-')
      ELSE
         printf0('-|-')
      END
   ELSE
      IF initOrFinal
      THEN
         printf0('-+-')
      ELSE
         printf0('-|-')
      END ;
      IF fn#0
      THEN
         FOR i := 1 TO fn DO
            printf0('-')
         END
      END ;
      IF initOrFinal
      THEN
         printf0('-+-')
      ELSE
         printf0('-|-')
      END ;
      IF tk#0
      THEN
         FOR i := 1 TO tk DO
            printf0('-')
         END ;
         printf0('-+\n')
      END
   END
END DisplayRow ;


(*
   SkipToField -
*)

PROCEDURE SkipToField (s: String; n: CARDINAL) : INTEGER ;
VAR
   i, h: INTEGER ;
BEGIN
   i := 0 ;
   h := Length(s) ;
   WHILE (n>0) AND (i<h) DO
      IF Index(s, '|', i)>0
      THEN
         DEC(n) ;
         IF (i<h) AND (char(s, i+1)='|')
         THEN
            (* comment seen, no field available *)
            RETURN -1
         END ;
         i := Index(s, '|', i)
      ELSE
         RETURN -1
      END ;
      INC(i)
   END ;
   IF i=h
   THEN
      i := -1
   END ;
   RETURN i
END SkipToField ;


(*
   Pad - padds out string, s, to paddedLength characters.
*)

PROCEDURE Pad (o: String; paddedLength: CARDINAL) : String ;
VAR
   i: CARDINAL ;
BEGIN
   i := Length(o) ;
   IF i<paddedLength
   THEN
      REPEAT
         o := ConCatChar(o, ' ') ;
         INC(i)
      UNTIL i=paddedLength
   END ;
   RETURN o
END Pad ;


(*
   doField - compute the string length given in annotation
             at position, n, on the stack between characters
             b and e.

             The string description between: b..e can contain any
             of these patterns:

             %a           ascii name key.
             %s           symbol number.
             %d           decimal cardinal number.
             |            indicates the next field.
*)

PROCEDURE doField (s: String; n: CARDINAL; f: CARDINAL; l: CARDINAL) : String ;
VAR
   h, i, j: INTEGER ;
   o      : String ;
BEGIN
   h := Length(s) ;
   i := SkipToField(s, f) ;
   o := InitString('') ;
   IF i>=0
   THEN
      j := SkipToField(s, f+1) ;
      IF j=-1
      THEN
         j := h
      END ;
      WHILE i<h DO
         CASE char(s, i) OF

         '|':  i := h |
         '%':  o := doPercent(o, s, i, h, n)

         ELSE
            o := ConCatChar(o, char(s, i)) ;
            INC(i)
         END
      END
   END ;
   o := Pad(o, l) ;
   RETURN o
END doField ;


(*
   doAnnotation -
*)

PROCEDURE doAnnotation (s: String; n: CARDINAL;
                        field: CARDINAL; width: CARDINAL) : String ;
VAR
   c    : INTEGER ;
   cf, o: String ;
BEGIN
   c := GetComment(s) ;
   IF c>=0
   THEN
      cf := Slice(s, c, 0) ;
      o := doField(cf, n, field, width) ;
      cf := KillString(cf) ;
      RETURN o
   ELSE
      RETURN InitString('')
   END
END doAnnotation ;


(*
   DisplayFields -
*)

PROCEDURE DisplayFields (n: CARDINAL; tn, fn, tk: CARDINAL) ;
VAR
   s      : String ;
   t, f, k: CARDINAL ;
BEGIN
   s := OperandAnno(n) ;
   IF s=NIL
   THEN
      t := OperandT(n) ;
      f := OperandF(n) ;
      k := OperandTok(n) ;
      printf0('| ') ;
      Output(Pad(CardinalToString(VAL(CARDINAL, t), 0, ' ', 10, TRUE), tn)) ;
      printf0(' | ') ;
      Output(Pad(CardinalToString(VAL(CARDINAL, f), 0, ' ', 10, TRUE), fn)) ;
      printf0(' | ') ;
      Output(Pad(CardinalToString(VAL(CARDINAL, k), 0, ' ', 10, TRUE), tk)) ;
      printf0(' |\n')
   ELSE
      IF tn>0
      THEN
         printf0('| ') ;
         Output(doField(s, n, 0, tn))
      END ;
      IF fn>0
      THEN
         printf0(' | ') ;
         Output(doField(s, n, 1, fn))
      END ;
      IF tk>0
      THEN
         printf0(' | ') ;
         Output(doField(s, n, 2, tk))
      END ;
      printf0(' |\n') ;
      IF tn>0
      THEN
         printf0('| ') ;
         Output(doAnnotation(s, n, 0, tn))
      END ;
      IF fn>0
      THEN
         printf0(' | ') ;
         Output(doAnnotation(s, n, 1, fn))
      END ;
      IF tk>0
      THEN
         printf0(' | ') ;
         Output(doAnnotation(s, n, 2, tk))
      END ;
      printf0(' |\n')
   END
END DisplayFields ;


(*
   DebugStack - displays the stack.
*)

PROCEDURE DebugStack (amount: CARDINAL;
                      opt, opf, opa, opd, oprw, optk: ProcedureWord;
                      opanno: ProcedureString) ;
VAR
   i         : CARDINAL ;
   tn, fn, tk: CARDINAL ;
BEGIN
   OperandT := opt ;
   OperandF := opf ;
   OperandA := opa ;
   OperandD := opd ;
   OperandRW := oprw ;
   OperandAnno := opanno ;
   OperandTok := optk ;
   GetStackFieldLengths(tn, fn, tk, amount) ;
   i := 1 ;
   WHILE i<=amount DO
      IF i=1
      THEN
         DisplayRow(tn, fn, tk, TRUE)
      END ;
      DisplayFields(i, tn, fn, tk) ;
      DisplayRow(tn, fn, tk, i=amount) ;
      INC(i)
   END
END DebugStack ;


END M2DebugStack.