| (* M2CaseList.mod implement ISO case label lists. |
| |
| Copyright (C) 2009-2026 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, PushCard ; |
| FROM Indexing IMPORT Index, InitIndex, PutIndice, GetIndice, ForeachIndiceInIndexDo, HighIndice ; |
| FROM Lists IMPORT InitList, IncludeItemIntoList, RemoveItemFromList, NoOfItemsInList, GetItemFromList ; |
| FROM NameKey IMPORT NulName, KeyToCharStar ; |
| FROM SymbolConversion IMPORT GccKnowsAbout, Mod2Gcc, AddModGcc ; |
| FROM DynamicStrings IMPORT InitString, InitStringCharStar, InitStringChar, ConCat, Mark, KillString ; |
| FROM gcctypes IMPORT tree ; |
| FROM m2block IMPORT RememberType ; |
| FROM m2type IMPORT GetMinFrom ; |
| FROM m2expr IMPORT GetIntegerOne, CSTIntToString, CSTIntToChar ; |
| FROM Storage IMPORT ALLOCATE ; |
| FROM M2Base IMPORT IsExpressionCompatible, Char ; |
| FROM M2LexBuf IMPORT TokenToLocation ; |
| FROM NumberIO IMPORT WriteCard ; |
| |
| FROM SymbolTable IMPORT NulSym, IsConst, IsFieldVarient, IsRecord, IsRecordField, GetVarientTag, GetType, |
| ForeachLocalSymDo, GetSymName, IsEnumeration, SkipType, NoOfElements, GetNth, |
| IsSubrange, MakeConstLit, IsConstString, GetStringLength, MakeConstVar, PutConst, |
| PopValue ; |
| |
| 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 |
| resolved : BOOLEAN ; |
| 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 |
| resolved := FALSE ; |
| 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 |
| p: CaseDescriptor ; |
| BEGIN |
| p := GetIndice (caseArray, c) ; |
| IF p^.resolved |
| THEN |
| RETURN TRUE |
| ELSE |
| IF CheckCaseBoundsResolved (tokenno, c) |
| THEN |
| ConvertNulStr2NulChar (tokenno, c) ; |
| RETURN TRUE |
| ELSE |
| RETURN FALSE |
| END |
| END |
| END CaseBoundsResolved ; |
| |
| |
| (* |
| CheckCaseBoundsResolved - return TRUE if all constants in the case list c are known to GCC. |
| *) |
| |
| PROCEDURE CheckCaseBoundsResolved (tokenno: CARDINAL; c: CARDINAL) : BOOLEAN ; |
| VAR |
| 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 {%1dv}}', r^.low) |
| ELSE |
| MetaError1('the CASE statement variant low value in a range must be defined by a constant {%1Da:is a {%1dv}}', |
| 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 {%1dv}}', |
| r^.high) |
| END |
| END ; |
| INC(j) |
| END ; |
| INC(i) |
| END |
| END ; |
| RETURN( TRUE ) |
| END CheckCaseBoundsResolved ; |
| |
| |
| (* |
| ConvertNulStr2NulChar - |
| *) |
| |
| PROCEDURE ConvertNulStr2NulChar (tokenno: CARDINAL; c: CARDINAL) ; |
| VAR |
| p : CaseDescriptor ; |
| q : CaseList ; |
| r : RangePair ; |
| i, j: CARDINAL ; |
| BEGIN |
| p := GetIndice (caseArray, c) ; |
| 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) ; |
| r^.low := NulStr2NulChar (tokenno, r^.low) ; |
| r^.high := NulStr2NulChar (tokenno, r^.high) ; |
| INC (j) |
| END ; |
| INC (i) |
| END |
| END |
| END ConvertNulStr2NulChar ; |
| |
| |
| (* |
| NulStr2NulChar - if sym is a const string of length 0 then return |
| a nul char instead otherwise return sym. |
| *) |
| |
| PROCEDURE NulStr2NulChar (tok: CARDINAL; sym: CARDINAL) : CARDINAL ; |
| BEGIN |
| IF sym # NulSym |
| THEN |
| IF IsConst (sym) AND IsConstString (sym) AND GccKnowsAbout (sym) |
| THEN |
| IF GetStringLength (tok, sym) = 0 |
| THEN |
| sym := MakeConstVar (tok, NulName) ; |
| PutConst (sym, Char) ; |
| PushCard (0) ; |
| PopValue (sym) ; |
| TryDeclareConstant (tok, sym) ; |
| Assert (GccKnowsAbout (sym)) |
| END |
| END |
| END ; |
| RETURN sym |
| END NulStr2NulChar ; |
| |
| |
| (* |
| 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 ; |
| |
| |
| (* |
| GetCaseExpression - return the type from the expression. |
| *) |
| |
| PROCEDURE GetCaseExpression (p: CaseDescriptor) : CARDINAL ; |
| VAR |
| type: CARDINAL ; |
| BEGIN |
| WITH p^ DO |
| IF expression = NulSym |
| THEN |
| type := NulSym |
| ELSE |
| type := SkipType (GetType (expression)) |
| END |
| END ; |
| RETURN type |
| END GetCaseExpression ; |
| |
| |
| (* |
| 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 - return a new range from the freelist or heap. |
| *) |
| |
| 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 - returns a new set based on type with the low and high fields assigned |
| to the min and max values for the type. |
| *) |
| |
| 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 - place set and its list onto the free list. |
| *) |
| |
| 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 - only include enumeration field into errorString if it lies between low..high. |
| *) |
| |
| 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 - only include enumeration field values low..high in errorString. |
| *) |
| |
| 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 - include enumeration fields Low to High in errorString. |
| *) |
| |
| 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 ; |
| |
| |
| (* |
| appendString - appends str to errorString. |
| *) |
| |
| PROCEDURE appendString (str: String) ; |
| BEGIN |
| errorString := ConCat (errorString, str) |
| END appendString ; |
| |
| |
| (* |
| appendEnum - appends enum to errorString. |
| *) |
| |
| PROCEDURE appendEnum (enum: CARDINAL) ; |
| BEGIN |
| appendString (Mark (InitStringCharStar (KeyToCharStar (GetSymName (enum))))) |
| END appendEnum ; |
| |
| |
| (* |
| appendStr - appends str to errorString. |
| *) |
| |
| PROCEDURE appendStr (str: ARRAY OF CHAR) ; |
| BEGIN |
| appendString (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 ; |
| |
| |
| (* |
| NoOfSetElements - return the number of set elements. |
| *) |
| |
| PROCEDURE NoOfSetElements (set: SetRange) : tree ; |
| BEGIN |
| PushInt (0) ; |
| WHILE set # NIL DO |
| IF ((set^.low # NIL) AND (set^.high = NIL)) OR |
| ((set^.low = NIL) AND (set^.high # NIL)) |
| THEN |
| PushInt (1) ; |
| Addn |
| ELSIF (set^.low # NIL) AND (set^.high # NIL) |
| THEN |
| PushIntegerTree (set^.high) ; |
| PushIntegerTree (set^.low) ; |
| Sub ; |
| PushInt (1) ; |
| Addn ; |
| Addn |
| END ; |
| set := set^.next |
| END ; |
| RETURN PopIntegerTree () |
| END NoOfSetElements ; |
| |
| |
| (* |
| isPrintableChar - a cautious isprint. |
| *) |
| |
| PROCEDURE isPrintableChar (value: tree) : BOOLEAN ; |
| BEGIN |
| CASE CSTIntToChar (value) OF |
| |
| 'a'..'z': RETURN TRUE | |
| 'A'..'Z': RETURN TRUE | |
| '0'..'9': RETURN TRUE | |
| '!', '@': RETURN TRUE | |
| '#', '$': RETURN TRUE | |
| '%', '^': RETURN TRUE | |
| '&', '*': RETURN TRUE | |
| '(', ')': RETURN TRUE | |
| '[', ']': RETURN TRUE | |
| '{', '}': RETURN TRUE | |
| '-', '+': RETURN TRUE | |
| '_', '=': RETURN TRUE | |
| ':', ';': RETURN TRUE | |
| "'", '"': RETURN TRUE | |
| ',', '.': RETURN TRUE | |
| '<', '>': RETURN TRUE | |
| '/', '?': RETURN TRUE | |
| '\', '|': RETURN TRUE | |
| '~', '`': RETURN TRUE | |
| ' ' : RETURN TRUE |
| |
| ELSE |
| RETURN FALSE |
| END |
| END isPrintableChar ; |
| |
| |
| (* |
| appendTree - append tree value to the errorString. It attempts to pretty print |
| CHAR constants and will fall back to CHR (x) if necessary. |
| *) |
| |
| PROCEDURE appendTree (value: tree; type: CARDINAL) ; |
| BEGIN |
| IF SkipType (GetType (type)) = Char |
| THEN |
| IF isPrintableChar (value) |
| THEN |
| IF CSTIntToChar (value) = "'" |
| THEN |
| appendString (InitStringChar ('"')) ; |
| appendString (InitStringChar (CSTIntToChar (value))) ; |
| appendString (InitStringChar ('"')) |
| ELSE |
| appendString (InitStringChar ("'")) ; |
| appendString (InitStringChar (CSTIntToChar (value))) ; |
| appendString (InitStringChar ("'")) |
| END |
| ELSE |
| appendString (InitString ('CHR (')) ; |
| appendString (InitStringCharStar (CSTIntToString (value))) ; |
| appendString (InitStringChar (')')) |
| END |
| ELSE |
| appendString (InitStringCharStar (CSTIntToString (value))) |
| END |
| END appendTree ; |
| |
| |
| (* |
| SubrangeErrors - create an errorString containing all set ranges. |
| *) |
| |
| PROCEDURE SubrangeErrors (subrangetype: CARDINAL; set: SetRange) ; |
| VAR |
| sr : SetRange ; |
| rangeNo : CARDINAL ; |
| nMissing, |
| zero, one: tree ; |
| BEGIN |
| nMissing := NoOfSetElements (set) ; |
| PushInt (0) ; |
| zero := PopIntegerTree () ; |
| IF IsGreater (nMissing, zero) |
| THEN |
| PushInt (1) ; |
| one := PopIntegerTree () ; |
| IF IsGreater (nMissing, one) |
| THEN |
| errorString := InitString ('{%W}there are a total of ') |
| ELSE |
| errorString := InitString ('{%W}there is a total of ') |
| END ; |
| appendString (InitStringCharStar (CSTIntToString (nMissing))) ; |
| appendStr (' missing values in the subrange, the {%kCASE} statement needs labels (or an {%kELSE} statement)') ; |
| appendStr (' for the following values: ') ; |
| sr := set ; |
| rangeNo := 0 ; |
| WHILE sr # NIL DO |
| INC (rangeNo) ; |
| IF rangeNo > 1 |
| THEN |
| IF sr^.next = NIL |
| THEN |
| appendStr (' and ') |
| ELSE |
| appendStr (', ') |
| END |
| END ; |
| IF sr^.low = NIL |
| THEN |
| appendTree (sr^.high, subrangetype) |
| ELSIF (sr^.high = NIL) OR IsEqual (sr^.low, sr^.high) |
| THEN |
| appendTree (sr^.low, subrangetype) |
| ELSE |
| appendTree (sr^.low, subrangetype) ; |
| appendStr ('..') ; |
| appendTree (sr^.high, subrangetype) |
| END ; |
| sr := sr^.next |
| END |
| END |
| END SubrangeErrors ; |
| |
| |
| (* |
| 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)) |
| ELSIF IsSubrange (type) |
| THEN |
| SubrangeErrors (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 |
| type := GetCaseExpression (p) ; |
| IF type # NulSym |
| THEN |
| IF IsEnumeration (type) OR IsSubrange (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 {%1Wd} values in the {%kCASE} statements are specified, hint you either need to specify each value of {%1ad} 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. *) |
| WriteCard (c, 0) |
| 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 {%2dv}}', |
| 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. |