-
Notifications
You must be signed in to change notification settings - Fork 350
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
feat: intra-line withPosition formatting #1872
Conversation
057c9cf
to
2355562
Compare
2355562
to
242d3fc
Compare
Mmh, this does look a bit complicated for a single tactic. Don't we have basically all the tools to emulate
It looks like the |
I had hoped that we could use this combinator in a lot of cases where we have to resort to [let x := 42
id x] The above doesn't round-trip atm. Top-level structure instances would also look a lot nicer, right now they're printed like this: {
a := 42
b := 8 } |
I've wanted such an |
I hate top-level parentheses 😄 . It's so awkward that they introduce non-standard single-character indentation. But it's not like I have a better formatting in mind. The hanging initial token looks like the core issue to me. If we only had top-level parentheses, I assume we could simply indent by 1 for them. Thus why I'm saying we shouldn't need any new tools for cdot, which never hangs. I'm also concerned about Mario's last example, I can't tell yet whether this |
I should also mention that I'm working on integrating Lars' refactored |
The issue is that you have to think about how many characters there will be in the previous token, as well as what the indent size is, to implement an equivalent of |
I forgot to mention: there is a simple fix for that issue with this PR, namely to remove buggy newline reasoning and just always add an align at the start of a try
tac will always be line-broken like this, as well as other one-line tactic combinators like |
That's great if you primarily care about correct output, and maybe we should have it just for that, but evidently it is not sufficient to get good output: https://github.com/leanprover/lean4/pull/1872/files#diff-6bde8d5882f415e03ae56f70a39d8d0bed774302649baa5934c1962f024fdd54R40. My current thinking is:
and
where we really want to "right-append" a block to a token, we need to indent by the token(+whitespace) length (which we can have a helper combinator do), which should be sufficient as long as we're not in a hanging context. And I really don't ever want to see something like
If
everywhere where a single line is not sufficient, even in non-hanging contexts. Like with trailing commas in other contexts, it has the great advantage that the line of the first and last field is not special. |
The indentation option is far older than any plans to use Format for actual code formatting. But I agree that we may as well remove it to make our lives easier.
Why is that, is the option getting lost somewhere? |
This PR has some pretty ugly regressions on mathport: -def lsmul : A →ₐ[R] Module.EndCat R M where
+def lsmul :
+ A →ₐ[R] Module.EndCat R
+ M where (Also note the trailing space on the last line now.) |
The trailing I'm not sure what causes the crazy indentation in your example though; everything should be well below the line wrap threshold. What's the generated |
The trailing spaces are because I think it would be best to make align behave more like the whitespace combinators. It always resets the lead word, and always results in some kind of whitespace (separate line or space). |
Although I think the no-space version of align is useful for some things, it's not important for our immediate goals so I'm fine with replacing it with a version that always uses at least one space for now. Testing the change is going to be really annoying though. Do you have an idea for how to turn that example into a MWE that can go in |
Here you go: #eval fmt `(command|
def foo : a b c d e f g a b c d where
h := 42
i := 42
j := 42
k := 42) |
Now that people are starting to use mathport more heavily, I think it is more important that we get the formatting right so that people don't blindly copy current formatting mistakes as though they are recommended practice.
This PR is primarily aimed at fixing the formatting of the
· tac
combinator that is ubiquitous in mathported proofs.· tac
. So for example you would get:by · skip done · · skip done · done
·
parser usescdotTk many1Indent(tactic ";"? ppLine)
, and here we can see a second issue: since it's not usingsepBy1IndentSemicolon
we get the same issues as used to be happening inby
, e.g.· skip skip
works, and the tactics don't have to be strictly aligned resulting in weird parses.If we just switch out the
many1Indent
forsepBy1IndentSemicolon
we get some improvements and some regressions:by · skip done · · skip done · done
The dot now uses the same "hanging" indentation style as
by
,try
and all the other combinators. But we'd really like it to fit on the same line if possible.After investigating how to do this in
Formatter
for a while I came to the conclusion that it would be very hackish and special cased to the·
tactic, when what we really want is a way to getwithPosition
-safe line wrapping. So instead, this implements it inside theFormat
algorithm itself.Format
constructor,Format.align (force : Bool)
. The semantics are that if the current position is less than the indentation level (such as would happen immediately after anest 2
),align
will pad with spaces until the column matches the indent. If the current column is greater than the indent, then it adds a newline and indents as normal.force
flag is disabled,align
becomesnil
when the enclosing group is flattened; otherwise it always pads or line-breaks to the indent. (Currently onlyforce := true
is being used, see below.)sepByIndent
combinator used to conservatively put aFormat.line
before the list when there is at least one newline in the list (that's the spurious newlines we are seeing above) to prevent it from breakingwithPosition
parsing. Now it can use.align true
instead.The results look pretty good to me, see the
formatTerm
test. Because thealign
is used insepByIndent
the same behavior is inherited by other parsers as well. It doesn't make too much difference with lean's default 2-space indent but if you increase the indent to 4 then you will see things likeby
,try
,do
also getting the same nesting behavior when they appear at the start of a line.I had to remove the
HACK
which forcibly setssepByIndent
to ungrouped, because this causes there to be no indent on the last line, as in:(which still parses correctly, but is clearly not desirable). This was being done in order to allow structure
{
to hang on the previous line, as a way of recovering from the forced newline that used to be there. Now the{
is at the beginning of the line, which also looks fine and matches with the}
that hangs at the end of the last line. (I think we have the tools to do hanging{
properly now if we need to.)One issue that existed before and has not been fixed in this PR is that the check for whether to insert a newline, now an
align
, at the start ofsepByIndent
is imprecise: it looks for the existence of forced newlines in the sepBy list, e.g.skip\n skip
instead ofskip; skip
. The problem with this is that even if every tactic has a;
after it, if the sequence is long you can get line wrapping caused by theFormat
because it is using thefill
combinator. After the line wrapping, you might get a tactic at the indent line even though we did not use analign
at the start, as in:The
ohno
tactic is to the left of thepos
tactic, so it is either a parse error or will be parsed as part of the surrounding block. It is not possible to fix this in theFormatter
though because it doesn't know whether theFormat
will be line broken or not. We would either need some moreFormat
primitives or a general mechanism that I have seen in some implementations of the Wadler-Leijen pretty printer to allowchoice (useIfFlattened useIfNormal : Format)
nodes.