;;; ftnchek.el --- ftnchek support for fortran mode. ;; ;; Author: Judah Milgram ;; Version: 0.6 6/18/98 ;; Keywords: fortran syntax semantic ;; Current version at: http://www.glue.umd.edu/~milgram/ftnchekel.html ;; ;; Copyright 1998 Judah Milgram ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program; if not, write to the Free Software ;; Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;; ;; ================================================================== ;; ;;; FTNCHEK: Ftnchek is a fortran 77 syntax and semantics checker ;; by Dr. Robert Moniot, . Get it at ;; ftp://netlib.org/fortran ;; ;;====================================================================== ;;; About this package: ;; ;; This is still fairly unstable. All development has been done on ;; emacs 19, it may or may not work with emacs 20. May overlap some ;; functionality of the v 20 fortran.el ;; ;; INSTALLATION: ;; ;; Install ftnchek.el somewhere in your lisp load path. Maybe add ;; lines in your ~/.emacs along the lines of: ;; ;; (setq my-path (concat (getenv "HOME") "/local/share/emacs/site-lisp" ;; (setq load-path (cons my-path load-path)) ;; (add-hook 'fortran-mode-hook (require 'ftnchek-mode "ftnchek")) ;; ;; Byte-compile ftnchek.el, if you want. ;; ;; Defines functions: ;; ;; fortran-what-subprogram() ;; fortran-subprogram() ;; next-fortran-subprogram() ;; fortran-subprogram-list() ;; fortran-goto-subprogram() ;; ftnchek-region(first last temp-file) ;; ftnchek-command(fortran-source) ;; ftnchek-buffer() ;; ftnchek-subprogram() ;; fortran-first-executable() ;; ftnchek-version-display() ;; ftnchek-version() ;; ftnchek-next-error() ;; ftnchek-next-argument-data-type-mismatch() ;; ;; Fortran-mode hooks: ;; ;; key bindings ;; menu maps: fortran-mode maps ;; ftnchek-stuff. ;; ;; ==================================================================== ;; Bugs: ;; ":" in fortran file name will cause confusion. ;; ==================================================================== ;; To Do: ;; ;; ftnchek-next-arg-mismatch ;; ftnchek-other-place ;; Make default for "goto-subprogram" based on a nearby "call" statement ;; Maybe temp-files should revert to /tmp in case pwd non-writeable? ;; Make ftnchek-flags easier for user to customize (one for buffer, ;; one for subprogram)< ;; Re-implement goto-subprogram based on imenu? ;; Look at v 20 fortran.el, maybe there's something we can use. ;; normalize naming of functions ;; cleanup & speedup ;; ==================================================================== ;; Acknowledgements: ;; Bruce Ravel, Richard Stallman for advice and suggestions. ;; ==================================================================== ;; History: ;; ;; v 0.6 6/17/98 placed completion-ignore-case in a let ;; defvar ftnchek-mode ;; defun ftnchek-mode ;; V 0.5 6/14/98 implemented "ftnchek-next-error" ;; played with ftnchek-flags (array=2) ;; V 0.4 6/12/98 added require to "compile" ;; got "fortran-goto-subprogram" working ;; V 0.3 6/11/98 first public release (require 'fortran) (require 'compile) (defvar ftnchek-mode-version "0.6") (defvar ftnchek-maintainer "") (defvar ftnchek-flags nil) (defvar ftnchek-startup-message) ; maybe do this with "let"? (defvar ftnchek-mode nil "Mode variable for ftnchek minor mode") (make-variable-buffer-local 'ftnchek-mode) (defun ftnchek-mode(&optional arg) "Ftnchek minor mode." (interactive "P") (setq ftnchek-mode (if (null arg) (not ftnchek-mode) (> (prefix-numeric-value arg) 0))) ; (if ftnchek-mode ... etc. ) (defun fortran-what-subprogram() "Display the name of the FORTRAN subprogram the cursor is currently in." (interactive) (message "Currently in %s" (fortran-subprogram)) ) ;; N.B. I disagree with fortran-mode's "end-of-fortran-subprogram". ;; If the cursor is on the "end" statement, you're already there, ;; but fortran mode takes you to the end of the *next* subprogram. ;; Much of the kludgery surrounding fortran-subprogram results from ;; this sort of thing. (defun fortran-subprogram() "Return the name of the FORTRAN subprogram cursor is currently in. Suffers from fortran-mode bug: if cursor on first col of subprogram statement, thinks you're in previous subprogram." (let (here there aline name) (save-excursion (beginning-of-line) (setq here (point)) (end-of-line) (setq there (point) aline (buffer-substring here there)) (if (string-match "^ *end *$" aline) (forward-line -1)) (end-of-fortran-subprogram) (beginning-of-fortran-subprogram) (re-search-forward "^ *[^ ]") (setq here (point) here (- here 1)) (end-of-line) (setq there (point) name (buffer-substring here there) there (string-match " *\\((.*\\)*$" name)) (if there (setq name (substring name 0 there))) ) name ) ) (defun next-fortran-subprogram() "Move cursor to next subprogram" (interactive) (end-of-fortran-subprogram) ; might put you on a comment. (fortran-next-statement)) (defun fortran-subprogram-list() "Make an alist of fortran subprograms" (interactive) (save-excursion (goto-char (point-min)) (fortran-next-statement) (let (subprogram-list foo i) (while (progn (setq foo (fortran-subprogram) i (string-match "[^ ][^ ]*[ ]*$" foo) foo (substring foo i) i (point) subprogram-list (cons (list foo i) subprogram-list)) (end-of-fortran-subprogram) (not (eq (fortran-next-statement) 'last-statement)) ) ) subprogram-list ) ) ) (defun fortran-goto-subprogram() "Position cursor on beginning of a subprogram" (interactive) (let ((subprogram-list (fortran-subprogram-list)) target target-char (completion-ignore-case t)) (setq target (completing-read "Go to subprogram (TAB for completion list): " subprogram-list) target-char (car (cdr (assoc target subprogram-list)))) (goto-char target-char) ) ) ;; No point running this interactively. (defun ftnchek-region(first last temp-file) "Run ftnchek on a region using compile.el" (if (file-exists-p temp-file) (if (file-writable-p temp-file) (delete-file temp-file) (error "Cannot remove %s" temp-file) ) ) (let ((blanks (get-buffer-create "*Ftnchek-temp*")) (i 1) blanklines) (save-excursion (set-buffer blanks) (erase-buffer)) (copy-to-buffer blanks (point-min) (point-max)) ; (save-excursion (set-buffer blanks) (goto-char first) ;; There must be an easier way to get the line number! (setq blanklines (string-to-number (substring (what-line) 5))) (delete-region last (point-max)) (delete-region (point-min) first) (goto-char (point-min)) (setq i 1) (while (< i blanklines) (insert "\n") (setq i (+ i 1))) ; not write-buffer since don't want to visit the temp-file. (write-region (point-min) (point-max) temp-file) ; (compile-internal (ftnchek-command temp-file) nil) ; ) ) ) (defun ftnchek-command(fortran-source) "Form the command to run ftnchek" (concat "ftnchek " ftnchek-flags " -quiet " fortran-source)) (defun ftnchek-buffer() "Run ftnchek on current buffer." (interactive) (let (first last temp-file) (save-excursion (beginning-of-buffer) (setq first (point)) (end-of-buffer) (setq last (point) ftnchek-flags "-noextern -declare -library -usage=303 -array=2" temp-file (concat "ftnchek:" (buffer-name))) (ftnchek-region first last temp-file) ) ) ) ;; why not use fortran mode's mark-subprogram function? ;; hmmmm. (defun ftnchek-subprogram() "Run ftnchek on suprogram the cursor is in. You can run ftnchek-what-subprogram to find out what subprogram that is." (interactive) (save-excursion (let (here there temp-file) (setq temp-file (fortran-subprogram) here (string-match " [^ ]*$" temp-file) here (+ here 1) temp-file (substring temp-file here) temp-file (concat "ftnchek:" (buffer-name) ":" temp-file ".f")) (beginning-of-fortran-subprogram) (setq here (point)) (end-of-fortran-subprogram) (setq there (point) ; Isolated subprogram - common block usage n/a ftnchek-flags "-noextern -library -usage=303 -declare") (ftnchek-region here there temp-file) ) ) ) (defun fortran-first-executable() "Move cursor to first executable statement in current subprogram" (interactive) (beginning-of-fortran-subprogram) (while (progn (re-search-forward "^[ 0-9][ 0-9][ 0-9][ 0-9][ 0-9][ 0-9] *") (re-search-forward "[a-z]+") ; (make-regexp '("equivalence" "program" ; "external" "include" "common" "save" ; "parameter" "character" "subroutine" ; "function" "data" "integer" "double" ; "real" "logical" "dimension")) (string-match "c\\(haracter\\|ommon\\)\\|d\\(ata\\|imension\\|ouble\\)\\|e\\(quivalence\\|xternal\\)\\|function\\|in\\(clude\\|teger\\)\\|logical\\|p\\(arameter\\|rogram\\)\\|real\\|s\\(ave\\|ubroutine\\)" (match-string 0)))) (beginning-of-line) (message "First executable statement in %s" (fortran-subprogram)) ) ;; Fortran pull-down menu: (define-key fortran-mode-map [menu-bar fortran] (cons "Fortran 77" (make-sparse-keymap "Fortran"))) (define-key fortran-mode-map "\C-x`" 'ftnchek-next-error) ;; Fortran pull-down menu, ftnchek stuff: (define-key fortran-mode-map [menu-bar fortran menu-ftnchek-next-error] '("Next error" . ftnchek-next-error)) (define-key fortran-mode-map [menu-bar fortran menu-ftnchek-next-argument-data-type-mismatch] '("Next data type mismatch" . ftnchek-next-argument-data-type-mismatch)) (define-key fortran-mode-map [menu-bar fortran menu-ftnchek-buffer] '("ftnchek buffer" . ftnchek-buffer)) (define-key fortran-mode-map [menu-bar fortran menu-ftnchek-subprogram] '("ftnchek subprogram" . ftnchek-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-ftnchek-version] '("ftnchek version" . ftnchek-version-display)) ;; Fortran menu, fortran-mode stuff: (define-key fortran-mode-map [menu-bar fortran menu-fortran-first-executable] '("First executable statement" . fortran-first-executable)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-what-subprogram] '("What subprogram?" . fortran-what-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-goto-subprogram] '("Go to subprogram ..." . fortran-goto-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-comment-region] '("Comment region" . fortran-comment-region)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-beginning-of-fortran-subprogram] '("Beginning of subprogram" . beginning-of-fortran-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-end-of-fortran-subprogram] '("End of subprogram" . end-of-fortran-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-next-fortran-subprogram] '("Next subprogram" . next-fortran-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-fortran-column-ruler] '("Column ruler" . fortran-column-ruler)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-fortran-split-line] '("Split line" . fortran-split-line)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-fortran-indent-subprogram] '("Indent subprogram" . fortran-indent-subprogram)) (define-key fortran-mode-map [menu-bar fortran menu-fortran-mark-fortran-subprogram] '("Mark subprogram" . mark-fortran-subprogram)) ; Startup message. Possibly useless. (setq ftnchek-startup-message (concat "ftnchek.el " " Version " ftnchek-mode-version " bugs to " ftnchek-maintainer)) (message ftnchek-startup-message) (sleep-for 0.5) (defun ftnchek-version-display() "Print the ftnchek version and patch level." (interactive) (message (ftnchek-version)) ) (defun ftnchek-version() "Return ftnchek version as a string." (let (first last outbuf) (setq outbuf (get-buffer-create "*Ftnchek*")) (set-buffer outbuf) (goto-char (point-min)) (setq first (point)) (goto-char (point-max)) (setq last (point)) (if (> last first) (kill-region first last)) (call-process "ftnchek" nil outbuf nil "-help") (set-buffer outbuf) (goto-char (point-min)) (if (null (search-forward "FTNCHEK")) nil (beginning-of-line) (setq first (point)) (end-of-line) (setq last (point)) (buffer-substring first last) ) ) ) (defun ftnchek-next-error() "Find the next error reported by ftnchek" (interactive) (next-error) ; A matter of taste: ; (scroll-other-window-down 2) ) ;; Typical parse-able ftnchek error: (many are not) ;; Warning near line 70 col 12 file ftnchek:airfls.f (setq compilation-error-regexp-alist ( cons ; maybe want e.g. \\(Warning\\|Error\\|Possibly.*appearance\\) ; at beginning of this regexp. ; If you do that, adjust the index parameters accordingly. ( list " near line \\([0-9]+\\) col \\([0-9]+\\) file ftnchek:\\([^ :]+\\)\\(:[^ ]+\\)?" 3 1 2 ) compilation-error-regexp-alist ) ) (provide 'ftnchek-mode)