]> code.delx.au - gnu-emacs-elpa/blob - packages/f90-interface-browser/f90-interface-browser.el
Fix some quoting problems in doc strings
[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, 2015 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 its 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)
276 t))
277 do (f90-parse-interfaces file f90-all-interfaces)))
278
279 (defun f90-find-tag-interface (name &optional match-sublist)
280 "List all interfaces matching NAME.
281
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 (thing-at-point 'symbol)))
288 (list (completing-read
289 (format "Find interface/tag (default %s): " def)
290 (f90-lazy-completion-table)
291 nil t nil nil def)
292 current-prefix-arg)))
293 (if (f90-valid-interface-name name)
294 (f90-browse-interface-specialisers name (f90-arglist-types)
295 match-sublist
296 (point-marker))
297 (find-tag name match-sublist)))
298
299 (defun f90-browse-interface-specialisers (name &optional arglist-to-match
300 match-sublist
301 invocation-point)
302 "Browse all interfaces matching NAME.
303
304 If ARGLIST-TO-MATCH is non-nil restrict to those interfaces that match
305 it.
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.
308
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
311 `pop-tag-mark'."
312 (interactive (let ((def (thing-at-point 'symbol)))
313 (list (completing-read
314 (format "Interface%s: "
315 (if def
316 (format " (default %s)" def)
317 ""))
318 f90-all-interfaces
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))
324 (type nil)
325 (n-specs 0))
326 (setq buffer-read-only nil)
327 (erase-buffer)
328 (setq n-specs
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))
335 do (insert
336 (propertize
337 (concat
338 (propertize
339 (format "%s [defined in %s]\n (%s)\n"
340 (propertize (f90-specialiser-name s)
341 'face 'bold)
342 (let ((f (car
343 (f90-specialiser-location s))))
344 (format "%s/%s"
345 (file-name-nondirectory
346 (directory-file-name
347 (file-name-directory f)))
348 (file-name-nondirectory f)))
349 (f90-fontify-arglist
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
355 'help-echo
356 "mouse-1: find definition in other window")
357 "\n")
358 'f90-specialiser-extent (f90-specialiser-name s)))
359 and count 1))
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"
365 (if (zerop n-specs)
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))))))
374
375 (defun f90-next-definition (&optional arg)
376 "Go to the next ARG'th specialiser definition."
377 (interactive "p")
378 (unless arg
379 (setq arg 1))
380 (while (> arg 0)
381 (goto-char (next-single-property-change
382 (point)
383 'f90-specialiser-extent
384 nil (point-max)))
385 (decf arg)))
386
387 (defun f90-previous-definition (&optional arg)
388 "Go to the previous ARG'th specialiser definition."
389 (interactive "p")
390 (unless arg
391 (setq arg 1))
392 (while (> arg 0)
393 (loop repeat 2
394 do (goto-char (previous-single-property-change
395 (point)
396 'f90-specialiser-extent
397 nil (point-min))))
398 (f90-next-definition 1)
399 (decf arg)))
400
401 (defun f90-mouse-find-definition (e)
402 "Visit the definition at the position of the event E."
403 (interactive "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)
409 (goto-char point)
410 (f90-find-definition))))
411
412 (defun f90-quit-browser ()
413 "Quit the interface browser."
414 (interactive)
415 (let ((buf f90-buffer-to-switch-to))
416 (kill-buffer (current-buffer))
417 (pop-to-buffer buf)))
418
419 (defun f90-find-definition ()
420 "Visit the definition at `point'."
421 (interactive)
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))
426 buf-to)
427 (if location
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
433 ;; location
434 (or (re-search-forward (format "%s[ \t]+%s[ \t]*("
435 type name) nil t)
436 (re-search-backward (format "%s[ \t]+%s[ \t]*("
437 type name) nil t))
438 (beginning-of-line)
439 (recenter 0)
440 (pop-to-buffer buf)
441 (setq f90-buffer-to-switch-to buf-to))
442 (error "No definition at point"))))
443
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)
453 map)
454 "Keymap for `f90-interface-browser-mode'.")
455
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))
460
461 ;;; Type definitions
462
463 (defun f90-type-at-point ()
464 "Return a guess for the type of the thing at `point'.
465
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
469 references)."
470 (let ((name (or
471 ;; Are we on a line with type(TYPENAME)?
472 (save-excursion
473 (forward-line 0)
474 (f90-parse-single-type-declaration))
475 ;; No, try and derive the type of the variable at point
476 (save-excursion
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")
481 (f90-arg-types
482 (list
483 (buffer-substring-no-properties
484 (point)
485 (progn (skip-syntax-forward "w") (point)))))))))))
486 (f90-extract-type-name (f90-get-parsed-type-typename (car name)))))
487
488 (defun f90-show-type-definition (type)
489 "Show the definition of TYPE.
490
491 This formats the parsed definition of TYPE, rather than jumping to the
492 existing definition.
493
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
496 declaration type.
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)))
500 (completing-read
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)
507 (fundamental-mode)
508 (erase-buffer)
509 (let* ((tname (format "type(%s)" type))
510 (slots (f90-get-type (list nil tname))))
511 (if (null slots)
512 (insert (format "The type %s is not a known derived type."
513 type))
514 (insert (format "type %s\n" type))
515 (loop for slot in slots
516 do
517 (insert (format " %s :: %s\n"
518 (f90-format-parsed-slot-type slot)
519 (f90-get-parsed-type-varname slot))))
520 (insert (format "end type %s\n" type))
521 (f90-mode))
522 (goto-char (point-min))
523 (view-mode)
524 (pop-to-buffer (current-buffer)))))
525
526 ;;; Arglist matching/formatting
527
528 (defun f90-format-parsed-slot-type (type)
529 "Turn a parsed TYPE into a valid f90 type declaration."
530 (if (null type)
531 "UNION-TYPE"
532 ;; Ignore name
533 (setq type (cdr type))
534 (mapconcat #'identity (loop for a in type
535 if (and (consp a)
536 (string= (car a) "dimension"))
537 collect (format "dimension(%s)"
538 (mapconcat #'identity
539 (make-list (cdr a)
540 ":")
541 ","))
542 else if (not
543 (string-match
544 "\\`intent(\\(?:in\\|out\\|inout\\))"
545 a))
546 collect a)
547 ", ")))
548
549 (defun f90-fontify-arglist (arglist)
550 "Fontify ARGLIST using `f90-mode'."
551 (with-temp-buffer
552 (if (stringp arglist)
553 (insert (format "%s :: foo\n" arglist))
554 (insert (mapconcat (lambda (x)
555 (format "%s :: foo" (f90-format-parsed-slot-type x)))
556 arglist "\n")))
557 (f90-mode)
558 (if (fboundp 'font-lock-ensure)
559 (font-lock-ensure)
560 (with-no-warnings (font-lock-fontify-buffer)))
561 (goto-char (point-min))
562 (mapconcat #'identity
563 (loop while (not (eobp))
564 collect (buffer-substring (line-beginning-position)
565 (- (line-end-position)
566 (length " :: foo")))
567 do (forward-line 1))
568 "; ")))
569
570 (defun f90-count-non-optional-args (arglist)
571 "Count non-optional args in ARGLIST."
572 (loop for arg in arglist
573 count (not (member "optional" (f90-get-parsed-type-modifiers arg)))))
574
575 (defun f90-approx-arglist-match (arglist specialiser &optional match-sub-list)
576 "Return non-nil if ARGLIST matches the arglist of SPECIALISER.
577
578 If MATCH-SUB-LIST is non-nil just require that ARGLIST matches the
579 first (length ARGLIST) args of SPECIALISER."
580 (let* ((n-passed-args (length arglist))
581 (spec-arglist (f90-specialiser-arglist specialiser))
582 (n-spec-args (length spec-arglist))
583 (n-required-args (f90-count-non-optional-args spec-arglist)))
584 (when (or match-sub-list
585 (and (<= n-required-args n-passed-args)
586 (<= n-passed-args n-spec-args)))
587 (loop for arg in arglist
588 for spec-arg in spec-arglist
589 unless (or (null arg)
590 (string= (f90-get-parsed-type-typename arg)
591 (f90-get-parsed-type-typename spec-arg)))
592 do (return nil)
593 finally (return t)))))
594
595 ;;; Internal functions
596
597 (defun f90-clean-comments ()
598 "Clean Fortran 90 comments from the current buffer."
599 (save-excursion
600 (goto-char (point-min))
601 (set-syntax-table f90-mode-syntax-table)
602 (while (search-forward "!" nil t)
603 (when (nth 4 (parse-partial-sexp (line-beginning-position) (point)))
604 (delete-region (max (1- (point)) (line-beginning-position))
605 (line-end-position))))))
606
607 (defun f90-clean-continuation-lines ()
608 "Splat Fortran continuation lines in the current buffer onto one line."
609 (save-excursion
610 (goto-char (point-min))
611 (while (re-search-forward "&[ \t]*\n[ \t]*&?" nil t)
612 (replace-match "" nil t))))
613
614 (defun f90-normalise-string (string)
615 "Return a suitably normalised version of STRING."
616 ;; Trim whitespace
617 (save-match-data
618 (when (string-match "\\`[ \t]+" string)
619 (setq string (replace-match "" t t string)))
620 (when (string-match "[ \t]+\\'" string)
621 (setq string (replace-match "" t t string)))
622 (downcase string)))
623
624 (defun f90-get-interface (name &optional interfaces)
625 "Get the interface with NAME from INTERFACES.
626
627 If INTERFACES is nil use `f90-all-interfaces' instead."
628 (gethash name (or interfaces f90-all-interfaces)))
629
630 (defsetf f90-get-interface (name &optional interfaces) (val)
631 `(setf (gethash ,name (or ,interfaces f90-all-interfaces)) ,val))
632
633 ;;; Entry point to parsing routines
634
635 (defun f90-parse-file-p (file)
636 "Return non-nil if FILE should be parsed.
637
638 This checks that FILE exists and is readable, and then calls
639 additional test functions from `f90-file-name-check-functions'."
640 (and (file-exists-p file)
641 (file-readable-p file)
642 (loop for test in f90-file-name-check-functions
643 unless (funcall test file)
644 do (return nil)
645 finally (return t))))
646
647 (defun f90-check-fluidity-refcount (file)
648 "Return nil if FILE is that of a Fluidity refcount template."
649 (let ((fname (file-name-nondirectory file)))
650 (and (not (string-match "\\`Reference_count_interface" fname))
651 (not (string-equal "Refcount_interface_templates.F90" fname))
652 (not (string-equal "Refcount_templates.F90" fname)))))
653
654 (defun f90-maybe-insert-extra-files (file)
655 "Maybe insert extra files corresponding to FILE when parsing.
656
657 To actually insert extra files, customize the variable
658 `f90-extra-file-functions'. For an example insertion function
659 see `f90-insert-fluidity-refcount'."
660 (let ((fname (file-name-nondirectory file)))
661 (loop for fn in f90-extra-file-functions
662 do (funcall fn file fname))))
663
664 (defun f90-insert-fluidity-refcount (file fname)
665 "Insert a Fluidity reference count template for FILE.
666
667 If FNAME matches \\\\=`Reference_count_\\([^\\.]+\\)\\.F90 then this file
668 needs a reference count interface, so insert one."
669 (when (string-match "\\`Reference_count_\\([^\\.]+\\)\\.F90" fname)
670 (insert-file-contents-literally
671 (expand-file-name
672 (format "Reference_count_interface_%s.F90"
673 (match-string 1 fname))
674 (file-name-directory file)))))
675
676 (defun f90-parse-interfaces (file existing)
677 "Parse interfaces in FILE and merge into EXISTING interface data."
678 (with-temp-buffer
679 (let ((interfaces (make-hash-table :test 'equal)))
680 ;; Is this file valid for parsing
681 (when (f90-parse-file-p file)
682 (insert-file-contents-literally file)
683 ;; Does this file have other parts elsewhere?
684 (f90-maybe-insert-extra-files file)
685 ;; Easier if we don't have to worry about line wrap
686 (f90-clean-comments)
687 (f90-clean-continuation-lines)
688 (goto-char (point-min))
689 ;; Search forward for a named interface block
690 (while (re-search-forward
691 "^[ \t]*interface[ \t]+\\([^ \t\n]+\\)[ \t]*$" nil t)
692 (let* ((name (f90-normalise-string (match-string 1)))
693 interface)
694 (unless (string= name "")
695 (setq interface (make-f90-interface :name name))
696 (save-restriction
697 ;; Figure out all the specialisers for this generic name
698 (narrow-to-region
699 (point)
700 (re-search-forward
701 (format "[ \t]*end interface\\(?:[ \t]+%s\\)?[ \t]*$" name)
702 nil t))
703 (f90-populate-specialisers interface))
704 ;; Multiple interface blocks with same name (this seems to
705 ;; be allowed). In which case merge rather than overwrite.
706 (if (f90-get-interface name interfaces)
707 (f90-merge-interface interface interfaces)
708 (setf (f90-get-interface name interfaces) interface)))))
709 (goto-char (point-min))
710 ;; Parse type definitions
711 (save-excursion
712 (while (re-search-forward
713 "^[ \t]*type[ \t]+\\(?:[^ \t\n]+\\)[ \t]*$" nil t)
714 (let ((beg (match-beginning 0)))
715 (unless (re-search-forward "^[ \t]*end[ \t]+type.*$" nil t)
716 (error "Unable to find end of type definition"))
717 (save-restriction
718 (narrow-to-region beg (match-beginning 0))
719 (f90-parse-type-definition)))))
720
721 ;; Now find out if an interface is public or private to the module
722 (f90-set-public-attribute interfaces)
723
724 ;; Now find the arglists corresponding to the interface (so we
725 ;; can disambiguate) and record their location in the file.
726 (loop for interface being the hash-values of interfaces
727 do (when (f90-interface-specialisers interface)
728 (maphash (lambda (specialiser val)
729 (save-excursion
730 (goto-char (point-min))
731 (let ((thing (f90-argument-list specialiser)))
732 (setf (f90-specialiser-arglist
733 val)
734 (cadr thing))
735 (setf (f90-specialiser-location
736 val)
737 (list file (caddr thing)))
738 (setf (f90-specialiser-type
739 val)
740 (car thing)))))
741 (f90-interface-specialisers interface))))
742 ;; Finally merge these new interfaces into the existing data.
743 (f90-merge-interfaces interfaces existing)))))
744
745 (defun f90-merge-interface (interface interfaces)
746 "Merge INTERFACE into the existing set of INTERFACES."
747 (let ((name (f90-interface-name interface))
748 spec-name)
749 (when (f90-interface-specialisers interface)
750 (loop for val being the hash-values of
751 (f90-interface-specialisers interface)
752 do (setq spec-name (f90-specialiser-name val))
753 (setf (gethash spec-name (f90-specialisers name interfaces))
754 val)))))
755
756 (defun f90-merge-interfaces (new existing)
757 "Merge NEW interfaces into EXISTING ones."
758 (maphash (lambda (name val)
759 (if (gethash name existing)
760 (f90-merge-interface val existing)
761 (setf (gethash name existing)
762 val)))
763 new))
764
765 (defun f90-populate-specialisers (interface)
766 "Find all specialisers for INTERFACE."
767 (save-excursion
768 (goto-char (point-min))
769 (setf (f90-interface-specialisers interface)
770 (make-hash-table :test 'equal))
771 (while (search-forward "module procedure" nil t)
772 (let ((names (buffer-substring-no-properties
773 (point)
774 (line-end-position))))
775 (mapc (lambda (x)
776 (setq x (f90-normalise-string x))
777 (setf (gethash x (f90-interface-specialisers interface))
778 (make-f90-specialiser :name x)))
779 (split-string names "[, \n]+" t))))))
780
781 (defun f90-set-public-attribute (interfaces)
782 "Set public/private flag on all INTERFACES."
783 (save-excursion
784 ;; Default public unless private is specified.
785 (let ((public (not (save-excursion
786 (re-search-forward "^[ \t]*private[ \t]*$" nil t)))))
787 (while (re-search-forward (format "^[ \t]*%s[ \t]+"
788 (if public "private" "public"))
789 nil t)
790 (let ((names (buffer-substring-no-properties
791 (match-end 0)
792 (line-end-position))))
793 ;; Set default
794 (maphash (lambda (k v)
795 (ignore k)
796 (setf (f90-interface-publicp v) public))
797 interfaces)
798 ;; Override for those specified
799 (mapc (lambda (name)
800 (let ((interface (f90-get-interface name interfaces)))
801 (when interface
802 (setf (f90-interface-publicp interface) (not public)))))
803 (split-string names "[, \t]" t)))))))
804
805 ;;; Type/arglist parsing
806 (defun f90-argument-list (name)
807 "Return typed argument list of function or subroutine NAME."
808 (save-excursion
809 (when (re-search-forward
810 (format "\\(function\\|subroutine\\)[ \t]+%s[ \t]*("
811 name)
812 nil t)
813 (let* ((point (match-beginning 0))
814 (type (match-string 1))
815 (args (f90-split-arglist (buffer-substring-no-properties
816 (point)
817 (f90-end-of-arglist)))))
818 (list type (f90-arg-types args) point)))))
819
820 (defun f90-parse-type-definition ()
821 "Parse a type definition at (or in front of) `point'."
822 (goto-char (point-min))
823 (unless (re-search-forward "^[ \t]*type[ \t]+\\(.+?\\)[ \t]*$" nil t)
824 (error "Trying parse a type but no type found"))
825 (let ((type (format "type(%s)" (f90-normalise-string (match-string 1))))
826 (slots ()))
827 (while (not (eobp))
828 (let ((slot (f90-parse-single-type-declaration)))
829 (when slot
830 (setf slots (nconc slot slots)))
831 (forward-line 1)))
832 (setf (gethash type f90-types) slots)))
833
834 (defun f90-arglist-types ()
835 "Return the types of the arguments to the function at `point'."
836 (save-excursion
837 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
838 (b (save-excursion (f90-beginning-of-subprogram) (point)))
839 (str (buffer-substring-no-properties b e))
840 (p (point))
841 names)
842 (with-temp-buffer
843 (with-syntax-table f90-mode-syntax-table
844 (insert str)
845 (goto-char (- p b))
846 (setq p (point-marker))
847 (f90-clean-continuation-lines)
848 (goto-char p)
849 (search-forward "(")
850 (setq names (f90-split-arglist (buffer-substring
851 (point)
852 (f90-end-of-arglist))))
853 (goto-char (point-min))
854 (f90-arg-types names))))))
855
856 (defun f90-list-in-scope-vars ()
857 "Pop up a buffer showing all variables in scope in the procedure at `point'"
858 (interactive)
859 (let* ((e (save-excursion (f90-end-of-subprogram) (point)))
860 (b (save-excursion (f90-beginning-of-subprogram) (point)))
861 (str (buffer-substring-no-properties b e))
862 types)
863 (with-temp-buffer
864 (with-syntax-table f90-mode-syntax-table
865 (insert str)
866 (goto-char (point-min))
867 (f90-clean-comments)
868 (f90-clean-continuation-lines)
869 (forward-line 1) ; skip procedure name
870 (let ((not-done t)
871 type)
872 (while (and not-done (not (eobp)))
873 ;; skip "implicit none" which may appear at top of procedure
874 (when (looking-at "\\s-*implicit\\s-+none")
875 (forward-line 1))
876 (when (not (looking-at "^\\s-*$"))
877 (setq type (ignore-errors (f90-parse-single-type-declaration)))
878 ;; If we were on a line with text and failed to parse a
879 ;; type, we must have reached the end of the type
880 ;; definitions, so don't push it on and finish.
881 (if type
882 (push type types)
883 (setq not-done nil)))
884 (forward-line 1)))))
885 (with-current-buffer (get-buffer-create "*Variables in scope*")
886 (setq buffer-read-only nil)
887 (erase-buffer)
888 (f90-mode)
889 ;; Show types of the same type together
890 (setq types (sort types (lambda (x y)
891 (string< (cadar x) (cadar y)))))
892 (loop for (type _name) in types
893 do
894 (insert (format "%s :: %s\n"
895 (f90-format-parsed-slot-type type)
896 (f90-get-parsed-type-varname type))))
897 (pop-to-buffer (current-buffer))
898 (goto-char (point-min))
899 (setq buffer-read-only t))))
900
901 (defun f90-arg-types (names)
902 "Given NAMES of arguments return their types.
903
904 This works even with derived type subtypes (e.g. if A is a type(foo)
905 with slot B of type REAL, then A%B is returned being a REAL)."
906 (loop for arg in names
907 for subspec = nil then nil
908 do (setq arg (f90-normalise-string arg))
909 if (string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" arg)
910 do (setq subspec (match-string 2 arg)
911 arg (match-string 1 arg))
912 collect (save-excursion
913 (save-restriction
914 (when (re-search-forward
915 (format "^[ \t]*\\([^!\n].+?\\)[ \t]*::.*\\_<%s\\_>"
916 arg) nil t)
917 (goto-char (match-beginning 0))
918 (let ((type (assoc arg
919 (f90-parse-single-type-declaration))))
920 (f90-get-type-subtype type subspec)))))))
921
922 (defun f90-get-type-subtype (type subspec)
923 "Return the type of TYPE possibly including slot references in SUBSPEC."
924 (cond ((null subspec)
925 type)
926 ((string-match "\\`\\([^%]+?\\)[ \t]*%\\(.+\\)\\'" subspec)
927 (f90-get-type-subtype (f90-get-slot-type (match-string 1 subspec)
928 type)
929 (match-string 2 subspec)))
930 (t
931 (f90-get-slot-type subspec type))))
932
933 (defun f90-split-arglist (arglist &optional level)
934 "Split ARGLIST into words.
935
936 Split based on top-level commas. For example
937
938 (f90-split-arglist \"foo, bar, baz(quux, zot)\")
939 => (\"foo\" \"bar\" \"baz(quux, zot)\").
940
941 If LEVEL is non-nil split on commas up to and including LEVEL.
942 For example:
943
944 (f90-split-arglist \"foo, bar, baz(quux, zot)\" 1)
945 => (\"foo\" \"bar\" \"baz(quux\" \"zot)\")."
946 (setq level (or level 0))
947 (loop for c across arglist
948 for i = 0 then (1+ i)
949 with cur-level = 0
950 with b = 0
951 with len = (length arglist)
952 if (eq c ?\()
953 do (incf cur-level)
954 else if (eq c ?\))
955 do (decf cur-level)
956 if (and (<= cur-level level)
957 (eq c ?,))
958 collect (f90-normalise-string (substring arglist b i))
959 and do (setq b (1+ i))
960 if (and (<= cur-level level)
961 (= (1+ i) len))
962 collect (f90-normalise-string (substring arglist b))))
963
964 (defun f90-end-of-arglist ()
965 "Find the end of the arglist at `point'."
966 (save-excursion
967 (let ((level 0))
968 (while (> level -1)
969 (cond ((eq (char-after) ?\()
970 (incf level))
971 ((eq (char-after) ?\))
972 (decf level))
973 (t nil))
974 (forward-char)))
975 (1- (point))))
976
977 (defun f90-parse-names-list (names)
978 "Return a list of NAMES from the RHS of a :: type declaration."
979 (let ((names-list (f90-split-arglist names)))
980 (loop for name in names-list
981 if (string-match "\\`\\([^=]+\\)[ \t]*=.*\\'" name)
982 collect (f90-normalise-string (match-string 1 name))
983 else
984 collect (f90-normalise-string name))))
985
986 (defun f90-parse-single-type-declaration ()
987 "Parse a single f90 type declaration at `point'.
988
989 Assumes that this has the form
990 TYPENAME[, MODIFIERS]* :: NAME[, NAMES]*
991
992 NAMES can optionally have initialisation attached to them which is
993 dealt with correctly."
994 (when (looking-at "^[ \t]*\\(.*?\\)[ \t]*::[ \t]*\\(.*\\)$")
995 (let ((dec-orig (match-string 1))
996 (names (f90-parse-names-list (match-string 2))))
997 (loop for name in names
998 for dec = (f90-split-declaration dec-orig)
999 then (f90-split-declaration dec-orig)
1000 if (string-match "\\([^(]+\\)(\\([^)]+\\))" name)
1001 do (progn (if (assoc "dimension" dec)
1002 (setcdr (assoc "dimension" dec)
1003 (1+ (f90-count-commas
1004 (match-string 2 name))))
1005 (push (cons "dimension"
1006 (1+ (f90-count-commas
1007 (match-string 2 name))))
1008 dec))
1009 (setq name (match-string 1 name)))
1010 collect (cons name (nreverse dec))))))
1011
1012 (defun f90-split-declaration (dec)
1013 "Split and parse a type declaration DEC.
1014
1015 This takes the bit before the :: and returns a list of the typename
1016 and any modifiers."
1017 (let ((things (f90-split-arglist dec)))
1018 (cons (if (string-match
1019 "\\([^(]+?\\)[ \t]*([ \t]*\\(:?len\\|kind\\)[ \t]*=[^)]+)"
1020 (car things))
1021 (match-string 1 (car things))
1022 (car things))
1023 (loop for thing in (cdr things)
1024 if (string-match "dimension[ \t]*(\\(.+\\))" thing)
1025 collect (cons "dimension"
1026 (1+ (f90-count-commas (match-string 1 thing))))
1027 else
1028 collect thing))))
1029
1030 (provide 'f90-interface-browser)
1031
1032 ;;; f90-interface-browser.el ends here