(* nameKey.mod provides a dynamic binary tree name to key.

Copyright (C) 2015-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius@glam.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 nameKey ;


FROM SYSTEM IMPORT ADR ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM Indexing IMPORT Index, InitIndex, GetIndice, PutIndice, InBounds ;
FROM StrIO IMPORT WriteString, WriteLn ;
FROM StdIO IMPORT Write ;
FROM NumberIO IMPORT WriteCard ;
FROM StrLib IMPORT StrLen ;
FROM libc IMPORT strlen ;
FROM ASCII IMPORT nul ;


TYPE
   ptrToChar  = POINTER TO CHAR ;

   nameNode   = POINTER TO RECORD
                   data : ptrToChar ;
                   key  : Name ;
                   left,
                   right: nameNode ;
                END ;

   comparison = (less, equal, greater) ;

VAR
   binaryTree: nameNode ;
   keyIndex  : Index ;
   lastIndice: CARDINAL ;


(*
   getKey - returns the name, a, of the key, Key.
*)

PROCEDURE getKey (key: Name; VAR a: ARRAY OF CHAR) ;
VAR
   p       : ptrToChar ;
   i, higha: CARDINAL ;
BEGIN
   p := keyToCharStar (key) ;
   i := 0 ;
   higha := HIGH (a) ;
   WHILE (p#NIL) AND (i<=higha) AND (p^#nul) DO
      a[i] := p^ ;
      INC (p) ;
      INC (i)
   END ;
   IF i<=higha
   THEN
      a[i] := nul
   END
END getKey ;


(*
   isKey - returns TRUE if string, a, is currently a key.
           We dont use the Compare function, we inline it and avoid
           converting, a, into a String, for speed.
*)

PROCEDURE isKey (a: ARRAY OF CHAR) : BOOLEAN ;
VAR
   child : nameNode ;
   p     : ptrToChar ;
   i,
   higha : CARDINAL ;
BEGIN
   (* firstly set up the initial values of child, using sentinal node *)
   child := binaryTree^.left ;
   IF child#NIL
   THEN
      REPEAT
         i := 0 ;
         higha := HIGH (a) ;
         p := keyToCharStar (child^.key) ;
         WHILE (i<=higha) AND (a[i]#nul) DO
            IF a[i]<p^
            THEN
               child := child^.left ;
               i := higha
            ELSIF a[i]>p^
            THEN
               child := child^.right ;
               i := higha
            ELSE
               IF (a[i]=nul) OR (i=higha)
               THEN
                  IF p^=nul
                  THEN
                     RETURN TRUE
                  ELSE
                     child := child^.left
                  END
               END ;
               INC (p)
            END ;
            INC (i)
         END ;
      UNTIL child=NIL
   END ;
   RETURN FALSE
END isKey ;


(*
   doMakeKey - finds the name, n, in the tree or else create a name.
               If a name is found then the string, n, is deallocated.
*)

PROCEDURE doMakeKey (n: ptrToChar; higha: CARDINAL) : Name ;
VAR
   result: comparison ;
   father,
   child : nameNode ;
   k     : Name ;
BEGIN
   result := findNodeAndParentInTree (n, child, father) ;
   IF child=NIL
   THEN
      IF result=less
      THEN
         NEW (child) ;
         father^.left := child
      ELSIF result=greater
      THEN
         NEW (child) ;
         father^.right := child
      END ;
      WITH child^ DO
         right := NIL ;
         left := NIL ;
         INC (lastIndice) ;
         key := lastIndice ;
         data := n ;
         PutIndice (keyIndex, key, n)
      END ;
      k := lastIndice
   ELSE
      DEALLOCATE (n, higha+1) ;
      k := child^.key
   END ;
   RETURN k
END doMakeKey ;


(*
   makeKey - returns the Key of the symbol, a. If a is not in the
             name table then it is added, otherwise the Key of a is returned
             directly. Note that the name table has no scope - it merely
             presents a more convienient way of expressing strings. By a Key.
*)

PROCEDURE makeKey (a: ARRAY OF CHAR) : Name ;
VAR
   n, p  : ptrToChar ;
   i,
   higha : CARDINAL ;
BEGIN
   higha := StrLen(a) ;
   ALLOCATE (p, higha+1) ;
   IF p=NIL
   THEN
      HALT      (* out of memory error *)
   ELSE
      n := p ;
      i := 0 ;
      WHILE i<higha DO
         p^ := a[i] ;
         INC(i) ;
         INC(p)
      END ;
      p^ := nul ;

      RETURN doMakeKey (n, higha)
   END
END makeKey ;


(*
   makekey - returns the Key of the symbol, a. If a is not in the
             name table then it is added, otherwise the Key of a is returned
             directly. Note that the name table has no scope - it merely
             presents a more convienient way of expressing strings. By a Key.
             These keys last for the duration of compilation.
*)

PROCEDURE makekey (a: ADDRESS) : Name ;
VAR
   n,
   p, pa : ptrToChar ;
   i,
   higha : CARDINAL ;
BEGIN
   IF a=NIL
   THEN
      RETURN NulName
   ELSE
      higha := strlen (a) ;
      ALLOCATE (p, higha+1) ;
      IF p=NIL
      THEN
         HALT      (* out of memory error *)
      ELSE
         n  := p ;
         pa := a ;
         i  := 0 ;
         WHILE i<higha DO
            p^ := pa^ ;
            INC (i) ;
            INC (p) ;
            INC (pa)
         END ;
         p^ := nul ;

         RETURN doMakeKey (n, higha)
      END
   END
END makekey ;


(*
   lengthKey - returns the StrLen of Key.
*)

PROCEDURE lengthKey (key: Name) : CARDINAL ;
VAR
   i: CARDINAL ;
   p: ptrToChar ;
BEGIN
   p := keyToCharStar (key) ;
   i := 0 ;
   WHILE p^#nul DO
      INC (i) ;
      INC (p)
   END ;
   RETURN i
END lengthKey ;


(*
   compare - return the result of Names[i] with Names[j]
*)

PROCEDURE compare (pi: ptrToChar; j: Name) : comparison ;
VAR
   pj: ptrToChar ;
   c1, c2: CHAR ;
BEGIN
   pj := keyToCharStar(j) ;
   c1 := pi^ ;
   c2 := pj^ ;
   WHILE (c1#nul) OR (c2#nul) DO
      IF c1<c2
      THEN
         RETURN less
      ELSIF c1>c2
      THEN
         RETURN greater
      ELSE
         INC (pi) ;
         INC (pj) ;
         c1 := pi^ ;
         c2 := pj^
      END
   END ;
   RETURN equal
END compare ;


(*
   findNodeAndParentInTree - search BinaryTree for a name.
                             If this name is found in the BinaryTree then
                             child is set to this name and father is set to the node above.
                             A comparison is returned to assist adding entries into this tree.
*)

PROCEDURE findNodeAndParentInTree (n: ptrToChar; VAR child, father: nameNode) : comparison ;
VAR
   result: comparison ;
BEGIN
   (* firstly set up the initial values of child and father, using sentinal node *)
   father := binaryTree ;
   child := binaryTree^.left ;
   IF child=NIL
   THEN
      RETURN less
   ELSE
      REPEAT
         result := compare (n, child^.key) ;
         IF result=less
         THEN
            father := child ;
            child := child^.left
         ELSIF result=greater
         THEN
            father := child ;
            child := child^.right
         END
      UNTIL (child=NIL) OR (result=equal) ;
      RETURN result
   END
END findNodeAndParentInTree ;


(*
   isSameExcludingCase - returns TRUE if key1 and key2 are
                         the same. It is case insensitive.
                         This function deliberately inlines CAP for speed.
*)

PROCEDURE isSameExcludingCase (key1, key2: Name) : BOOLEAN ;
VAR
   pi, pj: ptrToChar ;
   c1, c2: CHAR ;
BEGIN
   IF key1=key2
   THEN
      RETURN TRUE
   ELSE
      pi := keyToCharStar(key1) ;
      pj := keyToCharStar(key2) ;
      c1 := pi^ ;
      c2 := pj^ ;
      WHILE (c1#nul) AND (c2#nul) DO
         IF (c1=c2) OR
            (((c1>='A') AND (c1<='Z')) AND (c2=CHR(ORD(c1)-ORD('A')+ORD('a')))) OR
            (((c2>='A') AND (c2<='Z')) AND (c1=CHR(ORD(c2)-ORD('A')+ORD('a'))))
         THEN
            INC (pi) ;
            INC (pj) ;
            c1 := pi^ ;
            c2 := pj^
         ELSE
            (* difference found *)
            RETURN FALSE
         END
      END ;
      RETURN c1=c2
   END
END isSameExcludingCase ;


(*
   keyToCharStar - returns the C char * string equivalent for, key.
*)

PROCEDURE keyToCharStar (key: Name) : ADDRESS ;
BEGIN
   IF (key=NulName) OR (NOT InBounds (keyIndex, key))
   THEN
      RETURN NIL
   ELSE
      RETURN GetIndice (keyIndex, key)
   END
END keyToCharStar ;


PROCEDURE writeKey (key: Name) ;
VAR
   s: ptrToChar ;
BEGIN
   s := keyToCharStar (key) ;
   WHILE (s#NIL) AND (s^#nul) DO
      Write (s^) ;
      INC (s)
   END
END writeKey ;


BEGIN
   lastIndice := 0 ;
   keyIndex := InitIndex(1) ;
   NEW (binaryTree) ;
   binaryTree^.left := NIL
END nameKey.