[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[ELPA-diffs] /srv/bzr/emacs/elpa r171: Add f90-interface-browser.
From: |
Stefan Monnier |
Subject: |
[ELPA-diffs] /srv/bzr/emacs/elpa r171: Add f90-interface-browser. |
Date: |
Sun, 05 Feb 2012 21:11:54 -0500 |
User-agent: |
Bazaar (2.3.1) |
------------------------------------------------------------
revno: 171 [merge]
author: Lawrence Mitchell <address@hidden>
committer: Stefan Monnier <address@hidden>
branch nick: elpa
timestamp: Sun 2012-02-05 21:11:54 -0500
message:
Add f90-interface-browser.
added:
packages/f90-interface-browser/
packages/f90-interface-browser/f90-interface-browser.el
=== added directory 'packages/f90-interface-browser'
=== added file 'packages/f90-interface-browser/f90-interface-browser.el'
--- a/packages/f90-interface-browser/f90-interface-browser.el 1970-01-01
00:00:00 +0000
+++ b/packages/f90-interface-browser/f90-interface-browser.el 2012-02-06
02:11:54 +0000
@@ -0,0 +1,1010 @@
+;;; f90-interface-browser.el --- Parse and browse f90 interfaces
+
+;; Copyright (C) 2011, 2012 Free Software Foundation, Inc
+
+;; Author: Lawrence Mitchell <address@hidden>
+;; Created: 2011-07-06
+;; Available-from: http://github.com/wence-/f90-iface/
+;; Version: 1.0
+
+;; COPYRIGHT NOTICE
+
+;; This program 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 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; You write (or work on) large, modern fortran code bases. These
+;; make heavy use of function overloading and generic interfaces. Your
+;; brain is too small to remember what all the specialisers are
+;; called. Therefore, your editor should help you.
+
+;; Load this file and tell it to parse all the fortran files in your
+;; code base. You can do this one directory at a time by calling
+;; `f90-parse-interfaces-in-dir' (M-x f90-parse-interfaces-in-dir
+;; RET). Or you can parse all the fortran files in a directory and
+;; recursively in its subdirectories by calling
+;; `f90-parse-all-interfaces'.
+
+;; Now you are able to browse (with completion) all defined interfaces
+;; in your code by calling `f90-browse-interface-specialisers'.
+;; Alternatively, if `point' is on a procedure call, you can call
+;; `f90-find-tag-interface' and you'll be shown a list of the
+;; interfaces that match the (possibly typed) argument list of the
+;; current procedure. This latter hooks into the `find-tag' machinery
+;; so that you can use it on the M-. keybinding and it will fall back
+;; to completing tag names if you don't want to look for an interface
+;; definition.
+
+;; Derived types are also parsed, so that slot types of derived types
+;; are given the correct type (rather than a UNION-TYPE) when arglist
+;; matching. You can show the definition of a known derived type by
+;; calling `f90-show-type-definition' which prompts (with completion)
+;; for a typename to show.
+
+;; The parser assumes you write Fortran in the style espoused in
+;; Metcalf, Reid and Cohen. Particularly, variable declarations use a
+;; double colon to separate the type from the name list.
+
+;; Here's an example of a derived type definition
+;; type foo
+;; real, allocatable, dimension(:) :: a
+;; integer, pointer :: b, c(:)
+;; type(bar) :: d
+;; end type
+
+;; Here's a subroutine declaration
+;; subroutine foo(a, b)
+;; integer, intent(in) :: a
+;; real, intent(inout), dimension(:,:) :: b
+;; ...
+;; end subroutine foo
+
+;; Local procedures whose names conflict with global ones will likely
+;; confuse the parser. For example
+
+;; subroutine foo(a, b)
+;; ...
+;; end subroutine foo
+;;
+;; subroutine bar(a, b)
+;; ...
+;; call subroutine foo
+;; ...
+;; contains
+;; subroutine foo
+;; ...
+;; end subroutine foo
+;; end subroutine bar
+
+;; Also not handled are overloaded operators, scalar precision
+;; modifiers, like integer(kind=c_int), for which the precision is
+;; just ignored, and many other aspects.
+
+;; Some tests of the parser are available in f90-tests.el (in the same
+;; repository as this file).
+
+;;; Code:
+
+;;; Preamble
+(eval-when-compile
+ (require 'cl))
+(require 'thingatpt)
+(require 'f90)
+(require 'etags)
+
+(defgroup f90-iface nil
+ "Static parser for Fortran 90 code"
+ :prefix "f90-"
+ :group 'f90)
+
+(defcustom f90-file-extensions (list "f90" "F90" "fpp")
+ "Extensions to consider when looking for Fortran 90 files."
+ :type '(repeat string)
+ :group 'f90-iface)
+
+(defcustom f90-file-name-check-functions '(f90-check-fluidity-refcount)
+ "List of functions to call to check if a file should be parsed.
+
+In addition to checking if a file exists and is readable, you can
+add extra checks before deciding to parse a file. Each function
+will be called with one argument, the fully qualified name of the
+file to test, it should return non-nil if the file should be
+parsed. For an example test function see
+`f90-check-fluidity-refcount'."
+ :type '(repeat function)
+ :group 'f90-iface)
+
+(defcustom f90-extra-file-functions '(f90-insert-fluidity-refcount)
+ "List of functions to call to insert extra files to parse.
+
+Each function should be a function of two arguments, the first is the
+fully qualified filename (with directory) the second is the
+unqualified filename."
+ :type '(repeat function)
+ :group 'f90-iface)
+
+;;; Internal variables
+(defvar f90-interface-type nil)
+(make-variable-buffer-local 'f90-interface-type)
+
+(defvar f90-buffer-to-switch-to nil)
+(make-variable-buffer-local 'f90-buffer-to-switch-to)
+
+(defvar f90-invocation-marker nil)
+(make-variable-buffer-local 'f90-invocation-marker)
+
+;; Data types for storing interface and specialiser definitions
+(defstruct f90-interface
+ (name "" :read-only t)
+ (publicp nil)
+ specialisers)
+
+(defstruct f90-specialiser
+ (name "" :read-only t)
+ (type "")
+ (arglist "")
+ location)
+
+(defvar f90-all-interfaces (make-hash-table :test 'equal)
+ "Hash table populated with all known f90 interfaces.")
+
+(defvar f90-types (make-hash-table :test 'equal)
+ "Hash table populated with all known f90 derived types.")
+
+;;; Inlineable utility functions
+(defsubst f90-specialisers (name interfaces)
+ "Return all specialisers for NAME in INTERFACES."
+ (f90-interface-specialisers (f90-get-interface name interfaces)))
+
+(defsubst f90-valid-interface-name (name)
+ "Return non-nil if NAME is an interface name."
+ (gethash name f90-all-interfaces))
+
+(defsubst f90-count-commas (str &optional level)
+ "Count commas in STR.
+
+If LEVEL is non-nil, only count commas up to the specified nesting
+level. For example, a LEVEL of 0 counts top-level commas."
+ (1- (length (f90-split-arglist str level))))
+
+(defsubst f90-get-parsed-type-varname (type)
+ "Return the variable name of TYPE."
+ (car type))
+
+(defsubst f90-get-parsed-type-typename (type)
+ "Return the type name of TYPE."
+ (cadr type))
+
+(defsubst f90-get-parsed-type-modifiers (type)
+ "Return the modifiers of TYPE."
+ (cddr type))
+
+(defsubst f90-get-type (type)
+ "Return the struct definition corresponding to TYPE."
+ (gethash (f90-get-parsed-type-typename type) f90-types))
+
+(defsubst f90-get-slot-type (slot type)
+ "Get the type of SLOT in TYPE."
+ (let ((fn (intern-soft (format "f90-type.%s.%s"
+ (f90-get-parsed-type-typename type) slot))))
+ (when fn
+ (funcall fn (f90-get-type type)))))
+
+(defun f90-lazy-completion-table ()
+ "Lazily produce a completion table of all interfaces and tag names."
+ (lexical-let ((buf (current-buffer)))
+ (lambda (string pred action)
+ (with-current-buffer buf
+ (save-excursion
+ ;; If we need to ask for the tag table, allow that.
+ (let ((enable-recursive-minibuffers t))
+ (visit-tags-table-buffer))
+ (complete-with-action action (f90-merge-into-tags-completion-table
f90-all-interfaces) string pred))))))
+
+
+(defsubst f90-merge-into-tags-completion-table (ctable)
+ "Merge completions in CTABLE into the tags completion table."
+ (if (or tags-file-name tags-table-list)
+ (let ((table (tags-completion-table)))
+ (maphash (lambda (k v)
+ (ignore v)
+ (intern k table))
+ ctable)
+ table)
+ ctable))
+
+(defsubst f90-extract-type-name (name)
+ "Return the typename from NAME.
+
+If NAME is like type(TYPENAME) return TYPENAME, otherwise just NAME."
+ (if (and name (string-match "\\`type(\\([^)]+\\))\\'" name))
+ (match-string 1 name)
+ name))
+
+;;; User-visible routines
+
+(defun f90-parse-all-interfaces (dir)
+ "Parse all interfaces found in DIR and its subdirectories.
+
+Recurse over all (non-hidden) directories below DIR and parse
+interfaces found within them using `f90-parse-interfaces-in-dir',
+a directory is considered hidden if it's name doesn't start with
+an alphanumeric character."
+ (interactive "DParse files in tree: ")
+ (let (dirs
+ attrs
+ seen
+ (pending (list (expand-file-name dir))))
+ (while pending
+ (push (pop pending) dirs)
+ (let* ((this-dir (car dirs))
+ (contents (directory-files this-dir))
+ (default-directory this-dir))
+ (setq attrs (nthcdr 10 (file-attributes this-dir)))
+ (unless (member attrs seen)
+ (push attrs seen)
+ (dolist (file contents)
+ ;; Ignore hidden directories
+ (and (string-match "\\`[[:alnum:]]" file)
+ (file-directory-p file)
+ (setq pending (nconc pending
+ (list (expand-file-name file)))))))))
+ (mapc 'f90-parse-interfaces-in-dir dirs)))
+
+(defun f90-parse-interfaces-in-dir (dir)
+ "Parse all Fortran 90 files in DIR to populate `f90-all-interfaces'."
+ (interactive "DParse files in directory: ")
+ (loop for file in (directory-files dir t
+ (rx-to-string
+ `(and "." (or ,@f90-file-extensions)
+ eos) t))
+ do (f90-parse-interfaces file f90-all-interfaces)))
+
+(defun f90-find-tag-interface (name &optional match-sublist)
+ "List all interfaces matching NAME.
+
+Restricts list to those matching the (possibly typed) arglist of
+the word at point. If MATCH-SUBLIST is non-nil, only check if
+the arglist is a sublist of the specialiser's arglist. For more
+details see `f90-approx-arglist-match' and
+`f90-browse-interface-specialisers'."
+ (interactive (let ((def (word-at-point)))
+ (list (completing-read
+ (format "Find interface/tag (default %s): " def)
+ (f90-lazy-completion-table)
+ nil t nil nil def)
+ current-prefix-arg)))
+ (if (f90-valid-interface-name name)
+ (f90-browse-interface-specialisers name (f90-arglist-types)
+ match-sublist
+ (point-marker))
+ (find-tag name match-sublist)))
+
+(defun f90-browse-interface-specialisers (name &optional arglist-to-match
+ match-sublist
+ invocation-point)
+ "Browse all interfaces matching NAME.
+
+If ARGLIST-TO-MATCH is non-nil restrict to those interfaces that match
+it.
+If MATCH-SUBLIST is non-nil only restrict to those interfaces for
+which ARGLIST-TO-MATCH is a sublist of the specialiser's arglist.
+
+If INVOCATION-POINT is non-nil it should be a `point-marker'
+indicating where we were called from, for jumping back to with
+`pop-tag-mark'."
+ (interactive (let ((def (word-at-point)))
+ (list (completing-read
+ (format "Interface%s: "
+ (if def
+ (format " (default %s)" def)
+ ""))
+ f90-all-interfaces
+ nil t nil nil def))))
+ (let ((buf (current-buffer)))
+ (or invocation-point (setq invocation-point (point-marker)))
+ (with-current-buffer (get-buffer-create "*Interface Browser*")
+ (let ((interface (f90-get-interface name f90-all-interfaces))
+ (type nil)
+ (n-specs 0))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (setq n-specs
+ (loop for s being the hash-values of
+ (f90-interface-specialisers interface)
+ do (setq type (f90-specialiser-type s))
+ when (or (null arglist-to-match)
+ (f90-approx-arglist-match
+ arglist-to-match s match-sublist))
+ do (insert
+ (propertize
+ (concat
+ (propertize
+ (format "%s [defined in %s]\n (%s)\n"
+ (propertize (f90-specialiser-name s)
+ 'face 'bold)
+ (let ((f (car
+ (f90-specialiser-location s))))
+ (format "%s/%s"
+ (file-name-nondirectory
+ (directory-file-name
+ (file-name-directory f)))
+ (file-name-nondirectory f)))
+ (f90-fontify-arglist
+ (f90-specialiser-arglist s)))
+ 'f90-specialiser-location
+ (f90-specialiser-location s)
+ 'f90-specialiser-name (f90-specialiser-name s)
+ 'mouse-face 'highlight
+ 'help-echo
+ "mouse-1: find definition in other window")
+ "\n")
+ 'f90-specialiser-extent (f90-specialiser-name s)))
+ and count 1))
+ (goto-char (point-min))
+ (insert (format "Interfaces for %s:\n\n"
+ (f90-interface-name interface)))
+ (when arglist-to-match
+ (insert (format "%s\n%s\n\n"
+ (if (zerop n-specs)
+ "No interfaces matching arglist (intrinsic?):"
+ "Only showing interfaces matching arglist:")
+ (f90-fontify-arglist arglist-to-match))))
+ (f90-interface-browser-mode)
+ (setq f90-buffer-to-switch-to buf)
+ (setq f90-interface-type type)
+ (setq f90-invocation-marker invocation-point)
+ (pop-to-buffer (current-buffer))))))
+
+(defun f90-next-definition (&optional arg)
+ "Go to the next ARG'th specialiser definition."
+ (interactive "p")
+ (unless arg
+ (setq arg 1))
+ (while (> arg 0)
+ (goto-char (next-single-property-change
+ (point)
+ 'f90-specialiser-extent
+ nil (point-max)))
+ (decf arg)))
+
+(defun f90-previous-definition (&optional arg)
+ "Go to the previous ARG'th specialiser definition."
+ (interactive "p")
+ (unless arg
+ (setq arg 1))
+ (while (> arg 0)
+ (loop repeat 2
+ do (goto-char (previous-single-property-change
+ (point)
+ 'f90-specialiser-extent
+ nil (point-min))))
+ (f90-next-definition 1)
+ (decf arg)))
+
+(defun f90-mouse-find-definition (e)
+ "Visit the definition at the position of the event E."
+ (interactive "e")
+ (let ((win (posn-window (event-end e)))
+ (point (posn-point (event-end e))))
+ (when (not (windowp win))
+ (error "No definition here"))
+ (with-current-buffer (window-buffer win)
+ (goto-char point)
+ (f90-find-definition))))
+
+(defun f90-quit-browser ()
+ "Quit the interface browser."
+ (interactive)
+ (let ((buf f90-buffer-to-switch-to))
+ (kill-buffer (current-buffer))
+ (pop-to-buffer buf)))
+
+(defun f90-find-definition ()
+ "Visit the definition at `point'."
+ (interactive)
+ (let ((location (get-text-property (point) 'f90-specialiser-location))
+ (name (get-text-property (point) 'f90-specialiser-name))
+ (type f90-interface-type)
+ (buf (current-buffer))
+ buf-to)
+ (if location
+ (progn (ring-insert find-tag-marker-ring f90-invocation-marker)
+ (find-file-other-window (car location))
+ (setq buf-to (current-buffer))
+ (goto-char (cadr location))
+ ;; Try forwards then backwards near the recorded
+ ;; location
+ (or (re-search-forward (format "%s[ \t]+%s[ \t]*("
+ type name) nil t)
+ (re-search-backward (format "%s[ \t]+%s[ \t]*("
+ type name) nil t))
+ (beginning-of-line)
+ (recenter 0)
+ (pop-to-buffer buf)
+ (setq f90-buffer-to-switch-to buf-to))
+ (error "No definition at point"))))
+
+(defvar f90-interface-browser-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "RET") 'f90-find-definition)
+ (define-key map (kbd "<down>") 'f90-next-definition)
+ (define-key map (kbd "TAB") 'f90-next-definition)
+ (define-key map (kbd "<up>") 'f90-previous-definition)
+ (define-key map (kbd "<backtab>") 'f90-previous-definition)
+ (define-key map (kbd "q") 'f90-quit-browser)
+ (define-key map (kbd "<mouse-1>") 'f90-mouse-find-definition)
+ map)
+ "Keymap for `f90-interface-browser-mode'.")
+
+(define-derived-mode f90-interface-browser-mode fundamental-mode "IBrowse"
+ "Major mode for browsing f90 interfaces."
+ (setq buffer-read-only t)
+ (set-buffer-modified-p nil))
+
+;;; Type definitions
+
+(defun f90-type-at-point ()
+ "Return a guess for the type of the thing at `point'.
+
+If `point' is currently on a line containing a variable declaration,
+return the typename of the declaration. Otherwise try and figure out
+the typename of the variable at point (possibly including slot
+references)."
+ (let ((name (or
+ ;; Are we on a line with type(TYPENAME)?
+ (save-excursion
+ (forward-line 0)
+ (f90-parse-single-type-declaration))
+ ;; No, try and derive the type of the variable at point
+ (save-excursion
+ (let ((syntax (copy-syntax-table f90-mode-syntax-table)))
+ (modify-syntax-entry ?% "w" syntax)
+ (with-syntax-table syntax
+ (skip-syntax-backward "w")
+ (f90-arg-types
+ (list
+ (buffer-substring-no-properties
+ (point)
+ (progn (skip-syntax-forward "w") (point)))))))))))
+ (f90-extract-type-name (f90-get-parsed-type-typename (car name)))))
+
+(defun f90-show-type-definition (type)
+ "Show the definition of TYPE.
+
+This formats the parsed definition of TYPE, rather than jumping to the
+existing definition.
+
+When called interactively, default to the type of the thing at `point'.
+If `point' is on a type declaration line, the default is the
+declaration type.
+If `point' is on a variable name (possibly with slot references) the
+default is the type of the variable."
+ (interactive (list (let ((def (f90-type-at-point)))
+ (completing-read
+ (if def (format "Type (default %s): " def) "Type: ")
+ (loop for type being the hash-keys of f90-types
+ collect (f90-extract-type-name type))
+ nil t nil nil def))))
+ (with-current-buffer (get-buffer-create "*Type definition*")
+ (setq buffer-read-only nil)
+ (fundamental-mode)
+ (erase-buffer)
+ (let* ((tname (format "type(%s)" type))
+ (type-struct (f90-get-type (list nil tname)))
+ fns)
+ (when type-struct
+ (setq fns (loop for name in (funcall (intern-soft
+ (format "f90-type.%s.-varnames"
+ tname))
+ type-struct)
+ collect (intern-soft (format "f90-type.%s.%s"
+ tname name)))))
+ (if (null type-struct)
+ (insert (format "The type %s is not a known derived type."
+ type))
+ (insert (format "type %s\n" type))
+ (loop for fn in fns
+ for parsed = (funcall fn type-struct)
+ then (funcall fn type-struct)
+ do
+ (insert (format " %s :: %s\n"
+ (f90-format-parsed-slot-type parsed)
+ (f90-get-parsed-type-varname parsed))))
+ (insert (format "end type %s\n" type))
+ (f90-mode))
+ (goto-char (point-min))
+ (view-mode)
+ (pop-to-buffer (current-buffer)))))
+
+;;; Arglist matching/formatting
+
+(defun f90-format-parsed-slot-type (type)
+ "Turn a parsed TYPE into a valid f90 type declaration."
+ (if (null type)
+ "UNION-TYPE"
+ ;; Ignore name
+ (setq type (cdr type))
+ (mapconcat 'identity (loop for a in type
+ if (and (consp a)
+ (string= (car a) "dimension"))
+ collect (format "dimension(%s)"
+ (mapconcat 'identity
+ (make-list (cdr a)
+ ":")
+ ","))
+ else if (not
+ (string-match
+ "\\`intent(\\(?:in\\|out\\|inout\\))"
+ a))
+ collect a)
+ ", ")))
+
+(defun f90-fontify-arglist (arglist)
+ "Fontify ARGLIST using `f90-mode'."
+ (with-temp-buffer
+ (if (stringp arglist)
+ (insert (format "%s :: foo\n" arglist))
+ (insert (mapconcat (lambda (x)
+ (format "%s :: foo" (f90-format-parsed-slot-type
x)))
+ arglist "\n")))
+ (f90-mode)
+ (font-lock-fontify-buffer)
+ (goto-char (point-min))
+ (mapconcat 'identity
+ (loop while (not (eobp))
+ collect (buffer-substring (line-beginning-position)
+ (- (line-end-position)
+ (length " :: foo")))
+ do (forward-line 1))
+ "; ")))
+
+(defun f90-count-non-optional-args (arglist)
+ "Count non-optional args in ARGLIST."
+ (loop for arg in arglist
+ count (not (member "optional" (f90-get-parsed-type-modifiers arg)))))
+
+(defun f90-approx-arglist-match (arglist specialiser &optional match-sub-list)
+ "Return non-nil if ARGLIST matches the arglist of SPECIALISER.
+
+If MATCH-SUB-LIST is non-nil just require that ARGLIST matches the
+first (length ARGLIST) args of SPECIALISER."
+ (let* ((n-passed-args (length arglist))
+ (spec-arglist (f90-specialiser-arglist specialiser))
+ (n-spec-args (length spec-arglist))
+ (n-required-args (f90-count-non-optional-args spec-arglist)))
+ (when (or match-sub-list
+ (and (<= n-required-args n-passed-args)
+ (<= n-passed-args n-spec-args)))
+ (loop for arg in arglist
+ for spec-arg in spec-arglist
+ with match = nil
+ unless (or (null arg)
+ (string= (f90-get-parsed-type-typename arg)
+ (f90-get-parsed-type-typename spec-arg)))
+ do (return nil)
+ finally (return t)))))
+
+;;; Internal functions
+
+(defun f90-clean-comments ()
+ "Clean Fortran 90 comments from the current buffer."
+ (save-excursion
+ (goto-char (point-min))
+ (set-syntax-table f90-mode-syntax-table)
+ (while (search-forward "!" nil t)
+ (when (nth 4 (parse-partial-sexp (line-beginning-position) (point)))
+ (delete-region (max (1- (point)) (line-beginning-position))
+ (line-end-position))))))
+
+(defun f90-clean-continuation-lines ()
+ "Splat Fortran continuation lines in the current buffer onto one line."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "&[ \t]*\n[ \t]*&?" nil t)
+ (replace-match "" nil t))))
+
+(defun f90-normalise-string (string)
+ "Return a suitably normalised version of STRING."
+ ;; Trim whitespace
+ (save-match-data
+ (when (string-match "\\`[ \t]+" string)
+ (setq string (replace-match "" t t string)))
+ (when (string-match "[ \t]+\\'" string)
+ (setq string (replace-match "" t t string)))
+ (downcase string)))
+
+(defun f90-get-interface (name &optional interfaces)
+ "Get the interface with NAME from INTERFACES.
+
+If INTERFACES is nil use `f90-all-interfaces' instead."
+ (gethash name (or interfaces f90-all-interfaces)))
+
+(defsetf f90-get-interface (name &optional interfaces) (val)
+ `(setf (gethash ,name (or ,interfaces f90-all-interfaces)) ,val))
+
+;;; Entry point to parsing routines
+
+(defun f90-parse-file-p (file)
+ "Return non-nil if FILE should be parsed.
+
+This checks that FILE exists and is readable, and then calls
+additional test functions from `f90-file-name-check-functions'."
+ (and (file-exists-p file)
+ (file-readable-p file)
+ (loop for test in f90-file-name-check-functions
+ unless (funcall test file)
+ do (return nil)
+ finally (return t))))
+
+(defun f90-check-fluidity-refcount (file)
+ "Return nil if FILE is that of a Fluidity refcount template."
+ (let ((fname (file-name-nondirectory file)))
+ (and (not (string-match "\\`Reference_count_interface" fname))
+ (not (string-equal "Refcount_interface_templates.F90" fname))
+ (not (string-equal "Refcount_templates.F90" fname)))))
+
+(defun f90-maybe-insert-extra-files (file)
+ "Maybe insert extra files corresponding to FILE when parsing.
+
+To actually insert extra files, customize the variable
+`f90-extra-file-functions'. For an example insertion function
+see `f90-insert-fluidity-refcount'."
+ (let ((fname (file-name-nondirectory file)))
+ (loop for fn in f90-extra-file-functions
+ do (funcall fn file fname))))
+
+(defun f90-insert-fluidity-refcount (file fname)
+ "Insert a Fluidity reference count template for FILE.
+
+If FNAME matches \\\\`Reference_count_.*\\\\.F90 then this file
+needs a reference count interface, so insert one."
+ (when (string-match "\\`Reference_count_\\([^\\.]+\\)\\.F90" fname)
+ (insert-file-contents-literally
+ (expand-file-name
+ (format "Reference_count_interface_%s.F90"
+ (match-string 1 fname))
+ (file-name-directory file)))))
+
+(defun f90-parse-interfaces (file existing)
+ "Parse interfaces in FILE and merge into EXISTING interface data."
+ (with-temp-buffer
+ (let ((interfaces (make-hash-table :test 'equal)))
+ ;; Is this file valid for parsing
+ (when (f90-parse-file-p file)
+ (insert-file-contents-literally file)
+ ;; Does this file have other parts elsewhere?
+ (f90-maybe-insert-extra-files file)
+ ;; Easier if we don't have to worry about line wrap
+ (f90-clean-comments)
+ (f90-clean-continuation-lines)
+ (goto-char (point-min))
+ ;; Search forward for a named interface block
+ (while (re-search-forward
+ "^[ \t]*interface[ \t]+\\([^ \t\n]+\\)[ \t]*$" nil t)
+ (let* ((name (f90-normalise-string (match-string 1)))
+ interface)
+ (unless (string= name "")
+ (setq interface (make-f90-interface :name name))
+ (save-restriction
+ ;; Figure out all the specialisers for this generic name
+ (narrow-to-region
+ (point)
+ (re-search-forward
+ (format "[ \t]*end interface\\(?:[ \t]+%s\\)?[ \t]*$" name)
+ nil t))
+ (f90-populate-specialisers interface))
+ ;; Multiple interface blocks with same name (this seems to
+ ;; be allowed). In which case merge rather than overwrite.
+ (if (f90-get-interface name interfaces)
+ (f90-merge-interface interface interfaces)
+ (setf (f90-get-interface name interfaces) interface)))))
+ (goto-char (point-min))
+ ;; Parse type definitions
+ (save-excursion
+ (while (re-search-forward
+ "^[ \t]*type[ \t]+\\(?:[^ \t\n]+\\)[ \t]*$" nil t)
+ (let ((beg (match-beginning 0)))
+ (unless (re-search-forward "^[ \t]*end[ \t]+type.*$" nil t)
+ (error "Unable to find end of type definition"))
+ (save-restriction
+ (narrow-to-region beg (match-beginning 0))
+ (f90-parse-type-definition)))))
+
+ ;; Now find out if an interface is public or private to the module
+ (f90-set-public-attribute interfaces)
+
+ ;; Now find the arglists corresponding to the interface (so we
+ ;; can disambiguate) and record their location in the file.
+ (loop for interface being the hash-values of interfaces
+ do (when (f90-interface-specialisers interface)
+ (maphash (lambda (specialiser val)
+ (save-excursion
+ (goto-char (point-min))
+ (let ((thing (f90-argument-list specialiser)))
+ (setf (f90-specialiser-arglist
+ val)
+ (cadr thing))
+ (setf (f90-specialiser-location
+ val)
+ (list file (caddr thing)))
+ (setf (f90-specialiser-type
+ val)
+ (car thing)))))
+ (f90-interface-specialisers interface))))
+ ;; Finally merge these new interfaces into the existing data.
+ (f90-merge-interfaces interfaces existing)))))
+
+(defun f90-merge-interface (interface interfaces)
+ "Merge INTERFACE into the existing set of INTERFACES."
+ (let ((name (f90-interface-name interface))
+ spec-name)
+ (when (f90-interface-specialisers interface)
+ (loop for val being the hash-values of
+ (f90-interface-specialisers interface)
+ do (setq spec-name (f90-specialiser-name val))
+ (setf (gethash spec-name (f90-specialisers name interfaces))
+ val)))))
+
+(defun f90-merge-interfaces (new existing)
+ "Merge NEW interfaces into EXISTING ones."
+ (maphash (lambda (name val)
+ (if (gethash name existing)
+ (f90-merge-interface val existing)
+ (setf (gethash name existing)
+ val)))
+ new))
+
+(defun f90-populate-specialisers (interface)
+ "Find all specialisers for INTERFACE."
+ (save-excursion
+ (goto-char (point-min))
+ (setf (f90-interface-specialisers interface)
+ (make-hash-table :test 'equal))
+ (while (search-forward "module procedure" nil t)
+ (let ((names (buffer-substring-no-properties
+ (point)
+ (line-end-position))))
+ (mapc (lambda (x)
+ (setq x (f90-normalise-string x))
+ (setf (gethash x (f90-interface-specialisers interface))
+ (make-f90-specialiser :name x)))
+ (split-string names "[, \n]+" t))))))
+
+(defun f90-set-public-attribute (interfaces)
+ "Set public/private flag on all INTERFACES."
+ (save-excursion
+ ;; Default public unless private is specified.
+ (let ((public (not (save-excursion
+ (re-search-forward "^[ \t]*private[ \t]*$" nil t)))))
+ (while (re-search-forward (format "^[ \t]*%s[ \t]+"
+ (if public "private" "public"))
+ nil t)
+ (let ((names (buffer-substring-no-properties
+ (match-end 0)
+ (line-end-position))))
+ ;; Set default
+ (maphash (lambda (k v)
+ (ignore k)
+ (setf (f90-interface-publicp v) public))
+ interfaces)
+ ;; Override for those specified
+ (mapc (lambda (name)
+ (let ((interface (f90-get-interface name interfaces)))
+ (when interface
+ (setf (f90-interface-publicp interface) (not public)))))
+ (split-string names "[, \t]" t)))))))
+
+;;; Type/arglist parsing
+(defun f90-argument-list (name)
+ "Return typed argument list of function or subroutine NAME."
+ (save-excursion
+ (when (re-search-forward
+ (format "\\(function\\|subroutine\\)[ \t]+%s[ \t]*("
+ name)
+ nil t)
+ (let* ((point (match-beginning 0))
+ (type (match-string 1))
+ (args (f90-split-arglist (buffer-substring-no-properties
+ (point)
+ (f90-end-of-arglist)))))
+ (list type (f90-arg-types args) point)))))
+
+(defun f90-parse-type-definition ()
+ "Parse a type definition at (or in front of) `point'."
+ (let (type slots slot fn)
+ (goto-char (point-min))
+ (unless (re-search-forward "^[ \t]*type[ \t]+\\(.+?\\)[ \t]*$" nil t)
+ (error "Trying parse a type but no type found"))
+ (setq type (format "type(%s)" (f90-normalise-string (match-string 1))))
+ (while (not (eobp))
+ (setq slot (f90-parse-single-type-declaration))
+ (when slot
+ (setf slots (nconc slot slots)))
+ (forward-line 1))
+ (eval (f90-make-type-struct type slots))
+ (setq fn (intern-soft (format "make-f90-type.%s" type)))
+ (unless fn
+ (error "Something bad went wrong parsing type definition %s" type))
+ (setf (gethash type f90-types) (funcall fn))))
+
+(defun f90-make-type-struct (type slots)
+ "Create a struct describing TYPE with SLOTS."
+ (let ((struct-name (make-symbol (format "f90-type.%s" type)))
+ (varnames (reverse (mapcar (lambda (x)
+ (setq x (car x))
+ (if (string-match "\\([^(]+\\)(" x)
+ (match-string 1 x)
+ x)) slots))))
+ `(defstruct (,struct-name
+ (:conc-name ,(make-symbol (format "f90-type.%s." type))))
+ (-varnames ',varnames :read-only t)
+ ,@(loop for (name . rest) in slots
+ collect `(,(make-symbol name) (cons ',name ',rest)
+ :read-only t)))))
+
+(defun f90-arglist-types ()
+ "Return the types of the arguments to the function at `point'."
+ (save-excursion
+ (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
+ (b (save-excursion (f90-beginning-of-subprogram) (point)))
+ (str (buffer-substring-no-properties b e))
+ (p (point))
+ names)
+ (with-temp-buffer
+ (with-syntax-table f90-mode-syntax-table
+ (insert str)
+ (goto-char (- p b))
+ (setq p (point-marker))
+ (f90-clean-continuation-lines)
+ (goto-char p)
+ (search-forward "(")
+ (setq names (f90-split-arglist (buffer-substring
+ (point)
+ (f90-end-of-arglist))))
+ (goto-char (point-min))
+ (f90-arg-types names))))))
+
+(defun f90-arg-types (names)
+ "Given NAMES of arguments return their types.
+
+This works even with derived type subtypes (e.g. if A is a type(foo)
+with slot B of type REAL, then A%B is returned being a REAL)."
+ (loop for arg in names
+ for subspec = nil then nil
+ do (setq arg (f90-normalise-string arg))
+ if (string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" arg)
+ do (setq subspec (match-string 2 arg)
+ arg (match-string 1 arg))
+ collect (save-excursion
+ (save-restriction
+ (when (re-search-forward
+ (format "^[ \t]*\\([^!\n].+?\\)[ \t]*::.*\\<%s\\>"
+ arg) nil t)
+ (goto-char (match-beginning 0))
+ (let ((type (assoc arg
+ (f90-parse-single-type-declaration))))
+ (f90-get-type-subtype type subspec)))))))
+
+(defun f90-get-type-subtype (type subspec)
+ "Return the type of TYPE possibly including slot references in SUBSPEC."
+ (cond ((null subspec)
+ type)
+ ((string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" subspec)
+ (f90-get-type-subtype (f90-get-slot-type (match-string 1 subspec)
+ type)
+ (match-string 2 subspec)))
+ (t
+ (f90-get-slot-type subspec type))))
+
+(defun f90-split-arglist (arglist &optional level)
+ "Split ARGLIST into words.
+
+Split based on top-level commas. For example
+
+ (f90-split-arglist \"foo, bar, baz(quux, zot)\")
+ => (\"foo\" \"bar\" \"baz(quux, zot)\").
+
+If LEVEL is non-nil split on commas up to and including LEVEL.
+For example:
+
+ (f90-split-arglist \"foo, bar, baz(quux, zot)\" 1)
+ => (\"foo\" \"bar\" \"baz(quux\" \"zot)\")."
+ (setq level (or level 0))
+ (loop for c across arglist
+ for i = 0 then (1+ i)
+ with cur-level = 0
+ with b = 0
+ with len = (length arglist)
+ if (eq c ?\()
+ do (incf cur-level)
+ else if (eq c ?\))
+ do (decf cur-level)
+ if (and (<= cur-level level)
+ (eq c ?,))
+ collect (f90-normalise-string (substring arglist b i))
+ and do (setq b (1+ i))
+ if (and (<= cur-level level)
+ (= (1+ i) len))
+ collect (f90-normalise-string (substring arglist b))))
+
+(defun f90-end-of-arglist ()
+ "Find the end of the arglist at `point'."
+ (save-excursion
+ (let ((level 0))
+ (while (> level -1)
+ (cond ((eq (char-after) ?\()
+ (incf level))
+ ((eq (char-after) ?\))
+ (decf level))
+ (t nil))
+ (forward-char)))
+ (1- (point))))
+
+(defun f90-parse-names-list (names)
+ "Return a list of NAMES from the RHS of a :: type declaration."
+ (let ((names-list (f90-split-arglist names)))
+ (loop for name in names-list
+ if (string-match "\\`\\([^=]+\\)[ \t]*=.*\\'" name)
+ collect (f90-normalise-string (match-string 1 name))
+ else
+ collect (f90-normalise-string name))))
+
+(defun f90-parse-single-type-declaration ()
+ "Parse a single f90 type declaration at `point'.
+
+Assumes that this has the form
+ TYPENAME[, MODIFIERS]* :: NAME[, NAMES]*
+
+NAMES can optionally have initialisation attached to them which is
+dealt with correctly."
+ (when (looking-at "^[ \t]*\\(.*?\\)[ \t]*::[ \t]*\\(.*\\)$")
+ (let ((dec-orig (match-string 1))
+ (names (f90-parse-names-list (match-string 2))))
+ (loop for name in names
+ for dec = (f90-split-declaration dec-orig)
+ then (f90-split-declaration dec-orig)
+ if (string-match "\\([^(]+\\)(\\([^)]+\\))" name)
+ do (progn (if (assoc "dimension" dec)
+ (setcdr (assoc "dimension" dec)
+ (1+ (f90-count-commas
+ (match-string 2 name))))
+ (add-to-list 'dec
+ (cons "dimension"
+ (1+ (f90-count-commas
+ (match-string 2 name))))
+ t))
+ (setq name (match-string 1 name)))
+ collect (cons name dec)))))
+
+(defun f90-split-declaration (dec)
+ "Split and parse a type declaration DEC.
+
+This takes the bit before the :: and returns a list of the typename
+and any modifiers."
+ (let ((things (f90-split-arglist dec)))
+ (cons (if (string-match
+ "\\([^(]+?\\)[ \t]*([ \t]*\\(:?len\\|kind\\)[ \t]*=[^)]+)"
+ (car things))
+ (match-string 1 (car things))
+ (car things))
+ (loop for thing in (cdr things)
+ if (string-match "dimension[ \t]*(\\(.+\\))" thing)
+ collect (cons "dimension"
+ (1+ (f90-count-commas (match-string 1 thing))))
+ else
+ collect thing))))
+
+(provide 'f90-interface-browser)
+
+;;; f90-interface-browser.el ends here
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [ELPA-diffs] /srv/bzr/emacs/elpa r171: Add f90-interface-browser.,
Stefan Monnier <=