(* symbolKey.mod provides binary tree operations for storing symbols.

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 symbolKey ;


FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM StrIO IMPORT WriteString, WriteLn ;
FROM NumberIO IMPORT WriteCard ;
FROM Debug IMPORT Halt ;

FROM nameKey IMPORT writeKey ;


TYPE
   symbolTree = POINTER TO RECORD
                   name : Name ;   (* The sorted entity *)
                   key  : ADDRESS ;   (* The value entity  *)
                   left,
                   right: symbolTree ;
                END ;


PROCEDURE initTree () : symbolTree ;
VAR
   t: symbolTree ;
BEGIN
   NEW (t) ;
   WITH t^ DO
      left := NIL ;
      right := NIL
   END ;
   RETURN t
END initTree ;


PROCEDURE killTree (VAR t: symbolTree) ;
BEGIN
   IF t#NIL
   THEN
      killTree (t^.left) ;
      killTree (t^.right) ;
      DISPOSE (t) ;
      t := NIL
   END
END killTree ;


PROCEDURE getSymKey (t: symbolTree; name: Name) : ADDRESS ;
VAR
   father,
   child : symbolTree ;
BEGIN
   IF t=NIL
   THEN
      RETURN NulKey
   ELSE
      findNodeAndParentInTree (t, name, child, father) ;
      IF child=NIL
      THEN
         RETURN NulKey
      ELSE
         RETURN child^.key
      END
   END
END getSymKey ;


PROCEDURE putSymKey (t: symbolTree; name: Name; key: ADDRESS) ;
VAR
   father,
   child : symbolTree ;
BEGIN
   findNodeAndParentInTree (t, name, child, father) ;
   IF child=NIL
   THEN
      (* no child found, now is name less than father or greater? *)
      IF father=t
      THEN
         (* empty tree, add it to the left branch of t *)
         NEW(child) ;
         father^.left := child
      ELSE
         IF name<father^.name
         THEN
            NEW (child) ;
            father^.left := child
         ELSIF name>father^.name
         THEN
            NEW (child) ;
            father^.right := child
         END
      END ;
      WITH child^ DO
         right   := NIL ;
         left    := NIL
      END ;
      child^.key  := key ;
      child^.name := name
   ELSE
      Halt ('symbol already stored', __FILE__, __FUNCTION__, __LINE__)
   END
END putSymKey ;


(*
   delSymKey - deletes an entry in the binary tree.

               NB in order for this to work we must ensure that the InitTree sets
               both left and right to NIL.
*)

PROCEDURE delSymKey (t: symbolTree; name: Name) ;
VAR
   i, child, father: symbolTree ;
BEGIN
   findNodeAndParentInTree (t, name, child, father) ;  (* find father and child of the node *)
   IF (child#NIL) AND (child^.name=name)
   THEN
      (* Have found the node to be deleted *)
      IF father^.right=child
      THEN
         (* Node is child and this is greater than the father. *)
         (* Greater being on the right.                        *)
         (* Connect child^.left onto the father^.right.        *)
         (* Connect child^.right onto the end of the right     *)
         (* most branch of child^.left.                        *)
         IF child^.left#NIL
         THEN
            (* Scan for right most node of child^.left *)
            i := child^.left ;
            WHILE i^.right#NIL DO
               i := i^.right
            END ;
            i^.right      := child^.right ;
            father^.right := child^.left
         ELSE
            (* No child^.left node therefore link over child   *)
            (* (as in a single linked list) to child^.right    *)
            father^.right := child^.right
         END ;
         DISPOSE (child)
      ELSE
         (* Assert that father^.left=child will always be true *)
         (* Perform exactly the mirror image of the above code *)

         (* Connect child^.right onto the father^.left.        *)
         (* Connect child^.left onto the end of the left most  *)
         (* branch of child^.right                             *)
         IF child^.right#NIL
         THEN
            (* Scan for left most node of child^.right *)
            i := child^.right ;
            WHILE i^.left#NIL DO
               i := i^.left
            END ;
            i^.left      := child^.left ;
            father^.left := child^.right
         ELSE
            (* No child^.right node therefore link over c      *)
            (* (as in a single linked list) to child^.left.    *)
            father^.left := child^.left
         END ;
         DISPOSE (child)
      END
   ELSE
      Halt ('trying to delete a symbol that is not in the tree - the compiler never expects this to occur',
            __FILE__, __FUNCTION__, __LINE__)
   END
END delSymKey ;


(*
   findNodeAndParentInTree - find a node, child, in a binary tree, t, with name equal to n.
                             if an entry is found, father is set to the node above child.
*)

PROCEDURE findNodeAndParentInTree (t: symbolTree; n: Name;
                                   VAR child, father: symbolTree) ;
BEGIN
   (* remember to skip the sentinal value and assign father and child *)
   father := t ;
   IF t=NIL
   THEN
      Halt ('parameter t should never be NIL', __FILE__, __FUNCTION__, __LINE__)
   END ;
   child := t^.left ;
   IF child#NIL
   THEN
      REPEAT
         IF n<child^.name
         THEN
            father := child ;
            child := child^.left
         ELSIF n>child^.name
         THEN
            father := child ;
            child := child^.right
         END
      UNTIL (child=NIL) OR (n=child^.name)
   END
END findNodeAndParentInTree ;


(*
   isEmptyTree - returns true if symbolTree, t, is empty.
*)

PROCEDURE isEmptyTree (t: symbolTree) : BOOLEAN ;
BEGIN
   RETURN t^.left=NIL
END isEmptyTree ;


(*
   doesTreeContainAny - returns true if symbolTree, t, contains any
                        symbols which in turn return true when procedure,
                        p, is called with a symbol as its parameter.
                        The symbolTree root is empty apart from the field,
                        left, hence we need two procedures.
*)

PROCEDURE doesTreeContainAny (t: symbolTree; p: isSymbol) : BOOLEAN ;
BEGIN
   RETURN searchForAny (t^.left, p)
END doesTreeContainAny ;


(*
   searchForAny - performs the search required for doesTreeContainAny.
                  The root node always contains a nul data value,
                  therefore we must skip over it.
*)

PROCEDURE searchForAny (t: symbolTree; p: isSymbol) : BOOLEAN ;
BEGIN
   IF t=NIL
   THEN
      RETURN FALSE
   ELSE
      RETURN p (t^.key) OR
             searchForAny (t^.left, p) OR
             searchForAny (t^.right, p)
   END
END searchForAny ;


(*
   foreachNodeDo - for each node in symbolTree, t, a procedure, p,
                   is called with the node symbol as its parameter.
                   The tree root node only contains a legal left pointer,
                   therefore we need two procedures to examine this tree.
*)

PROCEDURE foreachNodeDo (t: symbolTree; p: performOperation) ;
BEGIN
   searchAndDo (t^.left, p)
END foreachNodeDo ;


(*
   searchAndDo - searches all the nodes in symbolTree, t, and
                 calls procedure, p, with a node as its parameter.
                 It traverse the tree in order.
*)

PROCEDURE searchAndDo (t: symbolTree; p: performOperation) ;
BEGIN
   IF t#NIL
   THEN
      WITH t^ DO
         searchAndDo (right, p) ;
         p (key) ;
         searchAndDo (left, p)
      END
   END
END searchAndDo ;


END symbolKey.