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