(* M2CaseList.mod implement ISO case label lists. Copyright (C) 2009-2023 Free Software Foundation, Inc. Contributed by Gaius Mulley . 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 . *) IMPLEMENTATION MODULE M2CaseList ; FROM M2Debug IMPORT Assert ; FROM M2GCCDeclare IMPORT TryDeclareConstant, GetTypeMin, GetTypeMax ; FROM M2MetaError IMPORT MetaError1, MetaError2, MetaErrorT0, MetaErrorT1, MetaErrorT2, MetaErrorT3, MetaErrorT4, 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 ; 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 Storage IMPORT ALLOCATE ; FROM M2Base IMPORT IsExpressionCompatible ; FROM M2Printf IMPORT printf1 ; FROM SymbolTable IMPORT NulSym, IsConst, IsFieldVarient, IsRecord, IsRecordField, GetVarientTag, GetType, ForeachLocalSymDo, GetSymName, IsEnumeration, SkipType ; 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 ; 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. r, is NulSym if this is a CASE statement. If, r, is a record then it indicates it includes one or more varients reside in the record. The particular varient is, v. Return the case id. *) PROCEDURE PushCase (r: CARDINAL; v: 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 := r ; varient := v ; 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 ; (* 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 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 IF h=set THEN set := set^.next ; h^.next := NIL ; h := DisposeRanges(h) ; h := set 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 ELSE h := h^.next END ELSE 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 ; (* ExcludeCaseRanges - excludes all case ranges found in, p, from, set *) PROCEDURE ExcludeCaseRanges (set: SetRange; p: CaseDescriptor) : SetRange ; VAR i, j: CARDINAL ; q : CaseList ; r : RangePair ; BEGIN 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 r^.high=NulSym THEN set := SubBitRange(set, Mod2Gcc(r^.low), Mod2Gcc(r^.low), r^.tokenno) ELSE set := SubBitRange(set, Mod2Gcc(r^.low), Mod2Gcc(r^.high), r^.tokenno) END ; INC(j) END ; INC(i) END END ; RETURN( set ) END ExcludeCaseRanges ; VAR High, Low : Tree ; errorString: String ; (* DoEnumValues - *) PROCEDURE DoEnumValues (sym: CARDINAL) ; BEGIN IF (Low#NIL) AND IsEqual(Mod2Gcc(sym), Low) THEN errorString := ConCat(errorString, InitStringCharStar(KeyToCharStar(GetSymName(sym)))) ; Low := NIL END ; IF (High#NIL) AND IsEqual(Mod2Gcc(sym), High) THEN errorString := ConCat(errorString, Mark(InitString('..'))) ; errorString := ConCat(errorString, Mark(InitStringCharStar(KeyToCharStar(GetSymName(sym))))) ; High := NIL END END DoEnumValues ; (* ErrorRange - *) PROCEDURE ErrorRange (p: CaseDescriptor; type: CARDINAL; set: SetRange) ; BEGIN type := SkipType(type) ; IF IsEnumeration(type) THEN Low := set^.low ; High := set^.high ; IF IsEqual(Low, High) THEN High := NIL ; errorString := InitString('enumeration value ') ; ForeachLocalSymDo(type, DoEnumValues) ; errorString := ConCat(errorString, InitString(' is ignored by the CASE variant record {%1D}')) ELSE errorString := InitString('enumeration values ') ; ForeachLocalSymDo(type, DoEnumValues) ; errorString := ConCat(errorString, InitString(' are ignored by the CASE variant record {%1D}')) END ; MetaErrorString1(errorString, p^.varient) END END ErrorRange ; (* ErrorRanges - *) PROCEDURE ErrorRanges (p: CaseDescriptor; type: CARDINAL; set: SetRange) ; BEGIN WHILE set#NIL DO ErrorRange(p, type, set) ; set := set^.next END END ErrorRanges ; (* 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 (record#NulSym) AND (varient#NulSym) AND (NOT elseClause) THEN (* not a CASE statement, but a varient record containing 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) ; ErrorRanges(p, type, set) END ; set := DisposeRanges(set) END END ; RETURN( missing ) END MissingCaseBounds ; (* 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.