;;;; -*- mode:lisp;coding:utf-8 -*-
;;;;**************************************************************************
;;;;FILE:               tree.lisp
;;;;LANGUAGE:           Common-Lisp
;;;;SYSTEM:             Common-Lisp
;;;;USER-INTERFACE:     NONE
;;;;DESCRIPTION
;;;;
;;;;    Implements a package tree index, for hierarchical package index.
;;;;
;;;;AUTHORS
;;;;    <PJB> Pascal J. Bourguignon <pjb@informatimago.com>
;;;;MODIFICATIONS
;;;;    2015-08-04 <PJB> Extracted from lispdoc.lisp.
;;;;BUGS
;;;;LEGAL
;;;;    LLGPL
;;;;
;;;;    Copyright Pascal J. Bourguignon 2015 - 2016
;;;;
;;;;    This library is licenced under the Lisp Lesser General Public
;;;;    License.
;;;;
;;;;    This library is free software; you can redistribute it and/or
;;;;    modify it under the terms of the GNU Lesser General Public
;;;;    License as published by the Free Software Foundation; either
;;;;    version 2 of the License, or (at your option) any later
;;;;    version.
;;;;
;;;;    This library 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 Lesser General Public License for more
;;;;    details.
;;;;
;;;;    You should have received a copy of the GNU Lesser General
;;;;    Public License along with this library; if not, write to the
;;;;    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
;;;;    Boston, MA 02111-1307 USA
;;;;**************************************************************************
(eval-when (:compile-toplevel :load-toplevel :execute)
  (setf *readtable* (copy-readtable nil)))
(in-package "COM.INFORMATIMAGO.LISPDOC.TREE")


(defvar *hierarchical-package-separator* #\.
  "The character used to separate package 'components' in hierarchical package names.
It's usually a dot, but some may use a different character such as a slash.")


(defun package-path (package)
  "

RETURN: A list of component strings representing the name of the
        PACKAGE, assuming it is structured as a list of components
        joined by the *HIERARCHICAL-PACKAGE-SEPARATOR*.

EXAMPLE: (package-path  \"COM.INFORMATIMAGO.LISPDOC.TREE\")
         --> (\"COM\" \"INFORMATIMAGO\" \"LISPDOC\" \"TREE\")

"
  (split-sequence *hierarchical-package-separator*
                  (etypecase package
                    (string package)
                    (package (package-name package)))))




(defstruct (tree
            (:copier tree-copy))
  "
A node in the package hierarchical naming tree.

PARENT:   a reference to the parent node, or NIL for the root.
NODE:     the string component naming this node.
PACKAGE:  the joined hierarchical package name of the package designated by this node, or NIL if none.
CHILDREN: the subtrees.
"
  parent
  node
  package
  (children '()))


(defmethod print-object ((tree tree) stream)
  (print-unreadable-object (tree stream :identity t :type t)
    (format stream "~S" (list :node (tree-node tree)
                              :package (tree-package tree)
                              :children (length (tree-children tree)))))
  tree)


(defun tree-children-named (tree node)
  "
RETURN: the child in the TREE named by NODE.
"
  (find node (tree-children tree)
        :key (function tree-node)
        :test (function equal)))


(defun tree-add-node-at-path (tree path pname)
  "
DO:     Add a new tree node in the TREE for the package named PNAME at
        the given relative PATH.

RETURN: tree.
"
  (if (endp path)
      (progn
        (setf (tree-package tree) pname)
        tree)
      (let ((child (tree-children-named tree (first path))))
        (if child
            (tree-add-node-at-path child (rest path) pname)
            (let ((new-child (make-tree :parent tree
                                        :node (first path)
                                        :package (when (endp (rest path))
                                                   pname))))
              (appendf (tree-children tree) (list new-child))
              (tree-add-node-at-path new-child (rest path) pname))))))


(defun make-index-tree (package-names)
  "
RETURN: a new tree filled with nodes for all the PACKAGE-NAMES.
"
  (let ((root (make-tree)))
    (dolist (pname package-names root)
     (tree-add-node-at-path root (package-path pname) pname))))


(defun tree-node-at-path (tree path)
  "
RETURN: The tree node found at the given PATH.
"
  (if (endp path)
      tree
      (let ((child (tree-children-named tree (first path))))
        (when child
          (tree-node-at-path child (rest path))))))


(defun tree-path (tree)
  "
RETURN: The path from TREE to the root.
"
  (cons (tree-node tree) (when (and (tree-parent tree)
                                    (tree-node (tree-parent tree)))
                           (tree-path (tree-parent tree)))))


;;;; THE END ;;;;
ViewGit