diff lisp/oobr/br-clos-ft.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 4103f0995bd7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/oobr/br-clos-ft.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,577 @@
+;;!emacs
+;;
+;; FILE:         br-clos-ft.el
+;; SUMMARY:      CLOS OO-Browser class and element functions.
+;; USAGE:        GNU Emacs Lisp Library
+;; KEYWORDS:     lisp, oop, tools
+;;
+;; AUTHOR:       Bob Weiner
+;; ORG:          Motorola Inc.
+;;
+;; ORIG-DATE:    03-Oct-90
+;; LAST-MOD:      6-Aug-95 at 01:52:28 by Bob Weiner
+;;
+;; Copyright (C) 1990-1995  Free Software Foundation, Inc.
+;; See the file BR-COPY for license information.
+;;
+;; This file is part of the OO-Browser.
+;;
+;; DESCRIPTION:  
+;; DESCRIP-END.
+
+;;; ************************************************************************
+;;; Other required Elisp libraries
+;;; ************************************************************************
+
+(mapcar 'require '(br-clos set))
+
+;;; ************************************************************************
+;;; Public variables
+;;; ************************************************************************
+
+(defconst clos-type-identifier
+  (concat "[" clos-type-identifier-chars "]+"))
+
+(defconst clos-type-tag-separator ","
+  "String that separates a tags type from its normalized definition form.")
+
+(defconst clos-def-form-match "\([^ \t\n\r]+[ \t\n\r]+")
+
+(defconst clos-feature-tag-regexp
+  (concat "\\(" clos-type-identifier "\\)"
+	  clos-type-tag-separator
+	  clos-def-form-match "['\(]?"
+	  "\\((setf[^\)]+)\\|[^\(;,]+\\)\\( *(.*)\\)?")
+  "Regexp matching a fully qualified, normalized clos feature tag.
+Class name is grouping 1.  Feature name is grouping 2.  Optional
+argument list (aliased features don't have one) is grouping 3.")
+
+;;; ************************************************************************
+;;; Public functions
+;;; ************************************************************************
+
+(defun clos-add-default-classes ()
+  ;; Add to 'system' class table.
+  (let ((classes (set:create (mapcar 'cdr clos-element-type-alist))))
+    ;; Methods are broken out into individual classes, so don't add "method"
+    ;; as a default class.
+    (setq classes (set:remove "method" classes))
+    (mapcar
+     (function (lambda (class)
+		 (br-add-class (concat "[" class "]")
+			       br-null-path nil)))
+     classes)))
+
+(defun clos-class-routine-to-regexp (class routine-name args)
+  "Return regexp matching definition of CLASS's ROUTINE-NAME with ARGS.
+ARGs should be a string or nil if routine definition had no argument list,
+i.e. an alias."
+  (setq class (regexp-quote class)
+	routine-name (regexp-quote routine-name)
+	args (if (stringp args) (regexp-quote args) args))
+  ;; Search for CLOS method definition based on first typed argument.
+  (concat "(defmethod[ \t\n\r]+"
+	  routine-name "[ \t\n\r]"
+	  ;; Alias defmethods don't have an argument list, so don't
+	  ;; try to find one unless signature had an argument list.
+	  (if (not args)
+	      "+"
+	    (concat "*[^\)]*[ \t\n\r]" class "[ \t\n\r]*\)"))
+	  "\\|"
+	  ;; Search for BWlib routine definition where class name is
+	  ;; prepended with a colon to the routine name.
+	  (concat "(defmethod[ \t\n\r]+" class ":" routine-name
+		  "[ \t\n\r]"
+		  ;; BWlib alias defmethods don't have an argument list,
+		  ;; so don't try to find one unless signature had an
+		  ;; argument list.
+		  (if (not args) "+" "*\("))))
+
+(defun clos-feature-implementors (ftr-name)
+  "Return unsorted list of clos feature tags which implement FTR-NAME."
+  (if (string-match "[ \t]+$" ftr-name)
+      (setq ftr-name (substring ftr-name 0 (match-beginning 0))))
+  (clos-feature-matches (concat "^" (regexp-quote ftr-name) "$")))
+
+(defun clos-feature-locate-p (feature-tag)
+  (let (start)
+    (if (not (re-search-forward
+	      (clos-feature-signature-to-regexp feature-tag) nil t))
+	nil
+      (setq start (match-beginning 0))
+      (goto-char start)
+      (skip-chars-forward " \t\n")
+      (clos-to-comments-begin)
+      (recenter 0)
+      (goto-char start)
+      t)))
+
+(defun clos-feature-name-to-regexp (name)
+  "Converts feature NAME into a regular expression matching the feature's name tag."
+  (if (string-match (concat "^" br-feature-type-regexp " ") name)
+      (setq name (substring name (match-end 0))))
+  (format "%s%s\(\\(%s\\) %s[ \n]"
+	  clos-type-identifier clos-type-tag-separator clos-def-form-regexp
+	  (regexp-quote name)))
+
+(defun clos-feature-signature-to-name (signature &optional with-class for-display)
+  "Extracts the feature name from SIGNATURE.
+The feature's class name is dropped from signature unless optional WITH-CLASS
+is non-nil.  If optional FOR-DISPLAY is non-nil, a \"- \" is prepended to
+the name for display in a browser listing."
+  (concat (if for-display "- ")
+	  (clos-feature-partial-name signature with-class)))
+
+(defun clos-feature-signature-to-regexp (signature)
+  "Given a clos element SIGNATURE, return regexp to match its definition."
+  (cond ((string-match (concat "\\`[^ \t\n\r;]+" clos-type-tag-separator)
+		       signature)
+	 (clos-element-def-to-regexp
+	  (substring signature (match-end 0))))
+	((string-match (concat "\\(" clos-arg-identifier "\\):\\("
+			       clos-element-identifier
+			       "\\)[ \t\n\r]*\\(\(\\)?")
+		       signature)
+	 (clos-class-routine-to-regexp
+	  (substring signature (match-beginning 1) (match-end 1))
+	  (substring signature (match-beginning 2) (match-end 2))
+	  (if (= ?\( (elt signature (match-end 0)))
+	      (substring signature (match-beginning 3)))))))
+
+(defun clos-feature-tree-command-p (class-or-signature)
+  "Display definition of CLASS-OR-SIGNATURE if a signature and return t, else return nil."
+  (if (br-in-browser) (br-to-view-window))
+  (br-feature-found-p (br-feature-file class-or-signature)
+		      class-or-signature))
+
+(defun clos-list-features (class &optional indent)
+  "Return sorted list of clos feature names lexically defined in CLASS."
+  (let ((obuf (current-buffer))
+	(class-tag (concat "\n" class clos-type-tag-separator))
+	(features))
+    (set-buffer (funcall br-find-file-noselect-function br-feature-tags-file))
+    (goto-char 1)
+    ;; Feature defs (methods) for a single class could occur in any file,
+    ;; according to Common Lisp rules.
+    (while (search-forward class-tag nil t)
+      (setq features (cons (br-feature-current) features)))
+    (set-buffer obuf)
+    (clos-sort-features (nreverse features))))
+
+(defun clos-scan-features ()
+  "Return reverse ordered list of clos feature definitions in current buffer.
+Assume point is at the beginning of a widened buffer."
+  (save-excursion
+    (let ((features) (tag-list)
+	  ;; t if current file is an Emacs Lisp file and therefore may
+	  ;; contain BWlib method definitions.  BWlib is a simple CLOS-like
+	  ;; object system for Emacs Lisp written by the author of the
+	  ;; OO-Browser for use in InfoDock, but not yet released.
+	  (bwlib-flag (and buffer-file-name
+			   (string-match "\\.el$" buffer-file-name)
+			   t))
+	  def-form)
+      (while (re-search-forward clos-element-def nil t)
+	(setq tag-list (mapcar
+			'clos-feature-normalize
+			(clos-element-tag-list
+			 (setq def-form
+			       (buffer-substring
+				(match-beginning clos-def-form-grpn)
+				(match-end clos-def-form-grpn)))
+			 (buffer-substring (match-beginning clos-feature-grpn)
+					   (match-end clos-feature-grpn))
+			 (if (string-match clos-def-form-with-args-regexp
+					   def-form)
+			     (clos-scan-routine-arglist))
+			 bwlib-flag))
+	      features (nconc features tag-list)))
+      features)))
+
+(defun clos-scan-routine-arglist ()
+  "Return list of routine's formal parameters.  Leaves point after arglist.
+Requires that caller has left point in front of arglist.
+If routine is an alias, get argument list from the routine aliased, if
+defined, else return nil."
+  (skip-chars-forward " \t\n\r")
+  (if (= (following-char) ?\()
+      (buffer-substring (point) (progn (progn (forward-list) (point))))
+    ;; No arglist, treat as an alias.
+    (let ((aliased-function (read (current-buffer)))
+	  arg-list)
+      (setq aliased-function
+	    (condition-case ()
+		(cond ((fboundp 'indirect-function)
+		       (indirect-function aliased-function))
+		      ((fboundp 'hypb:indirect-function)
+		       (indirect-function aliased-function))
+		      (t aliased-function))
+	      (void-function nil)))
+      (if (null aliased-function)
+	  nil
+	(setq arg-list
+	      (cond ((fboundp 'action:params)
+		     (action:params aliased-function))
+		    ((listp aliased-function)
+		     (if (eq (car aliased-function) 'autoload)
+			 (error "(clos-scan-routine-arglist): Arglist unknown for autoload functions: %s" aliased-function)
+		       (car (cdr aliased-function))))
+		    ((funcall (if (fboundp 'compiled-function-p)
+				  'compiled-function-p
+				'byte-code-function-p)
+			      aliased-function)
+		     ;; Turn into a list for extraction
+		     (car (cdr (cons nil (append aliased-function nil)))))))
+	(if arg-list (prin1-to-string arg-list))))))
+
+(defun clos-sort-features (feature-list)
+  (sort feature-list 'clos-feature-lessp))
+
+;; !! Need to write clos-to-definition function.
+;;    Move from an identifier to its definition as best as possible.
+;;    Use the following temporarily.
+(fset 'clos-to-definition 'smart-lisp)
+
+;;; ************************************************************************
+;;; Private functions
+;;; ************************************************************************
+
+(defun clos-element-def-to-regexp (element-def)
+  "Convert a normalized clos element definition to a regular expression that will match to its definition in the source code."
+  (setq element-def (regexp-quote element-def))
+  (mapconcat (function (lambda (c)
+			 (if (= c ?\ )
+			     "[ \t\n\r]+\\(;.*[ \t\n\r]+\\)?"
+			   (char-to-string c))))
+	     element-def nil))
+
+(defun clos-feature-def-p ()
+  "Return nil unless point is within a feature definition.
+If point is within a comment, return nil.
+Leaves point at start of the definition for visual clarity."
+  (if (clos-skip-to-statement)
+      (looking-at "\(def")))
+
+(defun clos-feature-partial-name (signature &optional with-class)
+  "Extract the feature name without its class name from feature SIGNATURE.
+If optional WITH-CLASS is non-nil, class name and 'clos-type-tag-separator'
+are prepended to the name returned."
+  (if (string-match clos-feature-tag-regexp signature)
+      (let ((class (substring signature
+			      (match-beginning 1) (match-end 1)))
+	    (name (substring signature (match-beginning 2)
+			     (match-end 2))))
+	(setq name (br-delete-space name))
+	(if (string-match (concat "\\`" class ":") name)
+	    (setq name (substring name (match-end 0))))
+	(if with-class
+	    (concat class clos-type-tag-separator name)
+	  name))
+    signature))
+
+(defun clos-feature-lessp (routine1 routine2)
+  (string-lessp (clos-feature-partial-name routine1)
+		(clos-feature-partial-name routine2)))
+	
+(defun clos-feature-matches (regexp)
+  "Return an unsorted list of feature tags whose names match in part or whole to REGEXP."
+  ;; Ensure match to feature names only; also handle "^" and "$" meta-chars
+  (setq regexp
+	(concat "^\\(" clos-type-identifier "\\)"
+		clos-type-tag-separator
+		clos-def-form-match "['\(]?"
+		(if (equal (substring regexp 0 1) "^")
+		    (progn (setq regexp (substring regexp 1)) nil)
+		  (concat "[" clos-identifier-chars "]*"))
+		(if (equal (substring regexp -1) "$")
+		    (substring regexp 0 -1)
+		  (concat regexp "[" clos-identifier-chars "]*"))
+		"[ \t\n\r]"))
+  (save-excursion
+    (set-buffer (funcall br-find-file-noselect-function br-feature-tags-file))
+    (goto-char 1)
+    (let ((features))
+      (while (re-search-forward regexp nil t)
+	(backward-char) ;; Might have moved past newline.
+	(setq features (cons (br-feature-current) features)))
+      features)))
+
+(defun clos-feature-normalize (routine)
+  (let* ((len (length routine))
+	 (normal-feature (make-string len ?\ ))
+	 (n 0) (i 0)
+	 (space-list '(?\  ?\t ?\n ?\r))
+	 (space-regexp "[ \t\n\r]+")
+	 chr)
+    (while (< i len)
+      (setq chr (aref routine i)) 
+      (cond
+       ;; Convert sequences of space characters to a single space.
+       ((memq chr space-list)
+	(aset normal-feature n ?\ )
+	(if (string-match space-regexp routine i)
+	    (setq i (match-end 0)
+		  n (1+ n))
+	  (setq i (1+ i)
+		n (1+ n))))
+       ;;
+       ;; Remove ; style comments
+       ((= chr ?\;)
+	(setq i (1+ i))
+	(while (and (< i len) (/= (aref routine i) ?\n))
+	  (setq i (1+ i))))
+       (t ;; Normal character
+	(aset normal-feature n chr)
+	(setq i (1+ i)
+	      n (1+ n)))))
+    (substring normal-feature 0 n)))
+
+(defun clos-element-tag-list (element-type element arglist-string
+			      &optional bwlib-flag)
+  "Return list of tags (strings) of ELEMENT-TYPE, ELEMENT and its ARGLIST-STRING.
+All three arguments should be strings.
+Optional BWLIB-FLAG non-nil means check for BWlib expressions of the form:
+\(defmethod class:method-name (args)...)."
+  (let* ((element-category (downcase element-type))
+	 (element-tag-function
+	  (intern-soft (concat "clos-" element-category "-tag-list")))
+	 (args (if (or (null arglist-string)
+		       (string-equal arglist-string ""))
+		   ""
+		 (concat " " arglist-string)))
+	 element-def-and-type)
+    (cond ((fboundp element-tag-function)
+	   ;; If any such function is defined, it must return a list of
+	   ;; element-tags generated from the defining form, even if it
+	   ;; generates only 1 tag.
+	   (funcall element-tag-function element-type element arglist-string))
+	  ((and bwlib-flag
+		(string-match clos-def-form-with-args-regexp element-category)
+		(string-match "\\`['\(]?\\([^ \t\n\r]+\\):" element))
+	   ;; BWlib element definition support
+	   (list
+	    (format "%s%s\(%s %s%s"
+		    (substring element (match-beginning 1) (match-end 1))
+		    clos-type-tag-separator
+		    element-type element args)))
+	  ((equal element-category "defmethod")
+	   ;; CLOS defmethod
+	   (let ((arglist (if (string-equal args "")
+			      t
+			    (read arglist-string)))
+		 (class)
+		 (tags))
+	     (if (nlistp arglist)
+		 ;; Add to CLOS default 't' class.
+		 (list (format "t%s\(defmethod %s" 
+			       clos-type-tag-separator element))
+	       ;; If any argument in arglist is itself a list, then this is a
+	       ;; CLOS method definition with one or more (<arg-name>
+	       ;; <type-name>) arguments.  We generate one tag for each arg
+	       ;; list, with the tag's class = <type-name>.  We stop looking
+	       ;; for specialized arguments if we encounter a keyword
+	       ;; beginning with '&'.
+	       (setq tags
+		     (delq
+		      nil
+		      (mapcar
+		       (function
+			(lambda (arg)
+			  (cond ((null arglist)
+				 ;; Encountered &keyword, so ignore rest of
+				 ;; args.
+				 nil)
+				((null arg) nil)
+				((nlistp arg)
+				 (and (symbolp arg)
+				      (= ?& (aref (symbol-name arg) 0))
+				      ;; Encountered &keyword, set up to
+				      ;; ignore rest of args.
+				      (setq arglist nil)))
+				(t
+				 ;; Typed argument
+				 (setq class (car (cdr arg)))
+				 ;; Type may be of the form: (eql <form>)
+				 ;; which is used to compute the type.  We
+				 ;; can't compute this here, however, so
+				 ;; ignore such types.
+				 (if (listp class)
+				     nil
+				   (setq class (symbol-name class))
+				   (format "%s%s\(defmethod %s%s"
+					   class clos-type-tag-separator
+					   element args))))))
+		       arglist)))
+	       (or tags
+		   ;; Add this method to CLOS default 't' class since none of
+		   ;; its parameters were specialized.
+		   (list (format "t%s\(defmethod %s%s" 
+				 clos-type-tag-separator element args))))))
+	  ((setq element-def-and-type (assoc element-category
+					     clos-element-type-alist))
+	   (list (format "[%s]%s\(%s %s%s"
+			 (cdr element-def-and-type)
+			 clos-type-tag-separator
+			 element-type element args)))
+	  (t (beep)
+	     (message
+	      "(clos-element-tag): '%s' is an unknown definition type"
+	      element-type)
+	     (sit-for 3)))))
+
+(defun clos-feature-tag-class (element-tag)
+  "Extract the class name from ELEMENT-TAG."
+  (if (string-match (format "\\([^ \t%s]+\\)%s"
+			    clos-type-tag-separator
+			    clos-type-tag-separator)
+		    element-tag)
+      (substring element-tag (match-beginning 1) (match-end 1))
+    ""))
+
+(defun clos-files-with-source (class)
+  "Use CLASS to compute set of files that match to a clos source file regexp.
+Return as a list."
+  (let ((file (if class (br-class-path class) buffer-file-name)))
+    (and file
+	 (let* ((src-file-regexp (concat "^" (br-filename-head file)
+					 clos-src-file-regexp))
+		(dir (file-name-directory file))
+		(files (directory-files dir nil src-file-regexp)))
+	   (mapcar (function (lambda (f) (concat dir f)))
+		   files)))))
+
+(defun clos-find-class-name ()
+  "Return current word as a potential class name."
+  (save-excursion
+    (let* ((start)
+	   (ignore " \t\n\r ;,\(\){}")
+	   (pat (concat "^" ignore)))
+      (forward-char 1)
+      (skip-chars-backward ignore)
+      (skip-chars-backward pat)
+      (setq start (point))
+      (skip-chars-forward (concat pat ":"))
+      (buffer-substring start (point)))))
+
+(defun clos-get-class-name-from-source ()
+  "Return class name from closest class definition preceding point or nil."
+  (save-excursion
+    (if (re-search-backward clos-class-def-regexp nil t)
+	(buffer-substring (match-beginning 1) (match-end 1)))))
+
+(defun clos-get-feature-tags (feature-file &optional feature-list)
+  "Scan clos FEATURE-FILE and hold feature tags in 'br-feature-tags-file'.
+Assume FEATURE-FILE has already been read into a buffer and that
+'br-feature-tags-init' has been called.  Optional FEATURE-LIST can be
+provided so that a non-standard scan function can be used before calling
+this function."
+  (interactive)
+  (let ((obuf (current-buffer)))
+    (or feature-list
+	(setq feature-list (clos-sort-features
+			    (nreverse (clos-scan-features)))))
+    (set-buffer (funcall br-find-file-noselect-function br-feature-tags-file))
+    (goto-char 1)
+    ;; Delete any prior feature tags associated with feature-file
+    (if (search-forward feature-file nil 'end)
+	(progn (forward-line -1)
+	       (let ((start (point)))
+		 (search-forward "\^L" nil 'end 2)
+		 (backward-char 1)
+		 (delete-region start (point))
+		 )))
+    (if feature-list
+	(progn (insert "\^L\n" feature-file "\n")
+	       (mapcar (function (lambda (tag) (insert tag "\n")))
+		       feature-list)))
+    (set-buffer obuf)))
+
+(defun clos-skip-past-comments ()
+  "Skip over comments immediately following point."
+  (skip-chars-forward " \t\n")
+  (while
+      (cond ((looking-at "//")
+	     (equal (forward-line 1) 0))
+	    ((looking-at "/\\*")
+	     (re-search-forward "\\*/" nil t))
+	    (t nil))))
+
+(defun clos-skip-to-statement ()
+  (let ((bol (save-excursion (beginning-of-line) (point))))
+    (if (save-excursion (search-backward ";" bol t))
+	nil  ;; In a comment
+      ;; Find definition beginning.
+      (re-search-backward "^\(\\|" nil t))))
+
+;;; ************************************************************************
+;;; Private variables
+;;; ************************************************************************
+
+(defconst clos-element-identifier
+  (let ((identifier "[^][ \t\n\r;,`'{}()]+"))
+    ;; Initial optional paren is for defstructs of the form:
+    ;; (defstruct (identifier options))
+    (concat "['\(]?\\(" identifier
+	    "\\|(setf[ \t\n\r]+" identifier "[ \t\n\r]*)\\)"
+	    "\\([ \t\n\r]+'?:" identifier "\\)?"))
+  "Regular expression matching a clos element name.
+If a method, this includes any method qualifier.  Optional method qualifier
+is of the form: :before, :after or :around.  \(setf <slot>) names the writer
+method for <slot>.")
+
+(defconst clos-comment-regexp "\\([ \t\n\r]*;.*[\n\r]\\)*[ \t\n\r]*")
+
+(defvar   clos-element-type-alist
+  '(("defconstant"  . "constant")
+    ("defconst"     . "constant")
+    ("defun"        . "function")
+    ("defgeneric"   . "generic")
+    ("defmacro"     . "macro")
+    ("defmethod"    . "method")
+    ("defpackage"   . "package")
+    ("defparameter" . "parameter")
+    ("defsetf"      . "setfunction")
+    ("defstruct"    . "structure")
+    ("deftype"      . "type")
+    ("defvar"       . "variable")
+    ("fset"         . "function"))
+  "*Alist of (<element-definition-function-string> . <element-type-string>) elements.
+
+Reread the definition of 'clos-def-form-regexp' if you change this variable,
+as its value depends on this variable.  You may also need to add to the
+definition of 'clos-def-form-with-args-regexp'.")
+
+(defconst clos-def-form-regexp
+  (mapconcat 'identity (mapcar 'car clos-element-type-alist) "\\|")
+  "*Regexp of Common Lisp/Clos form names that define new element types.
+Defclass is omitted since the OO-Browser handles that separately.")
+
+(defconst clos-def-form-with-args-regexp
+  "defun\\|defgeneric\\|defmacro\\|defmethod\\|defsetf\\|fset"
+  "*Regexp of Common Lisp/Clos defining forms whose signature includes arguments.")
+
+(defconst clos-feature-def-regexp
+  (concat "(\\(" clos-def-form-regexp "\\)[ \t\n\r]+\\(\\('?"
+	  clos-type-identifier ":\\)?"
+	  "\\(" clos-element-identifier "\\)\\)"
+	  clos-comment-regexp)
+  "Regexp matching a clos element definition.
+Defining form, e.g. defun, is group 'clos-def-form-grpn'.
+Class plus element name is group 'clos-feature-grpn'.
+Class name is group 'clos-feature-type-grpn.
+Element name, with optional qualifier but without class, is group
+'clos-feature-name-grpn'.")
+
+(defconst clos-def-form-grpn 1)
+(defconst clos-feature-grpn 2)
+(defconst clos-feature-type-grpn 3)
+(defconst clos-feature-name-grpn 4)
+
+(defconst clos-element-def (concat "^[ \t]*" clos-feature-def-regexp)
+  "Regexp matching a clos element definition.
+See 'clos-feature-def-regexp' for grouping definitions.")
+
+(defconst clos-arg-identifier (concat "[" clos-identifier-chars "]+")
+  "Regular expression matching a clos function argument identifier.")
+
+(provide 'br-clos-ft)