]> code.delx.au - gnu-emacs-elpa/blob - packages/f90-interface-browser/f90-interface-browser.el
* f90-interface-browser: Merge from upstream
[gnu-emacs-elpa] / packages / f90-interface-browser / f90-interface-browser.el
1 ;;; f90-interface-browser.el --- Parse and browse f90 interfaces
2
3 ;; Copyright (C) 2011, 2012, 2013, 2014 Free Software Foundation, Inc
4
5 ;; Author: Lawrence Mitchell <wence@gmx.li>
6 ;; Created: 2011-07-06
7 ;; URL: http://github.com/wence-/f90-iface/
8 ;; Version: 1.1
9 ;; Package-Type: simple
10
11 ;; COPYRIGHT NOTICE
12
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
17
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27 ;; You write (or work on) large, modern fortran code bases. These
28 ;; make heavy use of function overloading and generic interfaces. Your
29 ;; brain is too small to remember what all the specialisers are
30 ;; called. Therefore, your editor should help you.
31
32 ;; Load this file and tell it to parse all the fortran files in your
33 ;; code base. You can do this one directory at a time by calling
34 ;; `f90-parse-interfaces-in-dir' (M-x f90-parse-interfaces-in-dir
35 ;; RET). Or you can parse all the fortran files in a directory and
36 ;; recursively in its subdirectories by calling
37 ;; `f90-parse-all-interfaces'.
38
39 ;; Now you are able to browse (with completion) all defined interfaces
40 ;; in your code by calling `f90-browse-interface-specialisers'.
41 ;; Alternatively, if `point' is on a procedure call, you can call
42 ;; `f90-find-tag-interface' and you'll be shown a list of the
43 ;; interfaces that match the (possibly typed) argument list of the
44 ;; current procedure. This latter hooks into the `find-tag' machinery
45 ;; so that you can use it on the M-. keybinding and it will fall back
46 ;; to completing tag names if you don't want to look for an interface
47 ;; definition.
48 ;; In addition, if you're in a large procedure and want the list of
49 ;; the variables in scope (perhaps you want to define a new loop
50 ;; variable), you can use `f90-list-in-scope-vars' to pop up a buffer
51 ;; giving a reasonable guess. Note this doesn't give you module
52 ;; variables, or the variables of parent procedures if the current
53 ;; subroutine is contained within another.
54
55 ;; Derived types are also parsed, so that slot types of derived types
56 ;; are given the correct type (rather than a UNION-TYPE) when arglist
57 ;; matching. You can show the definition of a known derived type by
58 ;; calling `f90-show-type-definition' which prompts (with completion)
59 ;; for a typename to show.
60
61 ;; The parser assumes you write Fortran in the style espoused in
62 ;; Metcalf, Reid and Cohen. Particularly, variable declarations use a
63 ;; double colon to separate the type from the name list.
64
65 ;; Here's an example of a derived type definition
66 ;; type foo
67 ;; real, allocatable, dimension(:) :: a
68 ;; integer, pointer :: b, c(:)
69 ;; type(bar) :: d
70 ;; end type
71
72 ;; Here's a subroutine declaration
73 ;; subroutine foo(a, b)
74 ;; integer, intent(in) :: a
75 ;; real, intent(inout), dimension(:,:) :: b
76 ;; ...
77 ;; end subroutine foo
78
79 ;; Local procedures whose names conflict with global ones will likely
80 ;; confuse the parser. For example
81
82 ;; subroutine foo(a, b)
83 ;; ...
84 ;; end subroutine foo
85 ;;
86 ;; subroutine bar(a, b)
87 ;; ...
88 ;; call subroutine foo
89 ;; ...
90 ;; contains
91 ;; subroutine foo
92 ;; ...
93 ;; end subroutine foo
94 ;; end subroutine bar
95
96 ;; Also not handled are overloaded operators, scalar precision
97 ;; modifiers, like integer(kind=c_int), for which the precision is
98 ;; just ignored, and many other aspects.
99
100 ;; Some tests of the parser are available in f90-tests.el (in the same
101 ;; repository as this file).
102
103 ;;; Code:
104
105 ;;; Preamble
106 (eval-when-compile (require 'cl))
107 (require 'thingatpt)
108 (require 'f90)
109 (require 'etags)
110
111 (defgroup f90-iface nil
112 "Static parser for Fortran 90 code"
113 :prefix "f90-"
114 :group 'f90)
115
116 (defcustom f90-file-extensions (list "f90" "F90" "fpp")
117 "Extensions to consider when looking for Fortran 90 files."
118 :type '(repeat string)
119 :group 'f90-iface)
120
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.
123
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)
131 :group 'f90-iface)
132
133 (defcustom f90-extra-file-functions '(f90-insert-fluidity-refcount)
134 "List of functions to call to insert extra files to parse.
135
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)
140 :group 'f90-iface)
141
142 ;;; Internal variables
143 (defvar f90-interface-type nil)
144 (make-variable-buffer-local 'f90-interface-type)
145
146 (defvar f90-buffer-to-switch-to nil)
147 (make-variable-buffer-local 'f90-buffer-to-switch-to)
148
149 (defvar f90-invocation-marker nil)
150 (make-variable-buffer-local 'f90-invocation-marker)
151
152 ;; Data types for storing interface and specialiser definitions
153 (defstruct f90-interface
154 (name "" :read-only t)
155 (publicp nil)
156 specialisers)
157
158 (defstruct f90-specialiser
159 (name "" :read-only t)
160 (type "")
161 (arglist "")
162 location)
163
164 (defvar f90-all-interfaces (make-hash-table :test 'equal)
165 "Hash table populated with all known f90 interfaces.")
166
167 (defvar f90-types (make-hash-table :test 'equal)
168 "Hash table populated with all known f90 derived types.
169 The keys are type names and the values are lists of pairs of the form
170 \(NAME . REST) where NAME is the name of a slot of that type and REST
171 describes that slot.")
172
173 ;;; Inlineable utility functions
174 (defsubst f90-specialisers (name interfaces)
175 "Return all specialisers for NAME in INTERFACES."
176 (f90-interface-specialisers (f90-get-interface name interfaces)))
177
178 (defsubst f90-valid-interface-name (name)
179 "Return non-nil if NAME is an interface name."
180 (gethash name f90-all-interfaces))
181
182 (defsubst f90-count-commas (str &optional level)
183 "Count commas in STR.
184
185 If LEVEL is non-nil, only count commas up to the specified nesting
186 level. For example, a LEVEL of 0 counts top-level commas."
187 (1- (length (f90-split-arglist str level))))
188
189 (defsubst f90-get-parsed-type-varname (type)
190 "Return the variable name of TYPE."
191 (car type))
192
193 (defsubst f90-get-parsed-type-typename (type)
194 "Return the type name of TYPE."
195 (cadr type))
196
197 (defsubst f90-get-parsed-type-modifiers (type)
198 "Return the modifiers of TYPE."
199 (cddr type))
200
201 (defsubst f90-get-type (type)
202 "Return the struct definition corresponding to TYPE."
203 (gethash (f90-get-parsed-type-typename type) f90-types))
204
205 (defsubst f90-get-slot-type (slot type)
206 "Get the type of SLOT in TYPE."
207 (assoc slot (f90-get-type type)))
208
209 (defsubst f90-merge-into-tags-completion-table (ctable)
210 "Merge completions in CTABLE into the tags completion table."
211 (if (or tags-file-name tags-table-list)
212 (let ((table (tags-completion-table)))
213 (maphash (lambda (k v)
214 (ignore v)
215 (intern k table))
216 ctable)
217 table)
218 ctable))
219
220 (defun f90-lazy-completion-table ()
221 "Lazily produce a completion table of all interfaces and tag names."
222 (lexical-let ((buf (current-buffer)))
223 (lambda (string pred action)
224 (with-current-buffer buf
225 (save-excursion
226 ;; If we need to ask for the tag table, allow that.
227 (let ((enable-recursive-minibuffers t))
228 (visit-tags-table-buffer))
229 (complete-with-action action (f90-merge-into-tags-completion-table f90-all-interfaces) string pred))))))
230
231 (defsubst f90-extract-type-name (name)
232 "Return the typename from NAME.
233
234 If NAME is like type(TYPENAME) return TYPENAME, otherwise just NAME."
235 (if (and name (string-match "\\`type(\\([^)]+\\))\\'" name))
236 (match-string 1 name)
237 name))
238
239 ;;; User-visible routines
240
241 (defun f90-parse-all-interfaces (dir)
242 "Parse all interfaces found in DIR and its subdirectories.
243
244 Recurse over all (non-hidden) directories below DIR and parse
245 interfaces found within them using `f90-parse-interfaces-in-dir',
246 a directory is considered hidden if it's name doesn't start with
247 an alphanumeric character."
248 (interactive "DParse files in tree: ")
249 (let (dirs
250 attrs
251 seen
252 (pending (list (expand-file-name dir))))
253 (while pending
254 (push (pop pending) dirs)
255 (let* ((this-dir (car dirs))
256 (contents (directory-files this-dir))
257 (default-directory this-dir))
258 (setq attrs (nthcdr 10 (file-attributes this-dir)))
259 (unless (member attrs seen)
260 (push attrs seen)
261 (dolist (file contents)
262 ;; Ignore hidden directories
263 (and (string-match "\\`[[:alnum:]]" file)
264 (file-directory-p file)
265 (setq pending (nconc pending
266 (list (expand-file-name file)))))))))
267 (mapc 'f90-parse-interfaces-in-dir dirs)))
268
269 (defun f90-parse-interfaces-in-dir (dir)
270 "Parse all Fortran 90 files in DIR to populate `f90-all-interfaces'."
271 (interactive "DParse files in directory: ")
272 (loop for file in (directory-files dir t
273 (rx-to-string
274 `(and "." (or ,@f90-file-extensions)
275 eos) t))
276 do (f90-parse-interfaces file f90-all-interfaces)))
277
278 (defun f90-find-tag-interface (name &optional match-sublist)
279 "List all interfaces matching NAME.
280
281 Restricts list to those matching the (possibly typed) arglist of
282 the word at point. If MATCH-SUBLIST is non-nil, only check if
283 the arglist is a sublist of the specialiser's arglist. For more
284 details see `f90-approx-arglist-match' and
285 `f90-browse-interface-specialisers'."
286 (interactive (let ((def (thing-at-point 'symbol)))
287 (list (completing-read
288 (format "Find interface/tag (default %s): " def)
289 (f90-lazy-completion-table)
290 nil t nil nil def)
291 current-prefix-arg)))
292 (if (f90-valid-interface-name name)
293 (f90-browse-interface-specialisers name (f90-arglist-types)
294 match-sublist
295 (point-marker))
296 (find-tag name match-sublist)))
297
298 (defun f90-browse-interface-specialisers (name &optional arglist-to-match
299 match-sublist
300 invocation-point)
301 "Browse all interfaces matching NAME.
302
303 If ARGLIST-TO-MATCH is non-nil restrict to those interfaces that match
304 it.
305 If MATCH-SUBLIST is non-nil only restrict to those interfaces for
306 which ARGLIST-TO-MATCH is a sublist of the specialiser's arglist.
307
308 If INVOCATION-POINT is non-nil it should be a `point-marker'
309 indicating where we were called from, for jumping back to with
310 `pop-tag-mark'."
311 (interactive (let ((def (thing-at-point 'symbol)))
312 (list (completing-read
313 (format "Interface%s: "
314 (if def
315 (format " (default %s)" def)
316 ""))
317 f90-all-interfaces
318 nil t nil nil def))))
319 (let ((buf (current-buffer)))
320 (or invocation-point (setq invocation-point (point-marker)))
321 (with-current-buffer (get-buffer-create "*Interface Browser*")
322 (let ((interface (f90-get-interface name f90-all-interfaces))
323 (type nil)
324 (n-specs 0))
325 (setq buffer-read-only nil)
326 (erase-buffer)
327 (setq n-specs
328 (loop for s being the hash-values of
329 (f90-interface-specialisers interface)
330 do (setq type (f90-specialiser-type s))
331 when (or (null arglist-to-match)
332 (f90-approx-arglist-match
333 arglist-to-match s match-sublist))
334 do (insert
335 (propertize
336 (concat
337 (propertize
338 (format "%s [defined in %s]\n (%s)\n"
339 (propertize (f90-specialiser-name s)
340 'face 'bold)
341 (let ((f (car
342 (f90-specialiser-location s))))
343 (format "%s/%s"
344 (file-name-nondirectory
345 (directory-file-name
346 (file-name-directory f)))
347 (file-name-nondirectory f)))
348 (f90-fontify-arglist
349 (f90-specialiser-arglist s)))
350 'f90-specialiser-location
351 (f90-specialiser-location s)
352 'f90-specialiser-name (f90-specialiser-name s)
353 'mouse-face 'highlight
354 'help-echo
355 "mouse-1: find definition in other window")
356 "\n")
357 'f90-specialiser-extent (f90-specialiser-name s)))
358 and count 1))
359 (goto-char (point-min))
360 (insert (format "Interfaces for %s:\n\n"
361 (f90-interface-name interface)))
362 (when arglist-to-match
363 (insert (format "%s\n%s\n\n"
364 (if (zerop n-specs)
365 "No interfaces matching arglist (intrinsic?):"
366 "Only showing interfaces matching arglist:")
367 (f90-fontify-arglist arglist-to-match))))
368 (f90-interface-browser-mode)
369 (setq f90-buffer-to-switch-to buf)
370 (setq f90-interface-type type)
371 (setq f90-invocation-marker invocation-point)
372 (pop-to-buffer (current-buffer))))))
373
374 (defun f90-next-definition (&optional arg)
375 "Go to the next ARG'th specialiser definition."
376 (interactive "p")
377 (unless arg
378 (setq arg 1))
379 (while (> arg 0)
380 (goto-char (next-single-property-change
381 (point)
382 'f90-specialiser-extent
383 nil (point-max)))
384 (decf arg)))
385
386 (defun f90-previous-definition (&optional arg)
387 "Go to the previous ARG'th specialiser definition."
388 (interactive "p")
389 (unless arg
390 (setq arg 1))
391 (while (> arg 0)
392 (loop repeat 2
393 do (goto-char (previous-single-property-change
394 (point)
395 'f90-specialiser-extent
396 nil (point-min))))
397 (f90-next-definition 1)
398 (decf arg)))
399
400 (defun f90-mouse-find-definition (e)
401 "Visit the definition at the position of the event E."
402 (interactive "e")
403 (let ((win (posn-window (event-end e)))
404 (point (posn-point (event-end e))))
405 (when (not (windowp win))
406 (error "No definition here"))
407 (with-current-buffer (window-buffer win)
408 (goto-char point)
409 (f90-find-definition))))
410
411 (defun f90-quit-browser ()
412 "Quit the interface browser."
413 (interactive)
414 (let ((buf f90-buffer-to-switch-to))
415 (kill-buffer (current-buffer))
416 (pop-to-buffer buf)))
417
418 (defun f90-find-definition ()
419 "Visit the definition at `point'."
420 (interactive)
421 (let ((location (get-text-property (point) 'f90-specialiser-location))
422 (name (get-text-property (point) 'f90-specialiser-name))
423 (type f90-interface-type)
424 (buf (current-buffer))
425 buf-to)
426 (if location
427 (progn (ring-insert find-tag-marker-ring f90-invocation-marker)
428 (find-file-other-window (car location))
429 (setq buf-to (current-buffer))
430 (goto-char (cadr location))
431 ;; Try forwards then backwards near the recorded
432 ;; location
433 (or (re-search-forward (format "%s[ \t]+%s[ \t]*("
434 type name) nil t)
435 (re-search-backward (format "%s[ \t]+%s[ \t]*("
436 type name) nil t))
437 (beginning-of-line)
438 (recenter 0)
439 (pop-to-buffer buf)
440 (setq f90-buffer-to-switch-to buf-to))
441 (error "No definition at point"))))
442
443 (defvar f90-interface-browser-mode-map
444 (let ((map (make-sparse-keymap)))
445 (define-key map (kbd "RET") 'f90-find-definition)
446 (define-key map (kbd "<down>") 'f90-next-definition)
447 (define-key map (kbd "TAB") 'f90-next-definition)
448 (define-key map (kbd "<up>") 'f90-previous-definition)
449 (define-key map (kbd "<backtab>") 'f90-previous-definition)
450 (define-key map (kbd "q") 'f90-quit-browser)
451 (define-key map (kbd "<mouse-1>") 'f90-mouse-find-definition)
452 map)
453 "Keymap for `f90-interface-browser-mode'.")
454
455 (define-derived-mode f90-interface-browser-mode fundamental-mode "IBrowse"
456 "Major mode for browsing f90 interfaces."
457 (setq buffer-read-only t)
458 (set-buffer-modified-p nil))
459
460 ;;; Type definitions
461
462 (defun f90-type-at-point ()
463 "Return a guess for the type of the thing at `point'.
464
465 If `point' is currently on a line containing a variable declaration,
466 return the typename of the declaration. Otherwise try and figure out
467 the typename of the variable at point (possibly including slot
468 references)."
469 (let ((name (or
470 ;; Are we on a line with type(TYPENAME)?
471 (save-excursion
472 (forward-line 0)
473 (f90-parse-single-type-declaration))
474 ;; No, try and derive the type of the variable at point
475 (save-excursion
476 (let ((syntax (copy-syntax-table f90-mode-syntax-table)))
477 (modify-syntax-entry ?% "w" syntax)
478 (with-syntax-table syntax
479 (skip-syntax-backward "w")
480 (f90-arg-types
481 (list
482 (buffer-substring-no-properties
483 (point)
484 (progn (skip-syntax-forward "w") (point)))))))))))
485 (f90-extract-type-name (f90-get-parsed-type-typename (car name)))))
486
487 (defun f90-show-type-definition (type)
488 "Show the definition of TYPE.
489
490 This formats the parsed definition of TYPE, rather than jumping to the
491 existing definition.
492
493 When called interactively, default to the type of the thing at `point'.
494 If `point' is on a type declaration line, the default is the
495 declaration type.
496 If `point' is on a variable name (possibly with slot references) the
497 default is the type of the variable."
498 (interactive (list (let ((def (f90-type-at-point)))
499 (completing-read
500 (if def (format "Type (default %s): " def) "Type: ")
501 (loop for type being the hash-keys of f90-types
502 collect (f90-extract-type-name type))
503 nil t nil nil def))))
504 (with-current-buffer (get-buffer-create "*Type definition*")
505 (setq buffer-read-only nil)
506 (fundamental-mode)
507 (erase-buffer)
508 (let* ((tname (format "type(%s)" type))
509 (slots (f90-get-type (list nil tname))))
510 (if (null slots)
511 (insert (format "The type %s is not a known derived type."
512 type))
513 (insert (format "type %s\n" type))
514 (loop for slot in slots
515 do
516 (insert (format " %s :: %s\n"
517 (f90-format-parsed-slot-type slot)
518 (f90-get-parsed-type-varname slot))))
519 (insert (format "end type %s\n" type))
520 (f90-mode))
521 (goto-char (point-min))
522 (view-mode)
523 (pop-to-buffer (current-buffer)))))
524
525 ;;; Arglist matching/formatting
526
527 (defun f90-format-parsed-slot-type (type)
528 "Turn a parsed TYPE into a valid f90 type declaration."
529 (if (null type)
530 "UNION-TYPE"
531 ;; Ignore name
532 (setq type (cdr type))
533 (mapconcat 'identity (loop for a in type
534 if (and (consp a)
535 (string= (car a) "dimension"))
536 collect (format "dimension(%s)"
537 (mapconcat 'identity
538 (make-list (cdr a)
539 ":")
540 ","))
541 else if (not
542 (string-match
543 "\\`intent(\\(?:in\\|out\\|inout\\))"
544 a))
545 collect a)
546 ", ")))
547
548 (defun f90-fontify-arglist (arglist)
549 "Fontify ARGLIST using `f90-mode'."
550 (with-temp-buffer
551 (if (stringp arglist)
552 (insert (format "%s :: foo\n" arglist))
553 (insert (mapconcat (lambda (x)
554 (format "%s :: foo" (f90-format-parsed-slot-type x)))
555 arglist "\n")))
556 (f90-mode)
557 (if (fboundp 'font-lock-ensure)
558 (font-lock-ensure) (font-lock-fontify-buffer))
559 (goto-char (point-min))
560 (mapconcat 'identity
561 (loop while (not (eobp))
562 collect (buffer-substring (line-beginning-position)
563 (- (line-end-position)
564 (length " :: foo")))
565 do (forward-line 1))
566 "; ")))
567
568 (defun f90-count-non-optional-args (arglist)
569 "Count non-optional args in ARGLIST."
570 (loop for arg in arglist
571 count (not (member "optional" (f90-get-parsed-type-modifiers arg)))))
572
573 (defun f90-approx-arglist-match (arglist specialiser &optional match-sub-list)
574 "Return non-nil if ARGLIST matches the arglist of SPECIALISER.
575
576 If MATCH-SUB-LIST is non-nil just require that ARGLIST matches the
577 first (length ARGLIST) args of SPECIALISER."
578 (let* ((n-passed-args (length arglist))
579 (spec-arglist (f90-specialiser-arglist specialiser))
580 (n-spec-args (length spec-arglist))
581 (n-required-args (f90-count-non-optional-args spec-arglist)))
582 (when (or match-sub-list
583 (and (<= n-required-args n-passed-args)
584 (<= n-passed-args n-spec-args)))
585 (loop for arg in arglist
586 for spec-arg in spec-arglist
587 unless (or (null arg)
588 (string= (f90-get-parsed-type-typename arg)
589 (f90-get-parsed-type-typename spec-arg)))
590 do (return nil)
591 finally (return t)))))
592
593 ;;; Internal functions
594
595 (defun f90-clean-comments ()
596 "Clean Fortran 90 comments from the current buffer."
597 (save-excursion
598 (goto-char (point-min))
599 (set-syntax-table f90-mode-syntax-table)
600 (while (search-forward "!" nil t)
601 (when (nth 4 (parse-partial-sexp (line-beginning-position) (point)))
602 (delete-region (max (1- (point)) (line-beginning-position))
603 (line-end-position))))))
604
605 (defun f90-clean-continuation-lines ()
606 "Splat Fortran continuation lines in the current buffer onto one line."
607 (save-excursion
608 (goto-char (point-min))
609 (while (re-search-forward "&[ \t]*\n[ \t]*&?" nil t)
610 (replace-match "" nil t))))
611
612 (defun f90-normalise-string (string)
613 "Return a suitably normalised version of STRING."
614 ;; Trim whitespace
615 (save-match-data
616 (when (string-match "\\`[ \t]+" string)
617 (setq string (replace-match "" t t string)))
618 (when (string-match "[ \t]+\\'" string)
619 (setq string (replace-match "" t t string)))
620 (downcase string)))
621
622 (defun f90-get-interface (name &optional interfaces)
623 "Get the interface with NAME from INTERFACES.
624
625 If INTERFACES is nil use `f90-all-interfaces' instead."
626 (gethash name (or interfaces f90-all-interfaces)))
627
628 (defsetf f90-get-interface (name &optional interfaces) (val)
629 `(setf (gethash ,name (or ,interfaces f90-all-interfaces)) ,val))
630
631 ;;; Entry point to parsing routines
632
633 (defun f90-parse-file-p (file)
634 "Return non-nil if FILE should be parsed.
635
636 This checks that FILE exists and is readable, and then calls
637 additional test functions from `f90-file-name-check-functions'."
638 (and (file-exists-p file)
639 (file-readable-p file)
640 (loop for test in f90-file-name-check-functions
641 unless (funcall test file)
642 do (return nil)
643 finally (return t))))
644
645 (defun f90-check-fluidity-refcount (file)
646 "Return nil if FILE is that of a Fluidity refcount template."
647 (let ((fname (file-name-nondirectory file)))
648 (and (not (string-match "\\`Reference_count_interface" fname))
649 (not (string-equal "Refcount_interface_templates.F90" fname))
650 (not (string-equal "Refcount_templates.F90" fname)))))
651
652 (defun f90-maybe-insert-extra-files (file)
653 "Maybe insert extra files corresponding to FILE when parsing.
654
655 To actually insert extra files, customize the variable
656 `f90-extra-file-functions'. For an example insertion function
657 see `f90-insert-fluidity-refcount'."
658 (let ((fname (file-name-nondirectory file)))
659 (loop for fn in f90-extra-file-functions
660 do (funcall fn file fname))))
661
662 (defun f90-insert-fluidity-refcount (file fname)
663 "Insert a Fluidity reference count template for FILE.
664
665 If FNAME matches \\\\`Reference_count_.*\\\\.F90 then this file
666 needs a reference count interface, so insert one."
667 (when (string-match "\\`Reference_count_\\([^\\.]+\\)\\.F90" fname)
668 (insert-file-contents-literally
669 (expand-file-name
670 (format "Reference_count_interface_%s.F90"
671 (match-string 1 fname))
672 (file-name-directory file)))))
673
674 (defun f90-parse-interfaces (file existing)
675 "Parse interfaces in FILE and merge into EXISTING interface data."
676 (with-temp-buffer
677 (let ((interfaces (make-hash-table :test 'equal)))
678 ;; Is this file valid for parsing
679 (when (f90-parse-file-p file)
680 (insert-file-contents-literally file)
681 ;; Does this file have other parts elsewhere?
682 (f90-maybe-insert-extra-files file)
683 ;; Easier if we don't have to worry about line wrap
684 (f90-clean-comments)
685 (f90-clean-continuation-lines)
686 (goto-char (point-min))
687 ;; Search forward for a named interface block
688 (while (re-search-forward
689 "^[ \t]*interface[ \t]+\\([^ \t\n]+\\)[ \t]*$" nil t)
690 (let* ((name (f90-normalise-string (match-string 1)))
691 interface)
692 (unless (string= name "")
693 (setq interface (make-f90-interface :name name))
694 (save-restriction
695 ;; Figure out all the specialisers for this generic name
696 (narrow-to-region
697 (point)
698 (re-search-forward
699 (format "[ \t]*end interface\\(?:[ \t]+%s\\)?[ \t]*$" name)
700 nil t))
701 (f90-populate-specialisers interface))
702 ;; Multiple interface blocks with same name (this seems to
703 ;; be allowed). In which case merge rather than overwrite.
704 (if (f90-get-interface name interfaces)
705 (f90-merge-interface interface interfaces)
706 (setf (f90-get-interface name interfaces) interface)))))
707 (goto-char (point-min))
708 ;; Parse type definitions
709 (save-excursion
710 (while (re-search-forward
711 "^[ \t]*type[ \t]+\\(?:[^ \t\n]+\\)[ \t]*$" nil t)
712 (let ((beg (match-beginning 0)))
713 (unless (re-search-forward "^[ \t]*end[ \t]+type.*$" nil t)
714 (error "Unable to find end of type definition"))
715 (save-restriction
716 (narrow-to-region beg (match-beginning 0))
717 (f90-parse-type-definition)))))
718
719 ;; Now find out if an interface is public or private to the module
720 (f90-set-public-attribute interfaces)
721
722 ;; Now find the arglists corresponding to the interface (so we
723 ;; can disambiguate) and record their location in the file.
724 (loop for interface being the hash-values of interfaces
725 do (when (f90-interface-specialisers interface)
726 (maphash (lambda (specialiser val)
727 (save-excursion
728 (goto-char (point-min))
729 (let ((thing (f90-argument-list specialiser)))
730 (setf (f90-specialiser-arglist
731 val)
732 (cadr thing))
733 (setf (f90-specialiser-location
734 val)
735 (list file (caddr thing)))
736 (setf (f90-specialiser-type
737 val)
738 (car thing)))))
739 (f90-interface-specialisers interface))))
740 ;; Finally merge these new interfaces into the existing data.
741 (f90-merge-interfaces interfaces existing)))))
742
743 (defun f90-merge-interface (interface interfaces)
744 "Merge INTERFACE into the existing set of INTERFACES."
745 (let ((name (f90-interface-name interface))
746 spec-name)
747 (when (f90-interface-specialisers interface)
748 (loop for val being the hash-values of
749 (f90-interface-specialisers interface)
750 do (setq spec-name (f90-specialiser-name val))
751 (setf (gethash spec-name (f90-specialisers name interfaces))
752 val)))))
753
754 (defun f90-merge-interfaces (new existing)
755 "Merge NEW interfaces into EXISTING ones."
756 (maphash (lambda (name val)
757 (if (gethash name existing)
758 (f90-merge-interface val existing)
759 (setf (gethash name existing)
760 val)))
761 new))
762
763 (defun f90-populate-specialisers (interface)
764 "Find all specialisers for INTERFACE."
765 (save-excursion
766 (goto-char (point-min))
767 (setf (f90-interface-specialisers interface)
768 (make-hash-table :test 'equal))
769 (while (search-forward "module procedure" nil t)
770 (let ((names (buffer-substring-no-properties
771 (point)
772 (line-end-position))))
773 (mapc (lambda (x)
774 (setq x (f90-normalise-string x))
775 (setf (gethash x (f90-interface-specialisers interface))
776 (make-f90-specialiser :name x)))
777 (split-string names "[, \n]+" t))))))
778
779 (defun f90-set-public-attribute (interfaces)
780 "Set public/private flag on all INTERFACES."
781 (save-excursion
782 ;; Default public unless private is specified.
783 (let ((public (not (save-excursion
784 (re-search-forward "^[ \t]*private[ \t]*$" nil t)))))
785 (while (re-search-forward (format "^[ \t]*%s[ \t]+"
786 (if public "private" "public"))
787 nil t)
788 (let ((names (buffer-substring-no-properties
789 (match-end 0)
790 (line-end-position))))
791 ;; Set default
792 (maphash (lambda (k v)
793 (ignore k)
794 (setf (f90-interface-publicp v) public))
795 interfaces)
796 ;; Override for those specified
797 (mapc (lambda (name)
798 (let ((interface (f90-get-interface name interfaces)))
799 (when interface
800 (setf (f90-interface-publicp interface) (not public)))))
801 (split-string names "[, \t]" t)))))))
802
803 ;;; Type/arglist parsing
804 (defun f90-argument-list (name)
805 "Return typed argument list of function or subroutine NAME."
806 (save-excursion
807 (when (re-search-forward
808 (format "\\(function\\|subroutine\\)[ \t]+%s[ \t]*("
809 name)
810 nil t)
811 (let* ((point (match-beginning 0))
812 (type (match-string 1))
813 (args (f90-split-arglist (buffer-substring-no-properties
814 (point)
815 (f90-end-of-arglist)))))
816 (list type (f90-arg-types args) point)))))
817
818 (defun f90-parse-type-definition ()
819 "Parse a type definition at (or in front of) `point'."
820 (let (type slots slot fn)
821 (goto-char (point-min))
822 (unless (re-search-forward "^[ \t]*type[ \t]+\\(.+?\\)[ \t]*$" nil t)
823 (error "Trying parse a type but no type found"))
824 (setq type (format "type(%s)" (f90-normalise-string (match-string 1))))
825 (while (not (eobp))
826 (setq slot (f90-parse-single-type-declaration))
827 (when slot
828 (setf slots (nconc slot slots)))
829 (forward-line 1))
830 (setf (gethash type f90-types) slots)))
831
832 (defun f90-arglist-types ()
833 "Return the types of the arguments to the function at `point'."
834 (save-excursion
835 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
836 (b (save-excursion (f90-beginning-of-subprogram) (point)))
837 (str (buffer-substring-no-properties b e))
838 (p (point))
839 names)
840 (with-temp-buffer
841 (with-syntax-table f90-mode-syntax-table
842 (insert str)
843 (goto-char (- p b))
844 (setq p (point-marker))
845 (f90-clean-continuation-lines)
846 (goto-char p)
847 (search-forward "(")
848 (setq names (f90-split-arglist (buffer-substring
849 (point)
850 (f90-end-of-arglist))))
851 (goto-char (point-min))
852 (f90-arg-types names))))))
853
854 (defun f90-list-in-scope-vars ()
855 "Pop up a buffer showing all variables in scope in the procedure at `point'"
856 (interactive)
857 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
858 (b (save-excursion (f90-beginning-of-subprogram) (point)))
859 (str (buffer-substring-no-properties b e))
860 types)
861 (with-temp-buffer
862 (with-syntax-table f90-mode-syntax-table
863 (insert str)
864 (goto-char (point-min))
865 (f90-clean-comments)
866 (f90-clean-continuation-lines)
867 (forward-line 1) ; skip procedure name
868 (let ((not-done t)
869 type)
870 (while (and not-done (not (eobp)))
871 ;; skip "implicit none" which may appear at top of procedure
872 (when (looking-at "\\s-*implicit\\s-+none")
873 (forward-line 1))
874 (when (not (looking-at "^\\s-*$"))
875 (setq type (ignore-errors (f90-parse-single-type-declaration)))
876 ;; If we were on a line with text and failed to parse a
877 ;; type, we must have reached the end of the type
878 ;; definitions, so don't push it on and finish.
879 (if type
880 (push type types)
881 (setq not-done nil)))
882 (forward-line 1)))))
883 (with-current-buffer (get-buffer-create "*Variables in scope*")
884 (setq buffer-read-only nil)
885 (erase-buffer)
886 (f90-mode)
887 ;; Show types of the same type together
888 (setq types (sort types (lambda (x y)
889 (string< (cadar x) (cadar y)))))
890 (loop for (type _name) in types
891 do
892 (insert (format "%s :: %s\n"
893 (f90-format-parsed-slot-type type)
894 (f90-get-parsed-type-varname type))))
895 (pop-to-buffer (current-buffer))
896 (goto-char (point-min))
897 (setq buffer-read-only t))))
898
899 (defun f90-arg-types (names)
900 "Given NAMES of arguments return their types.
901
902 This works even with derived type subtypes (e.g. if A is a type(foo)
903 with slot B of type REAL, then A%B is returned being a REAL)."
904 (loop for arg in names
905 for subspec = nil then nil
906 do (setq arg (f90-normalise-string arg))
907 if (string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" arg)
908 do (setq subspec (match-string 2 arg)
909 arg (match-string 1 arg))
910 collect (save-excursion
911 (save-restriction
912 (when (re-search-forward
913 (format "^[ \t]*\\([^!\n].+?\\)[ \t]*::.*\\_<%s\\_>"
914 arg) nil t)
915 (goto-char (match-beginning 0))
916 (let ((type (assoc arg
917 (f90-parse-single-type-declaration))))
918 (f90-get-type-subtype type subspec)))))))
919
920 (defun f90-get-type-subtype (type subspec)
921 "Return the type of TYPE possibly including slot references in SUBSPEC."
922 (cond ((null subspec)
923 type)
924 ((string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" subspec)
925 (f90-get-type-subtype (f90-get-slot-type (match-string 1 subspec)
926 type)
927 (match-string 2 subspec)))
928 (t
929 (f90-get-slot-type subspec type))))
930
931 (defun f90-split-arglist (arglist &optional level)
932 "Split ARGLIST into words.
933
934 Split based on top-level commas. For example
935
936 (f90-split-arglist \"foo, bar, baz(quux, zot)\")
937 => (\"foo\" \"bar\" \"baz(quux, zot)\").
938
939 If LEVEL is non-nil split on commas up to and including LEVEL.
940 For example:
941
942 (f90-split-arglist \"foo, bar, baz(quux, zot)\" 1)
943 => (\"foo\" \"bar\" \"baz(quux\" \"zot)\")."
944 (setq level (or level 0))
945 (loop for c across arglist
946 for i = 0 then (1+ i)
947 with cur-level = 0
948 with b = 0
949 with len = (length arglist)
950 if (eq c ?\()
951 do (incf cur-level)
952 else if (eq c ?\))
953 do (decf cur-level)
954 if (and (<= cur-level level)
955 (eq c ?,))
956 collect (f90-normalise-string (substring arglist b i))
957 and do (setq b (1+ i))
958 if (and (<= cur-level level)
959 (= (1+ i) len))
960 collect (f90-normalise-string (substring arglist b))))
961
962 (defun f90-end-of-arglist ()
963 "Find the end of the arglist at `point'."
964 (save-excursion
965 (let ((level 0))
966 (while (> level -1)
967 (cond ((eq (char-after) ?\()
968 (incf level))
969 ((eq (char-after) ?\))
970 (decf level))
971 (t nil))
972 (forward-char)))
973 (1- (point))))
974
975 (defun f90-parse-names-list (names)
976 "Return a list of NAMES from the RHS of a :: type declaration."
977 (let ((names-list (f90-split-arglist names)))
978 (loop for name in names-list
979 if (string-match "\\`\\([^=]+\\)[ \t]*=.*\\'" name)
980 collect (f90-normalise-string (match-string 1 name))
981 else
982 collect (f90-normalise-string name))))
983
984 (defun f90-parse-single-type-declaration ()
985 "Parse a single f90 type declaration at `point'.
986
987 Assumes that this has the form
988 TYPENAME[, MODIFIERS]* :: NAME[, NAMES]*
989
990 NAMES can optionally have initialisation attached to them which is
991 dealt with correctly."
992 (when (looking-at "^[ \t]*\\(.*?\\)[ \t]*::[ \t]*\\(.*\\)$")
993 (let ((dec-orig (match-string 1))
994 (names (f90-parse-names-list (match-string 2))))
995 (loop for name in names
996 for dec = (f90-split-declaration dec-orig)
997 then (f90-split-declaration dec-orig)
998 if (string-match "\\([^(]+\\)(\\([^)]+\\))" name)
999 do (progn (if (assoc "dimension" dec)
1000 (setcdr (assoc "dimension" dec)
1001 (1+ (f90-count-commas
1002 (match-string 2 name))))
1003 (push (cons "dimension"
1004 (1+ (f90-count-commas
1005 (match-string 2 name))))
1006 dec))
1007 (setq name (match-string 1 name)))
1008 collect (cons name (nreverse dec))))))
1009
1010 (defun f90-split-declaration (dec)
1011 "Split and parse a type declaration DEC.
1012
1013 This takes the bit before the :: and returns a list of the typename
1014 and any modifiers."
1015 (let ((things (f90-split-arglist dec)))
1016 (cons (if (string-match
1017 "\\([^(]+?\\)[ \t]*([ \t]*\\(:?len\\|kind\\)[ \t]*=[^)]+)"
1018 (car things))
1019 (match-string 1 (car things))
1020 (car things))
1021 (loop for thing in (cdr things)
1022 if (string-match "dimension[ \t]*(\\(.+\\))" thing)
1023 collect (cons "dimension"
1024 (1+ (f90-count-commas (match-string 1 thing))))
1025 else
1026 collect thing))))
1027
1028 (provide 'f90-interface-browser)
1029
1030 ;;; f90-interface-browser.el ends here