[comp.emacs] Indenting Labels

harry@binky.UUCP (Harry Weeks) (10/05/87)

There have been a few queries recently for enhancements to LISP mode
that nicely indent LABELS and other complex constructions.  I have
an enhanced LISP mode that provides a generalized indenter for complex
forms, hooks for indenting semicolon comments, and handling of nested
comments and escaped symbol names.  Since the file is large, I am
providing a brief sketch of how the enhancements work, and anyone who
is interested can write to me directly for the file.

					Harry Weeks
					Franz Inc.
					harry%franz.uucp@berkeley.edu
--------
(defconst lisp-electric-semicolon nil
  "*If `t', semicolons that begin comments are indented as they are typed.")

(defconst lisp-comment-indent-specification (list comment-column t nil 0)
  "*Specification list for indentations of semicolon comments.
The nth element of the list specifies the indentation for a comment beginning
with n semicolons (e.g. the first element of the list is the indentation for
comments beginning with one semicolon).  Each element of the list may be one
of `t' (indent comment just like an s-expression), `nil' (don't change the
indentation of the comment, i.e. leave the semicolon where it is), a non-
negative integer (specifying the absolute column to which the comment is to
be indented), or a negative integer (specifying a negative offset for the
comment relative to the current column).")

(defvar lisp-tag-indentation 1
  "*Indentation of tags relative to containing list.
This variable is used by the function `lisp-indent-tagbody' to indent tags
that occur within special forms whose symbols have a 'lisp-indent-hook
property of 'tag, 'tagbody, or 'keyword.  The indentation is relative to
the indentation of the parenthesis enclosing the special form.")
 
(defvar lisp-tag-body-indentation 2
  "*Indentation of non-tagged lines relative to containing list.
This variable is used by the function `lisp-indent-tagbody' to indent normal
lines (lines without tags) that occur within special forms whose symbols have
a 'lisp-indent-hook property of 'tag, 'tagbody, or 'keyword.  The indentation
is relative to the indentation of the parenthesis enclosing the special form.
If the value is T, the body of tags will be indented as a block at the same
indentation as the first s-expression following the tag.  In this case, the
s-expressions before the first tag are indented as an undistinguished form.")
 
(defvar lisp-tag-indentation-hook nil
  "*Name of function to apply to return indentation of tag.
This variable may be bound to the name of a function to be applied (to
three arguments: the character position of the beginning of the tag,
the last parse state, and the indent point) to return the appropriate
indentation for tags occurring within special forms whose symbols have
a 'lisp-indent-hook property of 'tag, 'tagbody, or 'keyword.  The inden-
tation returned is absolute.")
 
(defvar lisp-maximum-indent-struct-depth nil
  "*Maximum depth to backtrack out from a sublist for structured indentation.
If this variable is NIL, no backtracking will occur and lists whose `car'
is a symbol with a 'lisp-indent-hook property of 'label, 'labels, 'flet,
'macrolet, 'defun, or a list may not be indented properly.  In addition,
quoted lists will not be treated specially.  If this variable is T, there
is no limit placed on backtracking.  A numeric value specifies the maximum
depth to backtrack.")

(defun lisp-indent-labels (depth count state indent-point)
  "Function to indent LABELS and related special forms.
This function indents forms that have a 'lisp-indent-hook property equal to
'label,'labels, 'flet, or 'macrolet.  It is equivalent to specifying a value
of '((2 1 def) (0 t 1)) for the 'lisp-indent-hook property and is provided
as a shorthand notation."
  . . . )

(defun lisp-indent-defun (depth count state indent-point)
  "Function to indent DEFUN and related special forms.
This function indents forms that have a 'lisp-indent-hook property equal to
'defun.  It is equivalent to specifying a value of '((1 2 quote) (0 t def))
for the 'lisp-indent-hook property and is provided as a shorthand notation."
  . . . )

(defun lisp-indent-struct (methods depth count state indent-point)
  "Function to indent Lisp special forms that have substructure.
The METHODS is a list of triples, (Depth Count Method), where Depth and
Count specify when special treatment is required of a sublist in a form,
and Method is the method of indentation to apply to such a sublist.  If
Depth or Count is t or nil, Method is used for any Depth or Count.  The
Method will be applied to the form itself if the Depth is zero.  The Method
is interpreted identically to the 'lisp-indent-hook property of symbols and
thus may be recursive (that is, itself a list of triples).  If the Method
is recursive, it is applied if DEPTH is greater or equal to Depth; this
implies that triples should be ordered greatest depth first in lists.  In
recursive applications of this function to a Method, DEPTH is set to the
depth of the current sublist relative to sublist associated with Method.
The METHODS list should be the value of the 'lisp-indent-hook property of
a symbol that is a Lisp special form having substructure.  The DEPTH is
the depth of the current sublist relative to the form.  The COUNT argument
is the number of the current s-expression in the form.

As an example, giving LABELS the following property

  (put 'labels 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1)))

will have the effect that s-expressions of the LABELS form itself are
indented with 1 distinguished form using `lisp-indent-specform' (this is
specified by '(0 t 1)).  In addition, all sublists of the first s-expression,
which is a list, of the LABELS special form (denoted by '(2 1 ...)) will
be treated just like a LAMBDA (whose method is '((1 1 quote) (0 t 1)))."
  . . . )

(defun lisp-indent-tagbody (state indent-point
			    &optional spec-count
			    &rest keywords)
  "Function for indenting TAGBODY and related forms.
This function indents special forms that have `tags' or `keywords' that
should be treated specially.  For example, TAGBODY forms have `tags' for
GOTO, and IF* forms have `keywords' THEN, ELSE, etc.  An optional argument
SPEC-COUNT is accepted, specifying the number of distinguished s-expressions
in the form.  Any further arguments constitute the `keywords' or `tags'
that are to be recognized.  In the absence of an explicit list, any atomic
expression is considered a keyword."
  . . . )

(defun lisp-indent-if (state indent-point &optional spec-count)
  "Function for indenting IF special forms.
This function is dispatched to indent a form whose `car' is a symbol with a
`lisp-indent-hook' property of `if'.  This provides a shorthand for a property
of '(lisp-indent-tagbody 2 \"then\" \"thenret\" \"else\" \"elseif\")."
  . . . )

;; Examples.
(put 'assert 'lisp-indent-hook '((1 2 quote) (0 t 2)))
(put 'block 'lisp-indent-hook 1)
(put 'compiler-let 'lisp-indent-hook '((1 1 quote) (0 t 1)))
(put 'defflavor 'lisp-indent-hook '((1 2 quote) (1 3 quote) (0 t 3)))
(put 'define-modify-macro 'lisp-indent-hook '((1 2 quote) (0 t 2)))
(put 'defsetf 'lisp-indent-hook '((1 2 quote) (0 t 3)))
(put 'defstruct 'lisp-indent-hook '((1 1 quote) (0 t 1)))
(put 'do 'lisp-indent-hook '((1 1 1) (1 2 1) (0 t (lisp-indent-tagbody 2))))
(put 'flet 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1)))
(put 'if 'lisp-indent-hook 'if)
(put 'labels 'lisp-indent-hook '((2 1 ((1 1 quote) (0 t 1))) (0 t 1)))
(put 'lambda 'lisp-indent-hook '((1 1 quote) (0 t 1)))
(put 'let 'lisp-indent-hook '((1 1 quote) (0 t 1)))
(put 'loop 'lisp-indent-hook 'tagbody)
(put 'multiple-value-bind 'lisp-indent-hook '((1 1 quote) (0 t 2)))

tsf@PROOF.ERGO.CS.CMU.EDU (Timothy Freeman) (05/17/88)

Here's code to indent Common Lisp's labels construct properly.  It's a
bit slow.  The problem it solves is that a labels construct looks
better when indented like this:

   (labels ((this-is-a-very-long-function-name ()
              stuff)))

than when indented like this:

   (labels ((this-is-a-very-long-function-name ()
                                               stuff)))

I hereby release this into the public domain.

(defun containing-sexp (indent-point)
  (let (state (retry t) last-sexp containing-sexp)
    (save-excursion
      (beginning-of-defun)
      ;; Find outermost containing sexp
      (while (< (point) indent-point)
	(setq state (parse-partial-sexp (point) indent-point 0)))
      ;; Now state is the state of the parser starting at the
      ;; outermost containing sexp for indent-point and ending at indent-point.
      ;; This loop has no side effects on indent-point.
      (while (and retry (car state) (> (car state) 0))
	(setq retry nil)
	(setq last-sexp (nth 2 state))
	(setq containing-sexp (car (cdr state)))
	;; Position following last unclosed open.
	(goto-char (1+ containing-sexp))
	;; Is there a complete sexp since then?
	(if (and last-sexp (> last-sexp (point)))
	    ;; Yes, but is there a containing sexp after that?
	    (let ((peek (parse-partial-sexp last-sexp indent-point 0)))
	      (if (setq retry (car (cdr peek))) (setq state peek)))))
      ;; Now:
      ;;    last-sexp is the start of the last sexp finished before point,
      ;;       nil if none.
      ;;    containing-sexp is the start of the innermost sexp
      ;;       containing indent-point, nil if none.
      ;;    state is the state of the parse from last-sexp to
      ;;       indent-point.  If last-sexp is nil, then state is
      ;;       vacuous.
      ;;    retry is t if indent-point is the first sexp in its
      ;;       containing sexp.
      containing-sexp)))

(defun in-labels-p (place)
  "Returns t if we are in the place where we need to indent specially
for labels."
  (save-excursion
    (let ((cont1 (containing-sexp place)))
      (and cont1
	   (let ((cont2 (containing-sexp cont1)))
	     (and cont2
		  (progn
		    (goto-char cont2)
		    (looking-at "(("))
		  (let ((cont3 (containing-sexp cont2)))
		    (goto-char cont3)
		    (or (looking-at "(labels")
			(looking-at "(flet")))))))))

(defun new-lisp-indent-hook (indent-point state)
  (if (in-labels-p indent-point)
      (+ lisp-body-indent
	 (save-excursion
	   (goto-char (car (cdr state)))
	   (current-column)))
      (lisp-indent-hook indent-point state)))

(setq lisp-indent-hook 'new-lisp-indent-hook)
-- 
Tim Freeman

Arpanet: tsf@theory.cs.cmu.edu
Uucp:    ...!seismo.css.gov!proof.ergo.cs.cmu.edu!tsf