1 ;;; f90-interface-browser.el --- Parse and browse f90 interfaces
3 ;; Copyright (C) 2011, 2012 Free Software Foundation, Inc
5 ;; Author: Lawrence Mitchell <wence@gmx.li>
7 ;; Available-from: http://github.com/wence-/f90-iface/
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
26 ;; You write (or work on) large, modern fortran code bases. These
27 ;; make heavy use of function overloading and generic interfaces. Your
28 ;; brain is too small to remember what all the specialisers are
29 ;; called. Therefore, your editor should help you.
31 ;; Load this file and tell it to parse all the fortran files in your
32 ;; code base. You can do this one directory at a time by calling
33 ;; `f90-parse-interfaces-in-dir' (M-x f90-parse-interfaces-in-dir
34 ;; RET). Or you can parse all the fortran files in a directory and
35 ;; recursively in its subdirectories by calling
36 ;; `f90-parse-all-interfaces'.
38 ;; Now you are able to browse (with completion) all defined interfaces
39 ;; in your code by calling `f90-browse-interface-specialisers'.
40 ;; Alternatively, if `point' is on a procedure call, you can call
41 ;; `f90-find-tag-interface' and you'll be shown a list of the
42 ;; interfaces that match the (possibly typed) argument list of the
43 ;; current procedure. This latter hooks into the `find-tag' machinery
44 ;; so that you can use it on the M-. keybinding and it will fall back
45 ;; to completing tag names if you don't want to look for an interface
47 ;; In addition, if you're in a large procedure and want the list of
48 ;; the variables in scope (perhaps you want to define a new loop
49 ;; variable), you can use `f90-list-in-scope-vars' to pop up a buffer
50 ;; giving a reasonable guess. Note this doesn't give you module
51 ;; variables, or the variables of parent procedures if the current
52 ;; subroutine is contained within another.
54 ;; Derived types are also parsed, so that slot types of derived types
55 ;; are given the correct type (rather than a UNION-TYPE) when arglist
56 ;; matching. You can show the definition of a known derived type by
57 ;; calling `f90-show-type-definition' which prompts (with completion)
58 ;; for a typename to show.
60 ;; The parser assumes you write Fortran in the style espoused in
61 ;; Metcalf, Reid and Cohen. Particularly, variable declarations use a
62 ;; double colon to separate the type from the name list.
64 ;; Here's an example of a derived type definition
66 ;; real, allocatable, dimension(:) :: a
67 ;; integer, pointer :: b, c(:)
71 ;; Here's a subroutine declaration
72 ;; subroutine foo(a, b)
73 ;; integer, intent(in) :: a
74 ;; real, intent(inout), dimension(:,:) :: b
78 ;; Local procedures whose names conflict with global ones will likely
79 ;; confuse the parser. For example
81 ;; subroutine foo(a, b)
85 ;; subroutine bar(a, b)
87 ;; call subroutine foo
95 ;; Also not handled are overloaded operators, scalar precision
96 ;; modifiers, like integer(kind=c_int), for which the precision is
97 ;; just ignored, and many other aspects.
99 ;; Some tests of the parser are available in f90-tests.el (in the same
100 ;; repository as this file).
111 (defgroup f90-iface nil
112 "Static parser for Fortran 90 code"
116 (defcustom f90-file-extensions (list "f90" "F90" "fpp")
117 "Extensions to consider when looking for Fortran 90 files."
118 :type '(repeat string)
121 (defcustom f90-file-name-check-functions '(f90-check-fluidity-refcount)
122 "List of functions to call to check if a file should be parsed.
124 In addition to checking if a file exists and is readable, you can
125 add extra checks before deciding to parse a file. Each function
126 will be called with one argument, the fully qualified name of the
127 file to test, it should return non-nil if the file should be
128 parsed. For an example test function see
129 `f90-check-fluidity-refcount'."
130 :type '(repeat function)
133 (defcustom f90-extra-file-functions '(f90-insert-fluidity-refcount)
134 "List of functions to call to insert extra files to parse.
136 Each function should be a function of two arguments, the first is the
137 fully qualified filename (with directory) the second is the
138 unqualified filename."
139 :type '(repeat function)
142 ;;; Internal variables
143 (defvar f90-interface-type nil)
144 (make-variable-buffer-local 'f90-interface-type)
146 (defvar f90-buffer-to-switch-to nil)
147 (make-variable-buffer-local 'f90-buffer-to-switch-to)
149 (defvar f90-invocation-marker nil)
150 (make-variable-buffer-local 'f90-invocation-marker)
152 ;; Data types for storing interface and specialiser definitions
153 (defstruct f90-interface
154 (name "" :read-only t)
158 (defstruct f90-specialiser
159 (name "" :read-only t)
164 (defvar f90-all-interfaces (make-hash-table :test 'equal)
165 "Hash table populated with all known f90 interfaces.")
167 (defvar f90-types (make-hash-table :test 'equal)
168 "Hash table populated with all known f90 derived types.")
170 ;;; Inlineable utility functions
171 (defsubst f90-specialisers (name interfaces)
172 "Return all specialisers for NAME in INTERFACES."
173 (f90-interface-specialisers (f90-get-interface name interfaces)))
175 (defsubst f90-valid-interface-name (name)
176 "Return non-nil if NAME is an interface name."
177 (gethash name f90-all-interfaces))
179 (defsubst f90-count-commas (str &optional level)
180 "Count commas in STR.
182 If LEVEL is non-nil, only count commas up to the specified nesting
183 level. For example, a LEVEL of 0 counts top-level commas."
184 (1- (length (f90-split-arglist str level))))
186 (defsubst f90-get-parsed-type-varname (type)
187 "Return the variable name of TYPE."
190 (defsubst f90-get-parsed-type-typename (type)
191 "Return the type name of TYPE."
194 (defsubst f90-get-parsed-type-modifiers (type)
195 "Return the modifiers of TYPE."
198 (defsubst f90-get-type (type)
199 "Return the struct definition corresponding to TYPE."
200 (gethash (f90-get-parsed-type-typename type) f90-types))
202 (defsubst f90-get-slot-type (slot type)
203 "Get the type of SLOT in TYPE."
204 (let ((fn (intern-soft (format "f90-type.%s.%s"
205 (f90-get-parsed-type-typename type) slot))))
207 (funcall fn (f90-get-type type)))))
209 (defun f90-lazy-completion-table ()
210 "Lazily produce a completion table of all interfaces and tag names."
211 (lexical-let ((buf (current-buffer)))
212 (lambda (string pred action)
213 (with-current-buffer buf
215 ;; If we need to ask for the tag table, allow that.
216 (let ((enable-recursive-minibuffers t))
217 (visit-tags-table-buffer))
218 (complete-with-action action (f90-merge-into-tags-completion-table f90-all-interfaces) string pred))))))
221 (defsubst f90-merge-into-tags-completion-table (ctable)
222 "Merge completions in CTABLE into the tags completion table."
223 (if (or tags-file-name tags-table-list)
224 (let ((table (tags-completion-table)))
225 (maphash (lambda (k v)
232 (defsubst f90-extract-type-name (name)
233 "Return the typename from NAME.
235 If NAME is like type(TYPENAME) return TYPENAME, otherwise just NAME."
236 (if (and name (string-match "\\`type(\\([^)]+\\))\\'" name))
237 (match-string 1 name)
240 ;;; User-visible routines
242 (defun f90-parse-all-interfaces (dir)
243 "Parse all interfaces found in DIR and its subdirectories.
245 Recurse over all (non-hidden) directories below DIR and parse
246 interfaces found within them using `f90-parse-interfaces-in-dir',
247 a directory is considered hidden if it's name doesn't start with
248 an alphanumeric character."
249 (interactive "DParse files in tree: ")
253 (pending (list (expand-file-name dir))))
255 (push (pop pending) dirs)
256 (let* ((this-dir (car dirs))
257 (contents (directory-files this-dir))
258 (default-directory this-dir))
259 (setq attrs (nthcdr 10 (file-attributes this-dir)))
260 (unless (member attrs seen)
262 (dolist (file contents)
263 ;; Ignore hidden directories
264 (and (string-match "\\`[[:alnum:]]" file)
265 (file-directory-p file)
266 (setq pending (nconc pending
267 (list (expand-file-name file)))))))))
268 (mapc 'f90-parse-interfaces-in-dir dirs)))
270 (defun f90-parse-interfaces-in-dir (dir)
271 "Parse all Fortran 90 files in DIR to populate `f90-all-interfaces'."
272 (interactive "DParse files in directory: ")
273 (loop for file in (directory-files dir t
275 `(and "." (or ,@f90-file-extensions)
277 do (f90-parse-interfaces file f90-all-interfaces)))
279 (defun f90-find-tag-interface (name &optional match-sublist)
280 "List all interfaces matching NAME.
282 Restricts list to those matching the (possibly typed) arglist of
283 the word at point. If MATCH-SUBLIST is non-nil, only check if
284 the arglist is a sublist of the specialiser's arglist. For more
285 details see `f90-approx-arglist-match' and
286 `f90-browse-interface-specialisers'."
287 (interactive (let ((def (word-at-point)))
288 (list (completing-read
289 (format "Find interface/tag (default %s): " def)
290 (f90-lazy-completion-table)
292 current-prefix-arg)))
293 (if (f90-valid-interface-name name)
294 (f90-browse-interface-specialisers name (f90-arglist-types)
297 (find-tag name match-sublist)))
299 (defun f90-browse-interface-specialisers (name &optional arglist-to-match
302 "Browse all interfaces matching NAME.
304 If ARGLIST-TO-MATCH is non-nil restrict to those interfaces that match
306 If MATCH-SUBLIST is non-nil only restrict to those interfaces for
307 which ARGLIST-TO-MATCH is a sublist of the specialiser's arglist.
309 If INVOCATION-POINT is non-nil it should be a `point-marker'
310 indicating where we were called from, for jumping back to with
312 (interactive (let ((def (word-at-point)))
313 (list (completing-read
314 (format "Interface%s: "
316 (format " (default %s)" def)
319 nil t nil nil def))))
320 (let ((buf (current-buffer)))
321 (or invocation-point (setq invocation-point (point-marker)))
322 (with-current-buffer (get-buffer-create "*Interface Browser*")
323 (let ((interface (f90-get-interface name f90-all-interfaces))
326 (setq buffer-read-only nil)
329 (loop for s being the hash-values of
330 (f90-interface-specialisers interface)
331 do (setq type (f90-specialiser-type s))
332 when (or (null arglist-to-match)
333 (f90-approx-arglist-match
334 arglist-to-match s match-sublist))
339 (format "%s [defined in %s]\n (%s)\n"
340 (propertize (f90-specialiser-name s)
343 (f90-specialiser-location s))))
345 (file-name-nondirectory
347 (file-name-directory f)))
348 (file-name-nondirectory f)))
350 (f90-specialiser-arglist s)))
351 'f90-specialiser-location
352 (f90-specialiser-location s)
353 'f90-specialiser-name (f90-specialiser-name s)
354 'mouse-face 'highlight
356 "mouse-1: find definition in other window")
358 'f90-specialiser-extent (f90-specialiser-name s)))
360 (goto-char (point-min))
361 (insert (format "Interfaces for %s:\n\n"
362 (f90-interface-name interface)))
363 (when arglist-to-match
364 (insert (format "%s\n%s\n\n"
366 "No interfaces matching arglist (intrinsic?):"
367 "Only showing interfaces matching arglist:")
368 (f90-fontify-arglist arglist-to-match))))
369 (f90-interface-browser-mode)
370 (setq f90-buffer-to-switch-to buf)
371 (setq f90-interface-type type)
372 (setq f90-invocation-marker invocation-point)
373 (pop-to-buffer (current-buffer))))))
375 (defun f90-next-definition (&optional arg)
376 "Go to the next ARG'th specialiser definition."
381 (goto-char (next-single-property-change
383 'f90-specialiser-extent
387 (defun f90-previous-definition (&optional arg)
388 "Go to the previous ARG'th specialiser definition."
394 do (goto-char (previous-single-property-change
396 'f90-specialiser-extent
398 (f90-next-definition 1)
401 (defun f90-mouse-find-definition (e)
402 "Visit the definition at the position of the event E."
404 (let ((win (posn-window (event-end e)))
405 (point (posn-point (event-end e))))
406 (when (not (windowp win))
407 (error "No definition here"))
408 (with-current-buffer (window-buffer win)
410 (f90-find-definition))))
412 (defun f90-quit-browser ()
413 "Quit the interface browser."
415 (let ((buf f90-buffer-to-switch-to))
416 (kill-buffer (current-buffer))
417 (pop-to-buffer buf)))
419 (defun f90-find-definition ()
420 "Visit the definition at `point'."
422 (let ((location (get-text-property (point) 'f90-specialiser-location))
423 (name (get-text-property (point) 'f90-specialiser-name))
424 (type f90-interface-type)
425 (buf (current-buffer))
428 (progn (ring-insert find-tag-marker-ring f90-invocation-marker)
429 (find-file-other-window (car location))
430 (setq buf-to (current-buffer))
431 (goto-char (cadr location))
432 ;; Try forwards then backwards near the recorded
434 (or (re-search-forward (format "%s[ \t]+%s[ \t]*("
436 (re-search-backward (format "%s[ \t]+%s[ \t]*("
441 (setq f90-buffer-to-switch-to buf-to))
442 (error "No definition at point"))))
444 (defvar f90-interface-browser-mode-map
445 (let ((map (make-sparse-keymap)))
446 (define-key map (kbd "RET") 'f90-find-definition)
447 (define-key map (kbd "<down>") 'f90-next-definition)
448 (define-key map (kbd "TAB") 'f90-next-definition)
449 (define-key map (kbd "<up>") 'f90-previous-definition)
450 (define-key map (kbd "<backtab>") 'f90-previous-definition)
451 (define-key map (kbd "q") 'f90-quit-browser)
452 (define-key map (kbd "<mouse-1>") 'f90-mouse-find-definition)
454 "Keymap for `f90-interface-browser-mode'.")
456 (define-derived-mode f90-interface-browser-mode fundamental-mode "IBrowse"
457 "Major mode for browsing f90 interfaces."
458 (setq buffer-read-only t)
459 (set-buffer-modified-p nil))
463 (defun f90-type-at-point ()
464 "Return a guess for the type of the thing at `point'.
466 If `point' is currently on a line containing a variable declaration,
467 return the typename of the declaration. Otherwise try and figure out
468 the typename of the variable at point (possibly including slot
471 ;; Are we on a line with type(TYPENAME)?
474 (f90-parse-single-type-declaration))
475 ;; No, try and derive the type of the variable at point
477 (let ((syntax (copy-syntax-table f90-mode-syntax-table)))
478 (modify-syntax-entry ?% "w" syntax)
479 (with-syntax-table syntax
480 (skip-syntax-backward "w")
483 (buffer-substring-no-properties
485 (progn (skip-syntax-forward "w") (point)))))))))))
486 (f90-extract-type-name (f90-get-parsed-type-typename (car name)))))
488 (defun f90-show-type-definition (type)
489 "Show the definition of TYPE.
491 This formats the parsed definition of TYPE, rather than jumping to the
494 When called interactively, default to the type of the thing at `point'.
495 If `point' is on a type declaration line, the default is the
497 If `point' is on a variable name (possibly with slot references) the
498 default is the type of the variable."
499 (interactive (list (let ((def (f90-type-at-point)))
501 (if def (format "Type (default %s): " def) "Type: ")
502 (loop for type being the hash-keys of f90-types
503 collect (f90-extract-type-name type))
504 nil t nil nil def))))
505 (with-current-buffer (get-buffer-create "*Type definition*")
506 (setq buffer-read-only nil)
509 (let* ((tname (format "type(%s)" type))
510 (type-struct (f90-get-type (list nil tname)))
513 (setq fns (loop for name in (funcall (intern-soft
514 (format "f90-type.%s.-varnames"
517 collect (intern-soft (format "f90-type.%s.%s"
519 (if (null type-struct)
520 (insert (format "The type %s is not a known derived type."
522 (insert (format "type %s\n" type))
524 for parsed = (funcall fn type-struct)
525 then (funcall fn type-struct)
527 (insert (format " %s :: %s\n"
528 (f90-format-parsed-slot-type parsed)
529 (f90-get-parsed-type-varname parsed))))
530 (insert (format "end type %s\n" type))
532 (goto-char (point-min))
534 (pop-to-buffer (current-buffer)))))
536 ;;; Arglist matching/formatting
538 (defun f90-format-parsed-slot-type (type)
539 "Turn a parsed TYPE into a valid f90 type declaration."
543 (setq type (cdr type))
544 (mapconcat 'identity (loop for a in type
546 (string= (car a) "dimension"))
547 collect (format "dimension(%s)"
554 "\\`intent(\\(?:in\\|out\\|inout\\))"
559 (defun f90-fontify-arglist (arglist)
560 "Fontify ARGLIST using `f90-mode'."
562 (if (stringp arglist)
563 (insert (format "%s :: foo\n" arglist))
564 (insert (mapconcat (lambda (x)
565 (format "%s :: foo" (f90-format-parsed-slot-type x)))
568 (font-lock-fontify-buffer)
569 (goto-char (point-min))
571 (loop while (not (eobp))
572 collect (buffer-substring (line-beginning-position)
573 (- (line-end-position)
578 (defun f90-count-non-optional-args (arglist)
579 "Count non-optional args in ARGLIST."
580 (loop for arg in arglist
581 count (not (member "optional" (f90-get-parsed-type-modifiers arg)))))
583 (defun f90-approx-arglist-match (arglist specialiser &optional match-sub-list)
584 "Return non-nil if ARGLIST matches the arglist of SPECIALISER.
586 If MATCH-SUB-LIST is non-nil just require that ARGLIST matches the
587 first (length ARGLIST) args of SPECIALISER."
588 (let* ((n-passed-args (length arglist))
589 (spec-arglist (f90-specialiser-arglist specialiser))
590 (n-spec-args (length spec-arglist))
591 (n-required-args (f90-count-non-optional-args spec-arglist)))
592 (when (or match-sub-list
593 (and (<= n-required-args n-passed-args)
594 (<= n-passed-args n-spec-args)))
595 (loop for arg in arglist
596 for spec-arg in spec-arglist
598 unless (or (null arg)
599 (string= (f90-get-parsed-type-typename arg)
600 (f90-get-parsed-type-typename spec-arg)))
602 finally (return t)))))
604 ;;; Internal functions
606 (defun f90-clean-comments ()
607 "Clean Fortran 90 comments from the current buffer."
609 (goto-char (point-min))
610 (set-syntax-table f90-mode-syntax-table)
611 (while (search-forward "!" nil t)
612 (when (nth 4 (parse-partial-sexp (line-beginning-position) (point)))
613 (delete-region (max (1- (point)) (line-beginning-position))
614 (line-end-position))))))
616 (defun f90-clean-continuation-lines ()
617 "Splat Fortran continuation lines in the current buffer onto one line."
619 (goto-char (point-min))
620 (while (re-search-forward "&[ \t]*\n[ \t]*&?" nil t)
621 (replace-match "" nil t))))
623 (defun f90-normalise-string (string)
624 "Return a suitably normalised version of STRING."
627 (when (string-match "\\`[ \t]+" string)
628 (setq string (replace-match "" t t string)))
629 (when (string-match "[ \t]+\\'" string)
630 (setq string (replace-match "" t t string)))
633 (defun f90-get-interface (name &optional interfaces)
634 "Get the interface with NAME from INTERFACES.
636 If INTERFACES is nil use `f90-all-interfaces' instead."
637 (gethash name (or interfaces f90-all-interfaces)))
639 (defsetf f90-get-interface (name &optional interfaces) (val)
640 `(setf (gethash ,name (or ,interfaces f90-all-interfaces)) ,val))
642 ;;; Entry point to parsing routines
644 (defun f90-parse-file-p (file)
645 "Return non-nil if FILE should be parsed.
647 This checks that FILE exists and is readable, and then calls
648 additional test functions from `f90-file-name-check-functions'."
649 (and (file-exists-p file)
650 (file-readable-p file)
651 (loop for test in f90-file-name-check-functions
652 unless (funcall test file)
654 finally (return t))))
656 (defun f90-check-fluidity-refcount (file)
657 "Return nil if FILE is that of a Fluidity refcount template."
658 (let ((fname (file-name-nondirectory file)))
659 (and (not (string-match "\\`Reference_count_interface" fname))
660 (not (string-equal "Refcount_interface_templates.F90" fname))
661 (not (string-equal "Refcount_templates.F90" fname)))))
663 (defun f90-maybe-insert-extra-files (file)
664 "Maybe insert extra files corresponding to FILE when parsing.
666 To actually insert extra files, customize the variable
667 `f90-extra-file-functions'. For an example insertion function
668 see `f90-insert-fluidity-refcount'."
669 (let ((fname (file-name-nondirectory file)))
670 (loop for fn in f90-extra-file-functions
671 do (funcall fn file fname))))
673 (defun f90-insert-fluidity-refcount (file fname)
674 "Insert a Fluidity reference count template for FILE.
676 If FNAME matches \\\\`Reference_count_.*\\\\.F90 then this file
677 needs a reference count interface, so insert one."
678 (when (string-match "\\`Reference_count_\\([^\\.]+\\)\\.F90" fname)
679 (insert-file-contents-literally
681 (format "Reference_count_interface_%s.F90"
682 (match-string 1 fname))
683 (file-name-directory file)))))
685 (defun f90-parse-interfaces (file existing)
686 "Parse interfaces in FILE and merge into EXISTING interface data."
688 (let ((interfaces (make-hash-table :test 'equal)))
689 ;; Is this file valid for parsing
690 (when (f90-parse-file-p file)
691 (insert-file-contents-literally file)
692 ;; Does this file have other parts elsewhere?
693 (f90-maybe-insert-extra-files file)
694 ;; Easier if we don't have to worry about line wrap
696 (f90-clean-continuation-lines)
697 (goto-char (point-min))
698 ;; Search forward for a named interface block
699 (while (re-search-forward
700 "^[ \t]*interface[ \t]+\\([^ \t\n]+\\)[ \t]*$" nil t)
701 (let* ((name (f90-normalise-string (match-string 1)))
703 (unless (string= name "")
704 (setq interface (make-f90-interface :name name))
706 ;; Figure out all the specialisers for this generic name
710 (format "[ \t]*end interface\\(?:[ \t]+%s\\)?[ \t]*$" name)
712 (f90-populate-specialisers interface))
713 ;; Multiple interface blocks with same name (this seems to
714 ;; be allowed). In which case merge rather than overwrite.
715 (if (f90-get-interface name interfaces)
716 (f90-merge-interface interface interfaces)
717 (setf (f90-get-interface name interfaces) interface)))))
718 (goto-char (point-min))
719 ;; Parse type definitions
721 (while (re-search-forward
722 "^[ \t]*type[ \t]+\\(?:[^ \t\n]+\\)[ \t]*$" nil t)
723 (let ((beg (match-beginning 0)))
724 (unless (re-search-forward "^[ \t]*end[ \t]+type.*$" nil t)
725 (error "Unable to find end of type definition"))
727 (narrow-to-region beg (match-beginning 0))
728 (f90-parse-type-definition)))))
730 ;; Now find out if an interface is public or private to the module
731 (f90-set-public-attribute interfaces)
733 ;; Now find the arglists corresponding to the interface (so we
734 ;; can disambiguate) and record their location in the file.
735 (loop for interface being the hash-values of interfaces
736 do (when (f90-interface-specialisers interface)
737 (maphash (lambda (specialiser val)
739 (goto-char (point-min))
740 (let ((thing (f90-argument-list specialiser)))
741 (setf (f90-specialiser-arglist
744 (setf (f90-specialiser-location
746 (list file (caddr thing)))
747 (setf (f90-specialiser-type
750 (f90-interface-specialisers interface))))
751 ;; Finally merge these new interfaces into the existing data.
752 (f90-merge-interfaces interfaces existing)))))
754 (defun f90-merge-interface (interface interfaces)
755 "Merge INTERFACE into the existing set of INTERFACES."
756 (let ((name (f90-interface-name interface))
758 (when (f90-interface-specialisers interface)
759 (loop for val being the hash-values of
760 (f90-interface-specialisers interface)
761 do (setq spec-name (f90-specialiser-name val))
762 (setf (gethash spec-name (f90-specialisers name interfaces))
765 (defun f90-merge-interfaces (new existing)
766 "Merge NEW interfaces into EXISTING ones."
767 (maphash (lambda (name val)
768 (if (gethash name existing)
769 (f90-merge-interface val existing)
770 (setf (gethash name existing)
774 (defun f90-populate-specialisers (interface)
775 "Find all specialisers for INTERFACE."
777 (goto-char (point-min))
778 (setf (f90-interface-specialisers interface)
779 (make-hash-table :test 'equal))
780 (while (search-forward "module procedure" nil t)
781 (let ((names (buffer-substring-no-properties
783 (line-end-position))))
785 (setq x (f90-normalise-string x))
786 (setf (gethash x (f90-interface-specialisers interface))
787 (make-f90-specialiser :name x)))
788 (split-string names "[, \n]+" t))))))
790 (defun f90-set-public-attribute (interfaces)
791 "Set public/private flag on all INTERFACES."
793 ;; Default public unless private is specified.
794 (let ((public (not (save-excursion
795 (re-search-forward "^[ \t]*private[ \t]*$" nil t)))))
796 (while (re-search-forward (format "^[ \t]*%s[ \t]+"
797 (if public "private" "public"))
799 (let ((names (buffer-substring-no-properties
801 (line-end-position))))
803 (maphash (lambda (k v)
805 (setf (f90-interface-publicp v) public))
807 ;; Override for those specified
809 (let ((interface (f90-get-interface name interfaces)))
811 (setf (f90-interface-publicp interface) (not public)))))
812 (split-string names "[, \t]" t)))))))
814 ;;; Type/arglist parsing
815 (defun f90-argument-list (name)
816 "Return typed argument list of function or subroutine NAME."
818 (when (re-search-forward
819 (format "\\(function\\|subroutine\\)[ \t]+%s[ \t]*("
822 (let* ((point (match-beginning 0))
823 (type (match-string 1))
824 (args (f90-split-arglist (buffer-substring-no-properties
826 (f90-end-of-arglist)))))
827 (list type (f90-arg-types args) point)))))
829 (defun f90-parse-type-definition ()
830 "Parse a type definition at (or in front of) `point'."
831 (let (type slots slot fn)
832 (goto-char (point-min))
833 (unless (re-search-forward "^[ \t]*type[ \t]+\\(.+?\\)[ \t]*$" nil t)
834 (error "Trying parse a type but no type found"))
835 (setq type (format "type(%s)" (f90-normalise-string (match-string 1))))
837 (setq slot (f90-parse-single-type-declaration))
839 (setf slots (nconc slot slots)))
841 (eval (f90-make-type-struct type slots))
842 (setq fn (intern-soft (format "make-f90-type.%s" type)))
844 (error "Something bad went wrong parsing type definition %s" type))
845 (setf (gethash type f90-types) (funcall fn))))
847 (defun f90-make-type-struct (type slots)
848 "Create a struct describing TYPE with SLOTS."
849 (let ((struct-name (make-symbol (format "f90-type.%s" type)))
850 (varnames (reverse (mapcar (lambda (x)
852 (if (string-match "\\([^(]+\\)(" x)
855 `(defstruct (,struct-name
856 (:conc-name ,(make-symbol (format "f90-type.%s." type))))
857 (-varnames ',varnames :read-only t)
858 ,@(loop for (name . rest) in slots
859 collect `(,(make-symbol name) (cons ',name ',rest)
862 (defun f90-arglist-types ()
863 "Return the types of the arguments to the function at `point'."
865 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
866 (b (save-excursion (f90-beginning-of-subprogram) (point)))
867 (str (buffer-substring-no-properties b e))
871 (with-syntax-table f90-mode-syntax-table
874 (setq p (point-marker))
875 (f90-clean-continuation-lines)
878 (setq names (f90-split-arglist (buffer-substring
880 (f90-end-of-arglist))))
881 (goto-char (point-min))
882 (f90-arg-types names))))))
884 (defun f90-list-in-scope-vars ()
885 "Pop up a buffer showing all variables in scope in the procedure at `point'"
887 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
888 (b (save-excursion (f90-beginning-of-subprogram) (point)))
889 (str (buffer-substring-no-properties b e))
892 (with-syntax-table f90-mode-syntax-table
894 (goto-char (point-min))
896 (f90-clean-continuation-lines)
897 (forward-line 1) ; skip procedure name
900 (while (and not-done (not (eobp)))
901 ;; skip "implicit none" which may appear at top of procedure
902 (when (looking-at "\\s-*implicit\\s-+none")
904 (when (not (looking-at "^\\s-*$"))
905 (setq type (ignore-errors (f90-parse-single-type-declaration)))
906 ;; If we were on a line with text and failed to parse a
907 ;; type, we must have reached the end of the type
908 ;; definitions, so don't push it on and finish.
911 (setq not-done nil)))
913 (with-current-buffer (get-buffer-create "*Variables in scope*")
914 (setq buffer-read-only nil)
917 ;; Show types of the same type together
918 (setq types (sort types (lambda (x y)
919 (string< (cadar x) (cadar y)))))
920 (loop for (type name) in types
922 (insert (format "%s :: %s\n"
923 (f90-format-parsed-slot-type type)
924 (f90-get-parsed-type-varname type))))
925 (pop-to-buffer (current-buffer))
926 (goto-char (point-min))
927 (setq buffer-read-only t))))
929 (defun f90-arg-types (names)
930 "Given NAMES of arguments return their types.
932 This works even with derived type subtypes (e.g. if A is a type(foo)
933 with slot B of type REAL, then A%B is returned being a REAL)."
934 (loop for arg in names
935 for subspec = nil then nil
936 do (setq arg (f90-normalise-string arg))
937 if (string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" arg)
938 do (setq subspec (match-string 2 arg)
939 arg (match-string 1 arg))
940 collect (save-excursion
942 (when (re-search-forward
943 (format "^[ \t]*\\([^!\n].+?\\)[ \t]*::.*\\<%s\\>"
945 (goto-char (match-beginning 0))
946 (let ((type (assoc arg
947 (f90-parse-single-type-declaration))))
948 (f90-get-type-subtype type subspec)))))))
950 (defun f90-get-type-subtype (type subspec)
951 "Return the type of TYPE possibly including slot references in SUBSPEC."
952 (cond ((null subspec)
954 ((string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" subspec)
955 (f90-get-type-subtype (f90-get-slot-type (match-string 1 subspec)
957 (match-string 2 subspec)))
959 (f90-get-slot-type subspec type))))
961 (defun f90-split-arglist (arglist &optional level)
962 "Split ARGLIST into words.
964 Split based on top-level commas. For example
966 (f90-split-arglist \"foo, bar, baz(quux, zot)\")
967 => (\"foo\" \"bar\" \"baz(quux, zot)\").
969 If LEVEL is non-nil split on commas up to and including LEVEL.
972 (f90-split-arglist \"foo, bar, baz(quux, zot)\" 1)
973 => (\"foo\" \"bar\" \"baz(quux\" \"zot)\")."
974 (setq level (or level 0))
975 (loop for c across arglist
976 for i = 0 then (1+ i)
979 with len = (length arglist)
984 if (and (<= cur-level level)
986 collect (f90-normalise-string (substring arglist b i))
987 and do (setq b (1+ i))
988 if (and (<= cur-level level)
990 collect (f90-normalise-string (substring arglist b))))
992 (defun f90-end-of-arglist ()
993 "Find the end of the arglist at `point'."
997 (cond ((eq (char-after) ?\()
999 ((eq (char-after) ?\))
1005 (defun f90-parse-names-list (names)
1006 "Return a list of NAMES from the RHS of a :: type declaration."
1007 (let ((names-list (f90-split-arglist names)))
1008 (loop for name in names-list
1009 if (string-match "\\`\\([^=]+\\)[ \t]*=.*\\'" name)
1010 collect (f90-normalise-string (match-string 1 name))
1012 collect (f90-normalise-string name))))
1014 (defun f90-parse-single-type-declaration ()
1015 "Parse a single f90 type declaration at `point'.
1017 Assumes that this has the form
1018 TYPENAME[, MODIFIERS]* :: NAME[, NAMES]*
1020 NAMES can optionally have initialisation attached to them which is
1021 dealt with correctly."
1022 (when (looking-at "^[ \t]*\\(.*?\\)[ \t]*::[ \t]*\\(.*\\)$")
1023 (let ((dec-orig (match-string 1))
1024 (names (f90-parse-names-list (match-string 2))))
1025 (loop for name in names
1026 for dec = (f90-split-declaration dec-orig)
1027 then (f90-split-declaration dec-orig)
1028 if (string-match "\\([^(]+\\)(\\([^)]+\\))" name)
1029 do (progn (if (assoc "dimension" dec)
1030 (setcdr (assoc "dimension" dec)
1031 (1+ (f90-count-commas
1032 (match-string 2 name))))
1035 (1+ (f90-count-commas
1036 (match-string 2 name))))
1038 (setq name (match-string 1 name)))
1039 collect (cons name dec)))))
1041 (defun f90-split-declaration (dec)
1042 "Split and parse a type declaration DEC.
1044 This takes the bit before the :: and returns a list of the typename
1046 (let ((things (f90-split-arglist dec)))
1047 (cons (if (string-match
1048 "\\([^(]+?\\)[ \t]*([ \t]*\\(:?len\\|kind\\)[ \t]*=[^)]+)"
1050 (match-string 1 (car things))
1052 (loop for thing in (cdr things)
1053 if (string-match "dimension[ \t]*(\\(.+\\))" thing)
1054 collect (cons "dimension"
1055 (1+ (f90-count-commas (match-string 1 thing))))
1059 (provide 'f90-interface-browser)
1061 ;;; f90-interface-browser.el ends here