-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revamp loop indentation #642
base: master
Are you sure you want to change the base?
Conversation
* test/sly-cl-indent-test.txt: Add a test for the indentation of the else and end clauses in (nested) conditional clauses in loop.
* lib/sly-cl-indent.el (sly--lisp-indent-loop-macro-1): Indent else and end clauses appropriately recognizing nesting. (This relies on the conditional line starting with the conditional keyword, so an `and if` construct will not indent properly.)
Thanks for this work. But this is too sensitive a part of SLY to touch, IMO.
You cannot also just assume that the tests expectations are wrong just because you disagree with them. The many tests in You could however provide some kind of As to the implementation I can't comment. Replacing a regexp-based indenter with a sexp-based one sounds like a great idea, but the changes are too large for me to judge. I see big complicated code chunks being replaced by other big complicated code chunks. I've never been comfortable with this code, basically just inherited it from SLIME, and then Zach Shaftel did some tweaks. You may want to contact him. Also, you should evaluate how many Emacs versions back this can work with. Emacs 25? 26? |
This is valuable advice that I will try to keep in mind as much as possible. In this situation specifically, however, the problem is due to
This is an acceptable compromise, possibly, but it introduces the problem of having to either implement non-greedy or negative matching. Non-greedy, as is, is a non-starter. I can't even imagine how I would go about it. Negative is a band-aid but I think it should be fairly simple, though ugly.
This is probably the biggest concern, and why the PR is marked as a draft. Ideally the indenting algorithm should be theoretically sound enough that all that needs to be audited is the implementation's correctness with respect to that. This is unfortunately not yet the case.
Off the top of my head: The limit, as is, is 29 due to the use of |
[ Just shooting from the side-lines, I'm not at all familiar with
that code. I'm also unfamiliar with its relationship to the
`cl-indent.el` that ships with Emacs. ]
> You could however provide some kind of
> `sly-thuna-cing-correct-loop-indentation` variable that switches on
> "spec-correct" indentation the way you see it, but it would be off
> by default.
This is an acceptable compromise, possibly, but it introduces the problem of
having to either implement non-greedy or negative matching.
Hmm... I'd have expected all it took was to keep the old code (in
addition to the new code) and add some `if`s to select between the two.
I guess you're thinking about mimicking the old/current indentation
style using the new code?
> As to the implementation I can't comment. Replacing a regexp-based
> indenter with a sexp-based one sounds like a great idea, but the changes
> are too large for me to judge. I see big complicated code chunks being
> replaced by other big complicated code chunks.
This is probably the biggest concern, and why the PR is marked as a draft.
I know the idea of throwing away code you just wrote is probably not
welcome, but I suspect indentation of `loop` should be able to use SMIE.
I have played with using SMIE to indent ELisp code, and to some extent
it kinda works, tho it would require changes in `smie.el` to support for
"hooks" to provide specific indentation rules for specific
special forms. And doing that would inevitably behave slightly
differently in some cases, while at the same time being most likely
slower than the current code. So I never pursued this idea in earnest.
But in your case, you could use the existing indentation code for most
things and use SMIE only when indenting `loop` (i.e. use
a `common-lisp-indent-function` which internally uses SMIE). I have
never tried such a thing, so I'm not sure how well it would work, but
it might be worth a try.
|
Considering how little people there are that are familiar with the code, I suspect that makes most people, including me until I looked into it for this (and even then!). I welcome all help.
AFAICT, this code doesn't have any relationship to cl-indent.el. A quick look at
I could check whether we are indenting a
I can part with the code I have written so far, though it would be a bit of a shame. I looked into SMIE for a bit but there are a couple problems, both due to my lack of experience and to its slight shortcomings. I can't see how you represent something like |
I don't follow this at all. Are you saying indentation matters for evaluation or macroexpansion?? Can you give an example?
I think 26 is a good target
I think it does. It's a common ancestor to both SLY and SLIME's |
Oof, sorry, I misspoke. What I meant is that while the loop's indentation would lead the reader to assume that the non-compound forms following |
> I'm also unfamiliar with its relationship to the `cl-indent.el` that ships with Emacs.
AFAICT, this code doesn't have any relationship to cl-indent.el. A quick
look at `common-lisp-loop-part-indentation`, which is a mechanism separate
from the rest of cl-indent.el, specifically for loop, reveals that it just
looks at the line that is being indented and indents relative to the loop
body, ignoring all surrounding context. All compound forms are offset, and
everything else is flush.
So IIUC Emacs's `cl-indent.el` is not used at all with SLY?
> Hmm... I'd have expected all it took was to keep the old code (in addition
> to the new code) and add some `if`s to select between the two. I guess
> you're thinking about mimicking the old/current indentation style using
> the new code?
I could check whether we are indenting a `DO` form and delegate the
indentation to the old code.
I can't see why. The `if`s I'm taking about would only need to check
the boolean configuration variable saying whether we should use the old
code or the new code.
I can't see how you represent something like `do conditional-clause+`
or `when sexp clause` in it. Maybe there is a trick that
I am missing.
Indeed you can't just use an existing grammar to write an SMIE grammar.
I expect the grammar would deal with `do` and `when` in a more "naive"
way as in:
(clause ...
("when" clause)
("do" clause)
...)
or probably even
(clause ...
(clause "when" clause)
(clause "do" clause)
...)
The way I have built SMIE grammars in the past was based more on adding
ad-hoc elements bit by bit to make it behave in a particular way on
specific tests. Using an existing grammar was usually more frustrating
than anything (except for things like specifying relative precedences
based on existing precedence tables).
|
That is my understanding, yes. Historical relations notwithstanding, the code I am replacing and the code in cl-indent.el are not the same.
Ah, I see, so you are proposing that the new indenter be opt-in? That would work, yes. It could depend on a buffer-local variable like
I was hoping that that wouldn't be the case :/. My goal with this is correctness above all - that the indentation reflects the actual state of the |
I was hoping that that wouldn't be the case :/. My goal with this is
correctness above all - that the indentation reflects the actual state of
the `LOOP` form at (almost) all times - so something this naive and loose
isn't a viable solution unfortunately.
I misunderstood then, sorry.
If you want "correctness above all", SMIE is definitely not your friend.
|
The goal of this PR is to indent
CL:LOOP
forms by parsing the body instead of using regexps on the surrounding context.This is done in two steps:
sly--lisp-indent-loop-macro-partial-parse
takes the currentLOOP
body and parses it, collecting each form in it as a cons cell(POS . OBJ)
wherePOS
is where the object starts andOBJ
is the object's string representation as it is written in the buffer if it is an atom and nil if it is a compound form.sly--lisp-indent-loop-macro-1
was completely rewritten such that it uses aconsume-forms
sub-function in acl-labels
which recursively moves along this((POS . OBJ)...)
that we obtained before. It does this according to the spec given to it, the documentation of which is provided in the new variablesly--lisp-indent-loop-macro-allowed-bodies-alist
, which also contains the specs of all clauses in aLOOP
body. The goal was to make it be as one-to-one with the spec as it could be.Some explanation of how
consume-forms
works: It receives four things: the current((POS . OBJ)...)
list (anything that came before we "moved across"), the spec that it is expecting, the column the current body begins at, and the offset relative to it. If it manages to parse until we reach the end of the forms, we return the current body plus the offset relative to it. If it parses it, finishes, and we still have left over forms, then it instead returns those forms. The sequence specs are such that the next subspec will be matched against these leftover forms instead. The entry point, the firstconsume-forms
that is called is one that will match against clauses indefinitely.consume-forms
may also returnnil
if it fails to match. (This is because the list((POS . OBJ)...)
is actually made to be terminated by at
and notnil
.) All of these should guarantee that we indent the target line appropriately.Now, a couple (or more) problems with the current state of this code:
consume-forms
into its own function will help with that, however some optimizations may need to be done even then.sly--lisp-indent-loop-macro-allowed-bodies-alist
is... fine, actually. It is by no means perfect, and I had to make some compromises in order to avoid having to bookkeep a bunch of stuff, but it is for the most part understandable. Some work on it may be done, if better ways to shape it are found, but it is decent.sly--lisp-indent-loop-macro-partial-parse
uses naive-ish code to determine what is and isn't a compound form, taking into account the standard reader macros. Some tests should be done to ensure that these work. Some of the more exotic cases, in case too difficult to handle, I think may be ignored, but as long as this code works then so shouldsly--lisp-indent-loop-macro-1
, as far as I can tell.TODOs:
sly-lisp-loop-body-forms-indentation
should be made to be recognized in the spec, likely by changing the existing 2-indents by&body
-indents. This brings up the issue of what the current 4-indents should be indented as, however. Possibly as a&special
indent which defaults to double the&body
-indent. There are also 0-indents, and while these can stay as hard-coded to 0, maybe a&join
-indent could be introduced which would default to 0 instead. I don't know at this point if this is good or not, but some sort of a decision needs to be made.sly-lisp-loop-indent-body-forms-relative-to-loop-start
should... not exist. I have no idea why it even exists, and I have no appreciation whatsoever for it. I vote for simply removing it altogether. The implementation, if I have to allow for its existance, and in fact, the whole conceptual basis for the code is made void by this variable. I have no intentions to add support for it.do
s bodies are compound forms. This makes implementation possible and establishes consistency with the spec. The testindent-11
is also invalid, due to thewhen here's something I used to botch
line. If some other invalid examples exist, they should either be fixed or explicitly be made examples of invalid loops. I can go through them, but then we move on to the next point:sly--lisp-indent-loop-macro-partial-parse
only parses up to the form to be indented. This has two benefits:LOOP
but a different sexp instead. This seems to work so far but a more rigorous check is needed at some point.consume-forms
or not: If we are at the end of the list of forms then we have figured out the indentation.This has a single problem however: The form
and
-chained clauses it should beif
as the starting point of the body - an inline body - however this is not consistent if we say thatelse if
indents relative toelse
which would imply thatif
actually starts a not-inline body. This could possibly be reserved if we were to say thatand
s subclauses must start inline bodies but currently only the body itself can signal whether it is inline or not, not the thing starting it, so if this is indeed considered the "correct" indentation then this would take additional work to fix, though it would not be impossible. However, I suggest that we simply sayand <clause>
is indented relative toand
.indent-69
,indent-70
, andindent-71
, body comments are aligned against each other. I cannot see a reason why this would be the case; the only comments that align against each other are supposed to be single comments. The correct indentation should instead have the second body comment align against where the body would go.There is however a problem, in that a comment is currently treated the same as a compound form and moves along the spec, which is something that I need to fix at some point. It should not be too difficult hopefully.
These are all the things I can immediately think of, there may be and likely are many more issues and things that need to be ironed out. I did not want to end up going too out of scope in #471 so I have this as a PR, but it is actually a draft.