[comp.emacs] Lisp indent question

HI.OLEARY@MCC.COM (Michael O'Leary) (03/17/90)

I am in the process of assigning values to the lisp-indent-hook
property on several lisp function names to get the indentation the way
I want it.  I am wondering if there is a way that I can use the
lisp-indent-hook property (or some other means) to get the indentation
for internal functions defined by LABELS, FLET, etc., to come out like
this:

(labels ((foo-internal (x y)
           (manipulate-args x y)))
   ...)

instead of like this:

(labels ((foo-internal (x y)
                       (manipulate-args x y)))
   ...)

In particular, I am wondering about the case in which the value of the
lisp-indent-hook property is a symbol that is the name of a function
that takes a state and a pos argument (see p. 138 of Gnu Emacs
Manual).  So I have two specific questions:
1) Can I write an indenting function and hang its name on the
   lisp-indent-hook property of functions such as labels and have it
   indent the lines *within* its first argument?  (and if
   lisp-indent-hook is not intended for this purpose, is there some
   other way to do it?)
2) The values passed in to these indenting functions for STATE and POS
   appear to be a seven element list and an integer, respectively.
   The values I got while playing around were
   STATE = (2 3089 3097 nil nil nil 0) and POS = 3106.
   What do these values mean, and how can I use them to indent
   functions (and are there Gnu-Emacs functions for accessing and
   manipulating them)?

	Michael O'leary
	oleary@mcc.com
-------

Duchier-Denys@cs.yale.edu (Denys Duchier) (03/18/90)

In article <12574253766.61.HI.OLEARY@MCC.COM>, HI.OLEARY@MCC (Michael O'Leary) writes:
> [...]  I am wondering if there is a way that I can use the
> lisp-indent-hook property (or some other means) to get the indentation
> for internal functions defined by LABELS, FLET, etc., to come out like
> this:
> 
> (labels ((foo-internal (x y)
>            (manipulate-args x y)))
>    ...)
> 
> instead of like this:
> 
> (labels ((foo-internal (x y)
>                        (manipulate-args x y)))
>    ...)
> 

Years ago, I decided to do the very same thing. I wrote a package to
do really smart automatic indenting of lisp code in elisp. At the
time, however, my machine was not fast enough and the package consed
too much to be practical (delays can be a serious nuisance when you
are intent on your work). So I canned it and tried a different
approach.

I came to the conclusion that GNU emacs was lacking the necessary
features to support smart indenting. Fortunately, it is very easy to
add those features to the C code.

In order to do smart indenting, one needs fast access to the syntactic
context. For instance, in your example, you need to be able to quickly
determine that "(manipulate-args x y)" is a part of the body of a
function locally defined by the labels construct.

In lisp, the syntactic context is entirely determined by the
parentheses and occasionaly by the symbols immediately following left
parentheses. Since parse-partial-sexp must parse the defun anyway, it
would be a great convenience if it also kept track of the open parens
down to the point to be indented and made this information available
to the user.

I extended parse_partial_sexp/scan_sexps_forward to update a vector of
positions (actually, I use the new function ctx-parse-partial-sexp
which, unlike the original parse-partial-sexp, was designed to avoid
consing).

After running ctx-parse-partial-sexp, the user may meaningfully invoke
open-paren:

(open-paren n) 
	evaluates to the position of the open paren n levels up from
	the ending point of the scan. Thus, in your example:

	(open-paren 0) => position of paren before "foo-internal"
	(open-paren 1) => position of paren beginning list of local
			  functions.
	(open-paren 2) => position of paren before "labels"

Next, it is desirable to be able to determine what symbol if any
immediately follows a given open paren. For instance, it is necessary
to determine that the symbol that follows (open-paren 2) is "labels".

(word-at pos &optional offset default)
	returns the symbol that can be read at position (pos +
	offset), but only if that symbol has already been interned. If
	no symbol can be read at the specified position or it is not
	yet interned, the default is returned. offset defaults to 0.
	This function must try very hard not to cons (for instance, it
	may try to read the symbol into an internal buffer rather than
	allocate a new string every time).

Finally, it is also convenient to be able to tell the rank of the
expression, beginning with a given open paren, in the immediately
surrounding expression. For instance, the argument list occurs as the
second element in a local function definition (the first being the
name, and all the others being in the body). parse-partial-sexp was
also extended to keep track of the ranks. This information is made
available to the user through the function "rank-at-level".

(rank-at-level n)
	returns the rank of the enclosing sexp n levels up within the
	immediately enclosing sexp at level n+1. Thus, in your example:

	(rank-at-level 0) => 2 because "(manipulate-args x y)" is the
		2nd expression (0-based) in the function definition.

	(rank-at-level 1) => 0 because this is the first local
		definition.

	(rank-at-level 2) => 1 because the list of local function
		definitions is the 1st element (0-based) of the labels
		construct.

Using this programming interface I wrote a lisp indenting package in
elisp (the main loop is written in C, but I think that was overkill).
It can automatically indent code as shown below:

(defun foo (arg1 arg2 arg3
            arg4 arg5 ...)                ;indent a lambda list
  (labels
      ((local1 (x1 x2
                x3 x4)                    ;indent a lambda list
         (local2 x1 x2 x3 x4
                 arg1 arg2))              ;indent an argument list
       (local2
           (y1 y2 y3
            y4 y5)
         body2...))
    body of foo...))

The idea is this:

	1. run ctx-parse-partial-sexp from the beginning of the defun
	   in order to set up and make available contextual
	   information.

	2. run each hook in LISP-indent-hooks until one of them
	   returns non nil.

	3. if non nil is returned, the value must be an integer
	   denoting the column to indent to.

	4. if all hooks return nil, try to indent according to
	   (word-at (1+ (open-paren 0))), else compute default
	   indentation.

Each hook in LISP-indent-hooks is a function which must check the
context to determine whether it is applicable. For instance, one such
hook might check to see if the indent point is in a local function
definition:

	(and (eq (word-at (1+ (open-paren 2))) 'labels)
	     (= (rank-at-level 2) 1))

My package was written so that several lisp indenting modes could
coexist: scheme has different indenting requirements from common lisp.
It was also written so that one lisp indenting package could inherit
from another. For instance, I wrote a Nisp indenting package (Nisp is
a macro package which among other things adds type checking to common
lisp and allows type declarations in lambda lists and in binding
constructs) which inherits from my Common Lisp indenting package.

Here is the hook for indenting local function definitions in common
lisp:

(defun CL-point-in-local-fundef ()
  (and (type-sexp-p CL-labels-properties (open-paren 2))
       (= (rank-at-level 2) 1)
       (if (= (rank-at-level 0) 1) (special-indent) (body-indent))))

and here is the function which indents lambda lists:

(defun CL-point-in-arglist ()
  (and (or (CL-point-in-lambda-arglist-p)
	   (CL-point-in-local-fun-arglist-p)
	   (CL-point-in-defun-arglist-p))
       (noindent)))

The code was also written so that different constructs could share the
same indenting behavior. All behaviors are determined by properties.
This is what (type-sexp-p CL-labels-properties (open-paren 2)) is
about, in the definition of hook CL-point-in-local-fundef. It first
picks up (word-at (1+ (open-paren 2))) and if that is a (non nil)
symbol, returns t iff the symbol has one of the properties in
CL-labels-properties.

Because of the use of list of properties, it is possible for one
package to inherit some properties from another package and overide
other properties with new ones. This makes the design very flexible
(perhaps unnecessarily so).

I am quite willing to make my code available to the FSF. I have been
using it for years, so it can be considered safe and thoroughly
tested. It could probably use some cleaning up.

--Denys