[comp.lang.eiffel] Looking for an Eiffel-mode for GNU Emacs

weiner@novavax.UUCP (Bob Weiner) (09/19/89)

If anyone has one, could you mail or post the code.  Thanks.

-- 
Bob Weiner, Motorola, Inc.,   USENET:  ...!gatech!uflorida!novavax!weiner
(407) 738-2087

om@icsib2 (Stephen M. Omohundro) (09/26/89)

Here is something I wrote that does most things correctly (if you have
keywords inside quotes, you must follow them with a "\" as in "this
is\ a" to keep the indentation from getting confused; it also doesn't
have anything for the features of the new release). I'd be interested
in ehancements anyone might make:

;; Major mode for editing Eiffel programs.
;; Author: Stephen M. Omohundro 
;; International Computer Science Institute
;; om@icsi.berkeley.edu
;; Created: May 26, 1989
;;
;; The following two statements, placed in a .emacs file or site-init.el,
;; will cause this file to be autoloaded, and eiffel-mode invoked, when
;; visiting .e files:
;;
;;	(autoload 'eiffel-mode "eiffel.el" "Eiffel mode" t nil)
;;      (setq auto-mode-alist
;;            (append
;;              (list (cons "\\.e$" 'eiffel-mode))
;;              auto-mode-alist))
  
(provide 'eiffel-mode)

(defvar eiffel-mode-map nil 
  "Keymap for Eiffel mode.")
(if eiffel-mode-map ()
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-cc" 'eiffel-class)
    (define-key map "\C-cr" 'eiffel-routine)
    (define-key map "\C-ci" 'eiffel-if)
    (define-key map "\C-cl" 'eiffel-loop)
    (define-key map "\C-cs" 'eiffel-set)
    (define-key map "\t" 'eiffel-indent-line)
    (define-key map "\r" 'eiffel-return)
    (define-key map "\177" 'backward-delete-char-untabify)
    (define-key map "\M-;" 'eiffel-comment)
    (setq eiffel-mode-map map)))

(defvar eiffel-mode-syntax-table nil
  "Syntax table in use in Eiffel-mode buffers.")

(if eiffel-mode-syntax-table
    ()
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?\\ "\\" table)
    (modify-syntax-entry ?/ ". 14" table)
    (modify-syntax-entry ?* ". 23" table)
    (modify-syntax-entry ?+ "." table)
    (modify-syntax-entry ?- "." table)
    (modify-syntax-entry ?= "." table)
    (modify-syntax-entry ?% "." table)
    (modify-syntax-entry ?< "." table)
    (modify-syntax-entry ?> "." table)
    (modify-syntax-entry ?& "." table)
    (modify-syntax-entry ?| "." table)
    (modify-syntax-entry ?\' "\"" table)
    (setq eiffel-mode-syntax-table table)))

(defvar eiffel-mode-abbrev-table 
  "*Abbrev table in use in Eiffel-mode buffers.")
(if eiffel-mode-abbrev-table
    nil
  (define-abbrev-table 'eiffel-mode-abbrev-table ())
  (define-abbrev eiffel-mode-abbrev-table "int" "INTEGER" nil)
  (define-abbrev eiffel-mode-abbrev-table "boo" "BOOLEAN" nil)
  (define-abbrev eiffel-mode-abbrev-table "cha" "CHARACTER" nil)
  (define-abbrev eiffel-mode-abbrev-table "str" "STRING" nil)
  (define-abbrev eiffel-mode-abbrev-table "res" "Result" nil)
  (define-abbrev eiffel-mode-abbrev-table "cre" "Create" nil)
  (define-abbrev eiffel-mode-abbrev-table "cur" "Current" nil))

(defconst eiffel-indent 3
  "*This variable gives the indentation in Eiffel-mode")

(defconst eiffel-comment-col 32
  "*This variable gives the desired comment column for comments to the right
of text.")

(defun eiffel-mode ()
  "A major editing mode for the language Eiffel.
Comments are begun with --.
Paragraphs are separated by blank lines
Delete converts tabs to spaces as it moves back.
Tab anywhere on a line indents it according to Eiffel conventions.
M-; inserts and indents a comment on the line, or indents an existing
comment if there is one.
Return indents to the expected indentation for the new line.
Skeletons of the major Eiffel constructs are inserted with:

 C-c c class           C-c i if          C-c set
 C-c r routine         C-c l loop

Abbreviations:
 int   for  INTEGER           boo  for  BOOLEAN
 cha   for  CHARACTER         str  for  STRING
 res   for  Result            cre  for  Create
 cur   for  Current

Variables controlling style:
   eiffel-indent          Indentation of Eiffel statements.
   eiffel-comment-col     Goal column for inline comments

Turning on Eiffel mode calls the value of the variable eiffel-mode-hook with
no args, if that value is non-nil."
  (interactive)
  (kill-all-local-variables)
  (use-local-map eiffel-mode-map)
  (setq major-mode 'eiffel-mode)
  (setq mode-name "Eiffel")
  (setq local-abbrev-table eiffel-mode-abbrev-table)
  (set-syntax-table eiffel-mode-syntax-table)
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "^$\\|" page-delimiter))
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate paragraph-start)
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (setq paragraph-ignore-fill-prefix t)
  (make-local-variable 'require-final-newline)
  (setq require-final-newline t)
  (run-hooks 'eiffel-mode-hook))

(defun eiffel-class ()
  "Build a class skeleton prompting for class name."
  (interactive)
  (if (not (e-empty-line-p))
      (progn (end-of-line)(newline)))
  (indent-to 0)				
  (insert "-- Author: " (user-full-name) "\n")
  (insert "-- Created: " (current-time-string) "\n\n")
  (insert "class ")
  (let ((cname (read-string "Class: ")))
    (insert cname 
	    " export\n\ninherit\n\nfeature\n\ninvariant\n\nend -- class "
	    cname))
  (re-search-backward "\ninherit")
  (eiffel-indent-line))

(defun eiffel-routine ()
  "Build a routine skeleton prompting for routine name."
  (interactive)
  (if (not (e-empty-line-p))
      (progn (end-of-line)(newline)))
  (indent-to eiffel-indent)				
  (let ((rname (read-string "Routine name: ")))
    (insert rname " () is\n")
    (indent-to (* 3 eiffel-indent))
    (insert "-- \n")
    (indent-to (* 2 eiffel-indent))
    (insert "require\n\n")
    (indent-to (* 2 eiffel-indent))
    (insert "local\n\n")
    (indent-to (* 2 eiffel-indent))
    (insert "do\n\n")
    (indent-to (* 2 eiffel-indent))
    (insert "ensure\n\n")
    (indent-to (* 2 eiffel-indent))
    (insert "end; -- " rname "\n")
    (re-search-backward ")")))				

(defun eiffel-if ()
  "Makes a template for an Eiffel if statement."
  (interactive)
  (insert "if  then")
  (eiffel-indent-line)
  (insert "\n\nelse")
  (eiffel-indent-line)
  (insert "\n\nend; -- if")
  (eiffel-indent-line)
  (re-search-backward " then"))

(defun eiffel-loop ()
  "Makes a template for an Eiffel loop statement."
  (interactive)
  (insert "from  ")
  (eiffel-indent-line)
  (insert "\n\ninvariant")
  (eiffel-indent-line)
  (insert "\n\nvariant")
  (eiffel-indent-line)
  (insert "\n\nuntil")
  (eiffel-indent-line)
  (insert "\n\nloop")
  (eiffel-indent-line)
  (insert "\n\nend; -- loop")
  (eiffel-indent-line)
  (re-search-backward "from")(forward-line)(eiffel-indent-line))

(defun eiffel-set ()
  "Makes a function to set the value of the given variable."
  (interactive)
  (let ((aname (read-string "Attribute name: ")))
    (insert "set_" aname "(n" aname ":) is do " aname ":=n" aname " end;")
    (eiffel-indent-line)
    (re-search-backward ")")))

(defun eiffel-return ()
  "Return and Eiffel indent in the new line."
  (interactive)
  (newline)(eiffel-indent-line))

(defun eiffel-indent-line ()
  "Indent the current line as Eiffel code."
  (interactive)
  (save-excursion
    (beginning-of-line)
    (delete-horizontal-space)
    (indent-to (e-calc-indent)))
  (skip-chars-forward " \t"))

;; Let us call the keywords: class, deferred class, if, from, check, 
;; is, and debug block-head keywords. They start new blocks of
;; indentation which end with end.

;; A line is either
;;    blank, 
;;    just a comment, 
;;    begins with a block-cont-keyword
;;       :export, inherit, feature, rescue, invariant, end
;;       :require, external, local, do, once, deferred, ensure
;;       :then, elsif, else, variant, until, loop
;;    begins with check or debug
;;    a block-head or something else

(defun e-calc-indent ()
  "Return the appropriate indentation for this line as an int."
  (cond ((e-empty-line-p)		;an empty line 
	 (+ eiffel-indent (e-get-block-indent))) ;go in one from block
	((e-comment-line-p)		;a comment line
	 (e-comment-indent))
	((e-block-cont-p)		;begins with cont keyword
	 (e-get-block-indent))		;indent same as block
	((e-debug-block-p)		;check or debug
	 (+ (* 2 eiffel-indent) (e-get-block-indent))) ;goes two in
	(t				;block-head or something else
	 (+ eiffel-indent (e-get-block-indent)))))

(defun eiffel-comment ()
  "Edit a comment on the line. If one exists, reindent it and move to it, 
otherwise, create one. Gets rid of trailing blanks, puts one space between
comment header comment text, leaves point at front of comment. If comment is
alone on a line it reindents relative to surrounding text. If it is before
any code, puts it at beginning of line. Uses the variable eiffel-comment-col 
to set goal start on lines after text."
  (interactive)
  (cond ((e-comment-line-p)		;just a comment on the line
	 (beginning-of-line)
	 (delete-horizontal-space)
	 (indent-to (e-comment-indent))
	 (forward-char 2)(delete-horizontal-space)(insert " "))
	((e-comment-on-line-p)		;comment already at end of line
	 (cond ((e-ends-with-end-p)	;end comments come immediately
		(e-goto-comment-beg)(delete-horizontal-space)(insert " ")
		(forward-char 2)(delete-horizontal-space)(insert " "))
	       (t
		(e-goto-comment-beg)(delete-horizontal-space)
		(if (< (current-column) eiffel-comment-col)
		    (indent-to eiffel-comment-col)
		  (insert " "))
		(forward-char 2)(delete-horizontal-space)(insert " "))))
	((e-empty-line-p)		;put just a comment on line
	 (beginning-of-line)
	 (delete-horizontal-space)
	 (indent-to (e-comment-indent))
	 (insert "-- "))
	((e-ends-with-end-p)		;end comments come immediately
	 (end-of-line)(delete-horizontal-space)(insert " -- "))
	(t				;put comment at end of line
	 (end-of-line)
	 (delete-horizontal-space)
	 (if (< (current-column) eiffel-comment-col)
	     (indent-to eiffel-comment-col)
	   (insert " "))
	 (insert "-- "))))
  
(defun e-ends-with-end-p ()
  "t if line ends with end or end; and a comment."
  (save-excursion
    (beginning-of-line)
    (looking-at "^[^\n]*end;?[ \t]*\\($\\|--\\)")))

(defun e-empty-line-p ()
  "True if current line is empty."
  (save-excursion
    (beginning-of-line)
    (looking-at "^[ \\t]*$")))

(defun e-comment-line-p ()
  "t if current line is just a comment."
  (save-excursion
    (beginning-of-line)
    (skip-chars-forward " \t")
    (looking-at "--")))

(defun e-comment-on-line-p ()
  "t if current line contains a comment."
  (save-excursion
    (beginning-of-line)
    (looking-at "[^\n]*--")))

(defun e-current-indentation ()
  "Return current indentation of front of line."
  (save-excursion
    (beginning-of-line)
    (skip-chars-forward " \t")
    (current-indentation)))

(defun e-goto-comment-beg ()
  "goes to beginning of comment on line."
  (beginning-of-line)
  (re-search-forward "--")(backward-char 2))

(defun e-block-cont-p ()
  "t if line continues the indentation of enclosing block."
  (save-excursion
    (beginning-of-line)
    (skip-chars-forward " \t")
    (looking-at "\\(export\\|inherit\\|feature\\|rescue\\|invariant\\|\
end\\|require\\|external\\|local\\|do\\|once\\|deferred\\|ensure\\|\
then\\|elsif\\|else\\|variant\\|until\\|loop\\)\\>")))

(defun e-debug-block-p ()
  "t if line begins with check or debug (and so gets double indent)."
  (save-excursion
    (beginning-of-line)
    (skip-chars-forward " \t")
    (looking-at "\\(check\\|debug\\)\\>")))

;; Eiffel keywords: 
;; and as check class debug deferred div do else elsif end ensure
;; export external false feature from if inherit invariant is language
;; like local loop mod name nochange not old once or redefine rename require
;; rescue retry then true until variant

(defun e-is-key-end ()
  "t if current line ends with the keyword is and comment."
  (save-excursion
    (beginning-of-line)
    (looking-at "^.*\\<is[ \t]*\\($\\|--\\)")))

(defun e-move-to-prev-non-comment ()
  "moves point to previous line excluding comment lines and blank lines. 
Returns t if successful, nil if not."
  (beginning-of-line)
  (re-search-backward "^[ \t]*\\([^ \t---\n]\\|-[^---]\\)" nil t))

(defun e-move-to-prev-non-blank ()
  "moves point to previous line excluding blank lines. 
Returns t if successful, nil if not."
  (beginning-of-line)
  (re-search-backward "^[ \t]*[^ \t\n]" nil t))

(defun e-comment-indent ()
  "return indentation for a comment line."
  (save-excursion
    (if (not (e-move-to-prev-non-blank)) ;move to prev line if there is one
	0				;early comments start to the left
      (cond ((e-is-key-end)		;line ends in is, indent twice
	     (+ eiffel-indent eiffel-indent (e-current-indentation)))
	    ((e-comment-line-p)		;is a comment, same indentation
	     (e-current-indentation))
	    (t				;otherwise indent once
	     (+ eiffel-indent (e-current-indentation)))))))

(defun e-in-comment-p ()
  "t if point is in a comment."
  (cond ((e-comment-on-line-p)
	 (let ((pt (current-column)))
	   (save-excursion
	     (e-goto-comment-beg)
	     (if (<= (current-column) pt)
		 t
	       nil))))
	(t
	 nil)))

(defun e-get-block-indent ()
  "Return the outer indentation of the current block. Returns -20 if it can't
find one."
  (let (indent (succeed t))
    (save-excursion
      (setq succeed (e-goto-block-head))
      (cond ((not succeed)
	     nil)
	    ((looking-at "is")		;heads ending in is have extra indent
	     (setq indent (+ (current-indentation) eiffel-indent)))
	    (t
	     (setq indent (current-indentation)))))
    (if succeed
	indent
      -20)))				;will put at first col if lost

(defun e-prev-char-is-underscore ()
  "true if previous character is an underscore. (common bug with keywords)"
  (save-excursion
    (backward-char 1)
    (looking-at "_")))

(defun e-goto-block-head ()
  "move point to the is, then, loop, class, debug, or check that would
be paired with an end at point. Return nil if none."
  (let ((depth 1))
    (while 
	(and (> depth 0)
	     (re-search-backward
	      "\\<\\(class\\|deferred[ \t]+class\\|if\\|from\\|\
check\\|is\\|debug\\|end\\)[--- \t;\n]" nil t))
      (cond ((or (e-in-comment-p)	;if keyword in comment
		 (e-prev-char-is-underscore)) ;not really a keyword
	     nil)			;ignore it
	    ((looking-at "end")		;end of block
	     (setq depth (1+ depth)))
	    (t				;head of block
	     (setq depth (1- depth)))))
    (if (> depth 0)			;check whether we hit top of file
	nil
      t)))
-- 

bertrand@eiffel.UUCP (Bertrand Meyer) (10/07/89)

Thanks for Stephen M. Omohundro for posting his Eiffel mode for GNU Emacs.
Vince Kraemer from Interactive has also developed one independently,
but did not feel ready to post it, if only because it was still 2.1
only.

He is planning to post a revised version, resulting from a merge with
Mr. Omohundro's posting. The target date is ``right after Halloween''.
(This means November 1st, and is not intended to scare anyone.)

-- Bertrand Meyer
bertrand@eiffel.com