(* M2CaseList.mod implement ISO case label lists.

Copyright (C) 2009-2023 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 M2CaseList ;


FROM M2Debug IMPORT Assert ;
FROM M2GCCDeclare IMPORT TryDeclareConstant, GetTypeMin, GetTypeMax ;
FROM M2MetaError IMPORT MetaError1, MetaError2, MetaErrorT0, MetaErrorT1, MetaErrorT2, MetaErrorT3, MetaErrorT4, MetaErrorStringT0, MetaErrorString1 ;
FROM M2Error IMPORT InternalError ;
FROM M2Range IMPORT OverlapsRange, IsEqual, IsGreater ;
FROM M2ALU IMPORT PushIntegerTree, PopIntegerTree, Addn, Sub, PushInt ;
FROM Indexing IMPORT Index, InitIndex, PutIndice, GetIndice, ForeachIndiceInIndexDo, HighIndice ;
FROM Lists IMPORT InitList, IncludeItemIntoList, RemoveItemFromList, NoOfItemsInList, GetItemFromList ;
FROM NameKey IMPORT KeyToCharStar ;
FROM SymbolConversion IMPORT GccKnowsAbout, Mod2Gcc, AddModGcc ;
FROM DynamicStrings IMPORT InitString, InitStringCharStar, ConCat, Mark, KillString ;
FROM m2tree IMPORT Tree ;
FROM m2block IMPORT RememberType ;
FROM m2type IMPORT GetMinFrom ;
FROM m2expr IMPORT GetIntegerOne ;
FROM Storage IMPORT ALLOCATE ;
FROM M2Base IMPORT IsExpressionCompatible ;
FROM M2Printf IMPORT printf1 ;
FROM M2LexBuf IMPORT TokenToLocation ;

FROM SymbolTable IMPORT NulSym, IsConst, IsFieldVarient, IsRecord, IsRecordField, GetVarientTag, GetType,
                        ForeachLocalSymDo, GetSymName, IsEnumeration, SkipType, NoOfElements, GetNth ;

TYPE
   RangePair = POINTER TO RECORD
                  low, high: CARDINAL ;
                  tokenno  : CARDINAL ;
               END ;

   ConflictingPair = POINTER TO RECORD
                        a, b: RangePair ;
                     END ;

   CaseList = POINTER TO RECORD
                 maxRangeId  : CARDINAL ;
                 rangeArray  : Index ;
                 currentRange: RangePair ;
                 varientField: CARDINAL ;
              END ;

   CaseDescriptor = POINTER TO RECORD
                       elseClause   : BOOLEAN ;
                       elseField    : CARDINAL ;
                       record       : CARDINAL ;
                       varient      : CARDINAL ;
                       expression   : CARDINAL ;
                       maxCaseId    : CARDINAL ;
                       caseListArray: Index ;
                       currentCase  : CaseList ;
                       next         : CaseDescriptor ;
                    END ;

   SetRange = POINTER TO RECORD
                 low, high: Tree ;
                 next     : SetRange ;
              END ;

VAR
   caseStack    : CaseDescriptor ;
   caseId       : CARDINAL ;
   caseArray    : Index ;
   conflictArray: Index ;
   FreeRangeList: SetRange ;



(*
   PushCase - create a case entity and push it to an internal stack.
              rec is NulSym if this is a CASE statement.
              If rec is a record then it indicates a possible
              varients reside in the record to check.
              Both rec and va might be NulSym and then the expr
              will contain the selector expression to a case statement.
              Return the case id.
*)

PROCEDURE PushCase (rec, va, expr: CARDINAL) : CARDINAL ;
VAR
   c: CaseDescriptor ;
BEGIN
   INC (caseId) ;
   NEW (c) ;
   IF c = NIL
   THEN
      InternalError ('out of memory error')
   ELSE
      WITH c^ DO
         elseClause := FALSE ;
         elseField := NulSym ;
         record := rec ;
         varient := va ;
         expression := expr ;
         maxCaseId := 0 ;
         caseListArray := InitIndex (1) ;
         next := caseStack ;
         currentCase := NIL
      END ;
      caseStack := c ;
      PutIndice (caseArray, caseId, c)
   END ;
   RETURN caseId
END PushCase ;


(*
   PopCase - pop the top element of the case entity from the internal
             stack.
*)

PROCEDURE PopCase ;
BEGIN
   IF caseStack=NIL
   THEN
      InternalError ('case stack is empty')
   END ;
   caseStack := caseStack^.next
END PopCase ;


(*
   ElseCase - indicates that this case varient does have an else clause.
*)

PROCEDURE ElseCase (f: CARDINAL) ;
BEGIN
   WITH caseStack^ DO
      elseClause := TRUE ;
      elseField := f
   END
END ElseCase ;


(*
   BeginCaseList - create a new label list.
*)

PROCEDURE BeginCaseList (v: CARDINAL) ;
VAR
   l: CaseList ;
BEGIN
   NEW(l) ;
   IF l=NIL
   THEN
      InternalError ('out of memory error')
   END ;
   WITH l^ DO
      maxRangeId   := 0 ;
      rangeArray   := InitIndex(1) ;
      currentRange := NIL ;
      varientField := v
   END ;
   WITH caseStack^ DO
      INC(maxCaseId) ;
      PutIndice(caseListArray, maxCaseId, l) ;
      currentCase := l
   END
END BeginCaseList ;


(*
   EndCaseList - terminate the current label list.
*)

PROCEDURE EndCaseList ;
BEGIN
   caseStack^.currentCase := NIL
END EndCaseList ;


(*
   AddRange - add a range to the current label list.
*)

PROCEDURE AddRange (r1, r2: CARDINAL; tok: CARDINAL) ;
VAR
   r: RangePair ;
BEGIN
   NEW(r) ;
   IF r=NIL
   THEN
      InternalError ('out of memory error')
   ELSE
      WITH r^ DO
         low := r1 ;
         high := r2 ;
         tokenno := tok
      END ;
      WITH caseStack^.currentCase^ DO
         INC(maxRangeId) ;
         PutIndice(rangeArray, maxRangeId, r) ;
         currentRange := r
      END
   END
END AddRange ;


(*
   GetVariantTagType - returns the type associated with, variant.
*)

PROCEDURE GetVariantTagType (variant: CARDINAL) : CARDINAL ;
VAR
   tag: CARDINAL ;
BEGIN
   tag := GetVarientTag(variant) ;
   IF IsFieldVarient(tag) OR IsRecordField(tag)
   THEN
      RETURN( GetType(tag) )
   ELSE
      RETURN( tag )
   END
END GetVariantTagType ;


(*
   CaseBoundsResolved - returns TRUE if all constants in the case list, c,
                        are known to GCC.
*)

PROCEDURE CaseBoundsResolved (tokenno: CARDINAL; c: CARDINAL) : BOOLEAN ;
VAR
   resolved: BOOLEAN ;
   p       : CaseDescriptor ;
   q       : CaseList ;
   r       : RangePair ;
   min,
   max,
   type,
   i, j    : CARDINAL ;
BEGIN
   p := GetIndice(caseArray, c) ;
   WITH p^ DO
      IF varient#NulSym
      THEN
         (* not a CASE statement, but a varient record containing without an ELSE clause *)
         type := GetVariantTagType(varient) ;
         resolved := TRUE ;
         IF NOT GccKnowsAbout(type)
         THEN
            (* do we need to add, type, to the list of types required to be resolved? *)
            resolved := FALSE
         END ;
         min := GetTypeMin(type) ;
         IF NOT GccKnowsAbout(min)
         THEN
            TryDeclareConstant(tokenno, min) ;
            resolved := FALSE
         END ;
         max := GetTypeMax(type) ;
         IF NOT GccKnowsAbout(max)
         THEN
            TryDeclareConstant(tokenno, max) ;
            resolved := FALSE
         END ;
         IF NOT resolved
         THEN
            RETURN( FALSE )
         END
      END ;
      i := 1 ;
      WHILE i<=maxCaseId DO
         q := GetIndice(caseListArray, i) ;
         j := 1 ;
         WHILE j<=q^.maxRangeId DO
            r := GetIndice(q^.rangeArray, j) ;
            IF r^.low#NulSym
            THEN
               IF IsConst(r^.low)
               THEN
                  TryDeclareConstant(tokenno, r^.low) ;
                  IF NOT GccKnowsAbout(r^.low)
                  THEN
                     RETURN( FALSE )
                  END
               ELSE
                  IF r^.high=NulSym
                  THEN
                     MetaError1('the CASE statement variant must be defined by a constant {%1Da:is a {%1d}}', r^.low)
                  ELSE
                     MetaError1('the CASE statement variant low value in a range must be defined by a constant {%1Da:is a {%1d}}',
                                r^.low)
                  END
               END
            END ;
            IF r^.high#NulSym
            THEN
               IF IsConst(r^.high)
               THEN
                  TryDeclareConstant(tokenno, r^.high) ;
                  IF NOT GccKnowsAbout(r^.high)
                  THEN
                     RETURN( FALSE )
                  END
               ELSE
                  MetaError1('the CASE statement variant high value in a range must be defined by a constant {%1Da:is a {%1d}}',
                             r^.high)
               END
            END ;
            INC(j)
         END ;
         INC(i)
      END
   END ;
   RETURN( TRUE )
END CaseBoundsResolved ;


(*
   IsSame - return TRUE if r, s, are in, e.
*)

PROCEDURE IsSame (e: ConflictingPair; r, s: RangePair) : BOOLEAN ;
BEGIN
   WITH e^ DO
      RETURN( ((a=r) AND (b=s)) OR ((a=s) AND (b=r)) )
   END
END IsSame ;


(*
   SeenBefore -
*)

PROCEDURE SeenBefore (r, s: RangePair) : BOOLEAN ;
VAR
   i, h: CARDINAL ;
   e   : ConflictingPair ;
BEGIN
   h := HighIndice(conflictArray) ;
   i := 1 ;
   WHILE i<=h DO
      e := GetIndice(conflictArray, i) ;
      IF IsSame(e, r, s)
      THEN
         RETURN( TRUE )
      END ;
      INC(i)
   END ;
   NEW(e) ;
   WITH e^ DO
      a := r ;
      b := s
   END ;
   PutIndice(conflictArray, h+1, e) ;
   RETURN( FALSE )
END SeenBefore ;


(*
   Overlaps -
*)

PROCEDURE Overlaps (r, s: RangePair) : BOOLEAN ;
VAR
   a, b, c, d: CARDINAL ;
BEGIN
   a := r^.low ;
   c := s^.low ;
   IF r^.high=NulSym
   THEN
      b := a ;
      IF s^.high=NulSym
      THEN
         d := c ;
         IF OverlapsRange(Mod2Gcc(a), Mod2Gcc(b), Mod2Gcc(c), Mod2Gcc(d))
         THEN
            IF NOT SeenBefore(r, s)
            THEN
               MetaErrorT2 (r^.tokenno, 'case label {%1ad} is a duplicate with {%2ad}', a, c) ;
               MetaErrorT2 (s^.tokenno, 'case label {%1ad} is a duplicate with {%2ad}', c, a)
            END ;
            RETURN( TRUE )
         END
      ELSE
         d := s^.high ;
         IF OverlapsRange(Mod2Gcc(a), Mod2Gcc(b), Mod2Gcc(c), Mod2Gcc(d))
         THEN
            IF NOT SeenBefore (r, s)
            THEN
               MetaErrorT3 (r^.tokenno, 'case label {%1ad} is a duplicate in the range {%2ad}..{%3ad}', a, c, d) ;
               MetaErrorT3 (s^.tokenno, 'case range {%2ad}..{%3ad} is a duplicate of case label {%1ad}', c, d, a)
            END ;
            RETURN( TRUE )
         END
      END
   ELSE
      b := r^.high ;
      IF s^.high=NulSym
      THEN
         d := c ;
         IF OverlapsRange (Mod2Gcc(a), Mod2Gcc(b), Mod2Gcc(c), Mod2Gcc(d))
         THEN
            IF NOT SeenBefore(r, s)
            THEN
               MetaErrorT3 (r^.tokenno, 'case range {%1ad}..{%2ad} is a duplicate with case label {%3ad}', a, b, c) ;
               MetaErrorT3 (s^.tokenno, 'case label {%1ad} is a duplicate with case range %{2ad}..{%3ad}', c, a, b)
            END ;
            RETURN( TRUE )
         END
      ELSE
         d := s^.high ;
         IF OverlapsRange(Mod2Gcc(a), Mod2Gcc(b), Mod2Gcc(c), Mod2Gcc(d))
         THEN
            IF NOT SeenBefore(r, s)
            THEN
               MetaErrorT4 (r^.tokenno, 'case range {%1ad}..{%2ad} overlaps case range {%3ad}..{%4ad}', a, b, c, d) ;
               MetaErrorT4 (s^.tokenno, 'case range {%1ad}..{%2ad} overlaps case range {%3ad}..{%4ad}', c, d, a, b)
            END ;
            RETURN( TRUE )
         END
      END
   END ;
   RETURN( FALSE )
END Overlaps ;


(*
   OverlappingCaseBound - returns TRUE if, r, overlaps any case bound in the
                          case statement, c.
*)

PROCEDURE OverlappingCaseBound (r: RangePair; c: CARDINAL) : BOOLEAN ;
VAR
   p      : CaseDescriptor ;
   q      : CaseList ;
   s      : RangePair ;
   i, j   : CARDINAL ;
   overlap: BOOLEAN ;
BEGIN
   p := GetIndice (caseArray, c) ;
   overlap := FALSE ;
   WITH p^ DO
      i := 1 ;
      WHILE i<=maxCaseId DO
         q := GetIndice (caseListArray, i) ;
         j := 1 ;
         WHILE j<=q^.maxRangeId DO
            s := GetIndice (q^.rangeArray, j) ;
            IF (s#r) AND Overlaps (r, s)
            THEN
               overlap := TRUE
            END ;
            INC (j)
         END ;
         INC (i)
      END
   END ;
   RETURN( overlap )
END OverlappingCaseBound ;


(*
   OverlappingCaseBounds - returns TRUE if there were any overlapping bounds
                           in the case list, c.  It will generate an error
                           messages for each overlapping bound found.
*)

PROCEDURE OverlappingCaseBounds (c: CARDINAL) : BOOLEAN ;
VAR
   p      : CaseDescriptor ;
   q      : CaseList ;
   r      : RangePair ;
   i, j   : CARDINAL ;
   overlap: BOOLEAN ;
BEGIN
   p := GetIndice(caseArray, c) ;
   overlap := FALSE ;
   WITH p^ DO
      i := 1 ;
      WHILE i<=maxCaseId DO
         q := GetIndice(caseListArray, i) ;
         j := 1 ;
         WHILE j<=q^.maxRangeId DO
            r := GetIndice(q^.rangeArray, j) ;
            IF OverlappingCaseBound (r, c)
            THEN
               overlap := TRUE
            END ;
            INC(j)
         END ;
         INC(i)
      END
   END ;
   RETURN( overlap )
END OverlappingCaseBounds ;


(*
   NewRanges -
*)

PROCEDURE NewRanges () : SetRange ;
VAR
   s: SetRange ;
BEGIN
   IF FreeRangeList=NIL
   THEN
      NEW(s)
   ELSE
      s := FreeRangeList ;
      FreeRangeList := FreeRangeList^.next
   END ;
   s^.next := NIL ;
   RETURN( s )
END NewRanges ;


(*
   NewSet -
*)

PROCEDURE NewSet (type: CARDINAL) : SetRange ;
VAR
   s: SetRange ;
BEGIN
   s := NewRanges() ;
   WITH s^ DO
      low := Mod2Gcc(GetTypeMin(type)) ;
      high := Mod2Gcc(GetTypeMax(type)) ;
      next := NIL
   END ;
   RETURN( s )
END NewSet ;


(*
   DisposeRanges -
*)

PROCEDURE DisposeRanges (set: SetRange) : SetRange ;
VAR
   t: SetRange ;
BEGIN
   IF set#NIL
   THEN
      IF FreeRangeList=NIL
      THEN
         FreeRangeList := set
      ELSE
         t := set ;
         WHILE t^.next#NIL DO
            t := t^.next
         END ;
         t^.next := FreeRangeList ;
         FreeRangeList := set
      END
   END ;
   RETURN( NIL )
END DisposeRanges ;


(*
   RemoveRange - removes the range descriptor h from set and return the
                 possibly new head of set.
*)

PROCEDURE RemoveRange (set: SetRange; h: SetRange) : SetRange ;
VAR
   i: SetRange ;
BEGIN
   IF h=set
   THEN
      set := set^.next ;
      h^.next := NIL ;
      h := DisposeRanges(h) ;
   ELSE
      i := set ;
      WHILE i^.next#h DO
         i := i^.next
      END ;
      i^.next := h^.next ;
      i := h ;
      h := h^.next ;
      i^.next := NIL ;
      i := DisposeRanges(i)
   END ;
   RETURN set
END RemoveRange ;


(*
   SubBitRange - subtracts bits, lo..hi, from, set.
*)

PROCEDURE SubBitRange (set: SetRange; lo, hi: Tree; tokenno: CARDINAL) : SetRange ;
VAR
   h, i: SetRange ;
BEGIN
   h := set ;
   WHILE h#NIL DO
      (* Check to see if a single set element h is obliterated by lo..hi.  *)
      IF (h^.high=NIL) OR IsEqual(h^.high, h^.low)
      THEN
         IF IsEqual(h^.low, lo) OR OverlapsRange(lo, hi, h^.low, h^.low)
         THEN
            set := RemoveRange (set, h) ;
            h := set
         ELSE
            h := h^.next
         END
      (* Now check to see if the lo..hi match exactly with the set range.  *)
      ELSIF (h^.high#NIL) AND IsEqual (lo, h^.low) AND IsEqual (hi, h^.high)
      THEN
         (* Remove h and return as lo..hi have been removed.  *)
         RETURN RemoveRange (set, h)
      ELSE
         (* All other cases require modifying the existing set range.  *)
         IF OverlapsRange(lo, hi, h^.low, h^.high)
         THEN
            IF IsGreater(h^.low, lo) OR IsGreater(hi, h^.high)
            THEN
               MetaErrorT0 (tokenno, 'variant case range lies outside tag value')
            ELSE
               IF IsEqual(h^.low, lo)
               THEN
                  PushIntegerTree(hi) ;
                  PushInt(1) ;
                  Addn ;
                  h^.low := PopIntegerTree()
               ELSIF IsEqual(h^.high, hi)
               THEN
                  PushIntegerTree(lo) ;
                  PushInt(1) ;
                  Sub ;
                  h^.high := PopIntegerTree()
               ELSE
                  (* lo..hi  exist inside range h^.low..h^.high *)
                  i := NewRanges() ;
                  i^.next := h^.next ;
                  h^.next := i ;
                  i^.high := h^.high ;
                  PushIntegerTree(lo) ;
                  PushInt(1) ;
                  Sub ;
                  h^.high := PopIntegerTree() ;
                  PushIntegerTree(hi) ;
                  PushInt(1) ;
                  Addn ;
                  i^.low := PopIntegerTree()
               END
            END
         ELSE
            h := h^.next
         END
      END
   END ;
   RETURN( set )
END SubBitRange ;


(*
   CheckLowHigh - checks to see the low value <= high value and issues an error
                  if this is not true.
*)

PROCEDURE CheckLowHigh (rp: RangePair) ;
VAR
   lo, hi: Tree ;
   temp  : CARDINAL ;
BEGIN
   lo := Mod2Gcc (rp^.low) ;
   hi := Mod2Gcc (rp^.high) ;
   IF IsGreater (lo, hi)
   THEN
      MetaErrorT2 (rp^.tokenno, 'case range should be low..high rather than high..low, range specified as {%1Euad}..{%2Euad}', rp^.low, rp^.high) ;
      temp := rp^.high ;
      rp^.high := rp^.low ;
      rp^.low := temp
   END
END CheckLowHigh ;


(*
   ExcludeCaseRanges - excludes all case ranges found in, p, from, set
*)

PROCEDURE ExcludeCaseRanges (set: SetRange; cd: CaseDescriptor) : SetRange ;
VAR
   i, j: CARDINAL ;
   cl  : CaseList ;
   rp  : RangePair ;
BEGIN
   WITH cd^ DO
      i := 1 ;
      WHILE i <= maxCaseId DO
         cl := GetIndice (caseListArray, i) ;
         j := 1 ;
         WHILE j <= cl^.maxRangeId DO
            rp := GetIndice (cl^.rangeArray, j) ;
            IF rp^.high = NulSym
            THEN
               set := SubBitRange (set,
                                   Mod2Gcc (rp^.low),
                                   Mod2Gcc (rp^.low), rp^.tokenno)
            ELSE
               CheckLowHigh (rp) ;
               set := SubBitRange (set,
                                   Mod2Gcc (rp^.low),
                                   Mod2Gcc (rp^.high), rp^.tokenno)
            END ;
            INC (j)
         END ;
         INC (i)
      END
   END ;
   RETURN set
END ExcludeCaseRanges ;


VAR
   errorString: String ;


(*
   IncludeElement -
*)

PROCEDURE IncludeElement (enumList: List; field: CARDINAL; low, high: Tree) ;
VAR
   fieldTree: Tree ;
BEGIN
   IF field # NulSym
   THEN
      fieldTree := Mod2Gcc (field) ;
      IF OverlapsRange (fieldTree, fieldTree, low, high)
      THEN
         IncludeItemIntoList (enumList, field)
      END
   END
END IncludeElement ;


(*
   IncludeElements -
*)

PROCEDURE IncludeElements (type: CARDINAL; enumList: List; low, high: Tree) ;
VAR
   field     : CARDINAL ;
   i,
   NoElements: CARDINAL ;
BEGIN
   NoElements := NoOfElements (type) ;
   i := 1 ;
   WHILE i <= NoElements DO
      field := GetNth (type, i) ;
      IncludeElement (enumList, field, low, high) ;
      INC (i)
   END
END IncludeElements ;


(*
   ErrorRangeEnum
*)

PROCEDURE ErrorRangeEnum (type: CARDINAL; set: SetRange; enumList: List) ;
VAR
   Low, High: Tree ;
BEGIN
   Low := set^.low ;
   High := set^.high ;
   IF Low = NIL
   THEN
      Low := High
   END ;
   IF High = NIL
   THEN
      High := Low
   END ;
   IF (Low # NIL) AND (High # NIL)
   THEN
      IncludeElements (type, enumList, Low, High)
   END
END ErrorRangeEnum ;


(*
   ErrorRanges - return a list of all enumeration fields not present in the case statement.
                 The return value will be nil if type is not an enumeration type.
*)

PROCEDURE ErrorRanges (type: CARDINAL; set: SetRange) : List ;
VAR
   enumSet: List ;
BEGIN
   type := SkipType (type) ;
   IF IsEnumeration (type)
   THEN
      InitList (enumSet) ;
      WHILE set#NIL DO
         ErrorRangeEnum (type, set, enumSet) ;
         set := set^.next
      END ;
      RETURN enumSet
   END ;
   RETURN NIL
END ErrorRanges ;


(*
   appendEnum -
*)

PROCEDURE appendEnum (enum: CARDINAL) ;
BEGIN
   errorString := ConCat (errorString,
                          Mark (InitStringCharStar (KeyToCharStar (GetSymName (enum)))))
END appendEnum ;


(*
   appendStr -
*)

PROCEDURE appendStr (str: ARRAY OF CHAR) ;
BEGIN
   errorString := ConCat (errorString, Mark (InitString (str)))
END appendStr ;


(*
   EnumerateErrors - populate errorString with the contents of enumList.
*)

PROCEDURE EnumerateErrors (enumList: List) ;
VAR
   i, n: CARDINAL ;
BEGIN
   n := NoOfItemsInList (enumList) ;
   IF (enumList # NIL) AND (n > 0)
   THEN
      IF n = 1
      THEN
         errorString := InitString ('{%W}the missing enumeration field is: ') ;
      ELSE
         errorString := InitString ('{%W}the missing enumeration fields are: ') ;
      END ;
      appendEnum (GetItemFromList (enumList, 1)) ;
      IF n > 1
      THEN
         IF n > 2
         THEN
            i := 2 ;
            WHILE i <= n-1 DO
               appendStr (', ') ;
               appendEnum (GetItemFromList (enumList, i)) ;
               INC (i)
            END
         END ;
         appendStr (' and ') ;
         appendEnum (GetItemFromList (enumList, n))
      END
   END
END EnumerateErrors ;


(*
   EmitMissingRangeErrors - emits a singular/plural error message for an enumeration type.
*)

PROCEDURE EmitMissingRangeErrors (tokenno: CARDINAL; type: CARDINAL; set: SetRange) ;
BEGIN
   errorString := NIL ;
   IF IsEnumeration (type)
   THEN
      EnumerateErrors (ErrorRanges (type, set))
   END ;
   IF errorString # NIL
   THEN
      MetaErrorStringT0 (tokenno, errorString)
   END
END EmitMissingRangeErrors ;


(*
   MissingCaseBounds - returns true if there were any missing bounds
                       in the varient record case list, c.  It will
                       generate an error message for each missing
                       bounds found.
*)

PROCEDURE MissingCaseBounds (tokenno: CARDINAL; c: CARDINAL) : BOOLEAN ;
VAR
   p      : CaseDescriptor ;
   type   : CARDINAL ;
   missing: BOOLEAN ;
   set    : SetRange ;
BEGIN
   p := GetIndice (caseArray, c) ;
   missing := FALSE ;
   WITH p^ DO
      IF NOT elseClause
      THEN
         IF (record # NulSym) AND (varient # NulSym)
         THEN
            (* Not a case statement, but a varient record without an else clause.  *)
            type := GetVariantTagType (varient) ;
            set := NewSet (type) ;
            set := ExcludeCaseRanges (set, p) ;
            IF set # NIL
            THEN
               missing := TRUE ;
               MetaErrorT2 (tokenno,
                            'not all variant record alternatives in the {%kCASE} clause are specified, hint you either need to specify each value of {%2ad} or use an {%kELSE} clause',
                            varient, type) ;
               EmitMissingRangeErrors (tokenno, type, set)
            END ;
            set := DisposeRanges (set)
         END
      END
   END ;
   RETURN missing
END MissingCaseBounds ;


(*
   MissingCaseStatementBounds - returns true if the case statement has a missing
                                clause.  It will also generate error messages.
*)

PROCEDURE MissingCaseStatementBounds (tokenno: CARDINAL; c: CARDINAL) : BOOLEAN ;
VAR
   p      : CaseDescriptor ;
   type   : CARDINAL ;
   missing: BOOLEAN ;
   set    : SetRange ;
BEGIN
   p := GetIndice (caseArray, c) ;
   missing := FALSE ;
   WITH p^ DO
      IF NOT elseClause
      THEN
         IF expression # NulSym
         THEN
            type := SkipType (GetType (expression)) ;
            IF (type # NulSym) AND IsEnumeration (type)
            THEN
               (* A case statement sequence without an else clause but
                  selecting using an enumeration type.  *)
               set := NewSet (type) ;
               set := ExcludeCaseRanges (set, p) ;
               IF set # NIL
               THEN
                  missing := TRUE ;
                  MetaErrorT1 (tokenno,
                               'not all enumeration values in the {%kCASE} statements are specified, hint you either need to specify each value of {%1Wad} or use an {%kELSE} clause',
                               type) ;
                  EmitMissingRangeErrors (tokenno, type, set)
               END ;
               set := DisposeRanges (set)
            END
         END
      END
   END ;
   RETURN missing
END MissingCaseStatementBounds ;


(*
   InRangeList - returns true if the value, tag, is defined in the case list.

procedure InRangeList (cl: CaseList; tag: cardinal) : boolean ;
var
   i, h: cardinal ;
   r   : RangePair ;
   a   : Tree ;
begin
   with cl^ do
      i := 1 ;
      h := HighIndice(rangeArray) ;
      while i<=h do
         r := GetIndice(rangeArray, i) ;
         with r^ do
            if high=NulSym
            then
               a := Mod2Gcc(low)
            else
               a := Mod2Gcc(high)
            end ;
            if OverlapsRange(Mod2Gcc(low), a, Mod2Gcc(tag), Mod2Gcc(tag))
            then
               return( true )
            end
         end ;
         inc(i)
      end
   end ;
   return( false )
end InRangeList ;
*)


(*
   WriteCase - dump out the case list (internal debugging).
*)

PROCEDURE WriteCase (c: CARDINAL) ;
BEGIN
   (* this debugging PROCEDURE should be finished.  *)
   printf1 ("%d", c)
END WriteCase ;


(*
   checkTypes - checks to see that, constant, and, type, are compatible.
*)

PROCEDURE checkTypes (constant, type: CARDINAL) : BOOLEAN ;
VAR
   consttype: CARDINAL ;
BEGIN
   IF (constant # NulSym) AND IsConst (constant)
   THEN
      consttype := GetType (constant) ;
      IF NOT IsExpressionCompatible (consttype, type)
      THEN
         MetaError2 ('the case statement variant tag {%1ad} must be type compatible with the constant {%2Da:is a {%2d}}',
                     type, constant) ;
         RETURN FALSE
      END
   END ;
   RETURN TRUE
END checkTypes ;


(*
   inRange - returns true if, min <= i <= max.
*)

PROCEDURE inRange (i, min, max: CARDINAL) : BOOLEAN ;
BEGIN
   RETURN OverlapsRange (Mod2Gcc (i), Mod2Gcc (i), Mod2Gcc (min), Mod2Gcc (max))
END inRange ;


(*
   TypeCaseBounds - returns true if all bounds in case list, c, are
                    compatible with the tagged type.
*)

PROCEDURE TypeCaseBounds (c: CARDINAL) : BOOLEAN ;
VAR
   p         : CaseDescriptor ;
   q         : CaseList ;
   r         : RangePair ;
   min, max,
   type,
   i, j      : CARDINAL ;
   compatible: BOOLEAN ;
BEGIN
   p := GetIndice(caseArray, c) ;
   type := NulSym ;
   WITH p^ DO
      type := NulSym ;
      IF varient#NulSym
      THEN
         (* not a CASE statement, but a varient record containing without an ELSE clause *)
         type := GetVariantTagType(varient) ;
         min := GetTypeMin(type) ;
         max := GetTypeMax(type)
      END ;
      IF type=NulSym
      THEN
         RETURN( TRUE )
      END ;
      compatible := TRUE ;
      i := 1 ;
      WHILE i<=maxCaseId DO
         q := GetIndice(caseListArray, i) ;
         j := 1 ;
         WHILE j<=q^.maxRangeId DO
            r := GetIndice(q^.rangeArray, j) ;
            IF (r^.low#NulSym) AND (NOT inRange(r^.low, min, max))
            THEN
               MetaError2('the CASE statement variant range {%1ad} exceeds that of the tag type {%2ad}',
                          r^.low, type) ;
               compatible := FALSE
            END ;
            IF NOT checkTypes(r^.low, type)
            THEN
               compatible := FALSE
            END ;
            IF (r^.high#NulSym) AND (NOT inRange(r^.high, min, max))
            THEN
               MetaError2('the CASE statement variant range {%1ad} exceeds that of the tag type {%2ad}',
                          r^.high, type) ;
               compatible := FALSE
            END ;
            IF NOT checkTypes(r^.high, type)
            THEN
               compatible := FALSE
            END ;
            INC (j)
         END ;
         INC (i)
      END ;
      RETURN compatible
   END
END TypeCaseBounds ;


BEGIN
   caseStack := NIL ;
   caseId := 0 ;
   caseArray := InitIndex(1) ;
   conflictArray := InitIndex(1) ;
   FreeRangeList := NIL
END M2CaseList.
