Skip to content
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

Merged
merged 1 commit into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 55 additions & 17 deletions src/Init/Data/Format/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ inductive Format where
/-- A position where a newline may be inserted
if the current group does not fit within the allotted column width. -/
| line : Format
/-- `align` tells the formatter to pad with spaces to the current indent,
or else add a newline if we are already past the indent. For example:
```
nest 2 <| "." ++ align ++ "a" ++ line ++ "b"
```
results in:
```
. a
b
```
If `force` is true, then it will pad to the indent even if it is in a flattened group.
-/
| align (force : Bool) : Format
/-- A node containing a plain string. -/
| text : String → Format
/-- `nest n f` tells the formatter that `f` is nested inside something with length `n`
Expand Down Expand Up @@ -70,6 +83,7 @@ namespace Format
def isEmpty : Format → Bool
| nil => true
| line => false
| align _ => true
| text msg => msg == ""
| nest _ f => f.isEmpty
| append f₁ f₂ => f₁.isEmpty && f₂.isEmpty
Expand Down Expand Up @@ -103,17 +117,23 @@ private structure SpaceResult where
let r₂ := r₂ (w - r₁.space);
{ r₂ with space := r₁.space + r₂.space }

private def spaceUptoLine : Format → Bool → Nat → SpaceResult
| nil, _, _ => {}
| line, flatten, _ => if flatten then { space := 1 } else { foundLine := true }
| text s, flatten, _ =>
let p := s.posOf '\n';
let off := s.offsetOfPos p;
private def spaceUptoLine : Format → Bool → Int → Nat → SpaceResult
| nil, _, _, _ => {}
| line, flatten, _, _ => if flatten then { space := 1 } else { foundLine := true }
| align force, flatten, m, w =>
if flatten && !force then {}
else if w ≤ m then
{ space := (m - w).toNat }
else
{ foundLine := true }
| text s, flatten, _, _ =>
let p := s.posOf '\n'
let off := s.offsetOfPos p
{ foundLine := p != s.endPos, foundFlattenedHardLine := flatten && p != s.endPos, space := off }
| append f₁ f₂, flatten, w => merge w (spaceUptoLine f₁ flatten w) (spaceUptoLine f₂ flatten)
| nest _ f, flatten, w => spaceUptoLine f flatten w
| group f _, _, w => spaceUptoLine f true w
| tag _ f, flatten, w => spaceUptoLine f flatten w
| append f₁ f₂, flatten, m, w => merge w (spaceUptoLine f₁ flatten m w) (spaceUptoLine f₂ flatten m)
| nest n f, flatten, m, w => spaceUptoLine f flatten (m - n) w
| group f _, _, m, w => spaceUptoLine f true m w
| tag _ f, flatten, m, w => spaceUptoLine f flatten m w

private structure WorkItem where
f : Format
Expand All @@ -125,10 +145,13 @@ private structure WorkGroup where
flb : FlattenBehavior
items : List WorkItem

private partial def spaceUptoLine' : List WorkGroup → Nat → SpaceResult
| [], _ => {}
| { items := [], .. }::gs, w => spaceUptoLine' gs w
| g@{ items := i::is, .. }::gs, w => merge w (spaceUptoLine i.f g.flatten w) (spaceUptoLine' ({ g with items := is }::gs))
private partial def spaceUptoLine' : List WorkGroup → Nat → Nat → SpaceResult
| [], _, _ => {}
| { items := [], .. }::gs, col, w => spaceUptoLine' gs col w
| g@{ items := i::is, .. }::gs, col, w =>
merge w
(spaceUptoLine i.f g.flatten (w + col - i.indent) w)
(spaceUptoLine' ({ g with items := is }::gs) col)

/-- A monad in which we can pretty-print `Format` objects. -/
class MonadPrettyFormat (m : Type → Type) where
Expand All @@ -145,8 +168,8 @@ private def pushGroup (flb : FlattenBehavior) (items : List WorkItem) (gs : List
let k ← currColumn
-- Flatten group if it + the remainder (gs) fits in the remaining space. For `fill`, measure only up to the next (ungrouped) line break.
let g := { flatten := flb == FlattenBehavior.allOrNone, flb := flb, items := items : WorkGroup }
let r := spaceUptoLine' [g] (w-k)
let r' := merge (w-k) r (spaceUptoLine' gs);
let r := spaceUptoLine' [g] k (w-k)
let r' := merge (w-k) r (spaceUptoLine' gs k)
-- Prevent flattening if any item contains a hard line break, except within `fill` if it is ungrouped (=> unflattened)
return { g with flatten := !r.foundFlattenedHardLine && r'.space <= w-k }::gs

Expand Down Expand Up @@ -199,13 +222,28 @@ private partial def be (w : Nat) [Monad m] [MonadPrettyFormat m] : List WorkGrou
let gs'@(g'::_) ← pushGroup FlattenBehavior.fill is gs (w - " ".length)
| panic "unreachable"
if g'.flatten then
pushOutput " ";
pushOutput " "
endTags i.activeTags
be w gs' -- TODO: use `return`
else
breakHere
else
breakHere
| align force =>
if g.flatten && !force then
-- flatten (align false) = nil
endTags i.activeTags
be w (gs' is)
else
let k ← currColumn
if k ≤ i.indent then
pushOutput ("".pushn ' ' (i.indent - k).toNat)
endTags i.activeTags
be w (gs' is)
else
pushNewline i.indent.toNat
endTags i.activeTags
be w (gs' is)
| group f flb =>
if g.flatten then
-- flatten (group f) = flatten f
Expand Down
8 changes: 2 additions & 6 deletions src/Init/NotationExtra.lean
Original file line number Diff line number Diff line change
Expand Up @@ -339,13 +339,9 @@ section
open Lean.Parser.Tactic
syntax cdotTk := patternIgnore("·" <|> ".")
/-- `· tac` focuses on the main goal and tries to solve it using `tac`, or else fails. -/
syntax cdotTk ppHardSpace many1Indent(tactic ";"? ppLine) : tactic
syntax cdotTk ppSpace sepBy1IndentSemicolon(tactic) : tactic
macro_rules
| `(tactic| $cdot:cdotTk $[$tacs $[;%$sc]?]*) => do
let tacs ← tacs.zip sc |>.mapM fun
| (tac, none) => pure tac
| (tac, some sc) => `(tactic| ($tac; with_annotate_state $sc skip))
`(tactic| { with_annotate_state $cdot skip; $[$tacs]* })
| `(tactic| $cdot:cdotTk $tacs*) => `(tactic| {%$cdot $tacs* }%$cdot)
end

/--
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Data/Format.lean
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ open Std
export Std
(Format ToFormat Format.nest Format.nil Format.joinSep Format.line
Format.sbracket Format.bracket Format.group Format.tag Format.pretty
Format.fill Format.paren Format.join)
Format.fill Format.paren Format.join Format.align)
export ToFormat (format)

instance : ToFormat Name where
Expand Down
11 changes: 5 additions & 6 deletions src/Lean/Parser/Extra.lean
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,17 @@ open PrettyPrinter Syntax.MonadTraverser Formatter in
@[combinator_formatter sepByIndent]
def sepByIndent.formatter (p : Formatter) (_sep : String) (pSep : Formatter) : Formatter := do
let stx ← getCur
let hasNewlineSep := stx.getArgs.mapIdx (fun ⟨i, _⟩ n => i % 2 == 1 && n.matchesNull 0) |>.any id
let hasNewlineSep := stx.getArgs.mapIdx (fun ⟨i, _⟩ n =>
i % 2 == 1 && n.matchesNull 0 && i != stx.getArgs.size - 1) |>.any id
visitArgs do
for i in (List.range stx.getArgs.size).reverse do
if i % 2 == 0 then p else pSep <|>
-- If the final separator is a newline, skip it.
((if i == stx.getArgs.size - 1 then pure () else pushWhitespace "\n") *> goLeft)
-- If there is any newline separator, then we need to force a newline at the
-- start so that `withPosition` will pick up the right column.
-- If there is any newline separator, then we add an `align` at the start
-- so that `withPosition` will pick up the right column.
if hasNewlineSep then
pushWhitespace "\n"
-- HACK: allow formatter to put initial brace on previous line in structure instances
modify ({ · with mustBeGrouped := false })
pushAlign (force := true)

@[combinator_formatter sepBy1Indent] def sepBy1Indent.formatter := sepByIndent.formatter

Expand Down
4 changes: 4 additions & 0 deletions src/Lean/PrettyPrinter/Formatter.lean
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ def pushWhitespace (f : Format) : FormatterM Unit := do
def pushLine : FormatterM Unit :=
pushWhitespace Format.line

def pushAlign (force : Bool) : FormatterM Unit :=
-- don't reset leadWord because .align may introduce zero space
push (.align force)

/-- Execute `x` at the right-most child of the current node, if any, then advance to the left. -/
def visitArgs (x : FormatterM Unit) : FormatterM Unit := do
let stx ← getCur
Expand Down
46 changes: 44 additions & 2 deletions tests/lean/formatTerm.lean
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Lean
open Lean
open Lean PrettyPrinter

def fmt (stx : CoreM Syntax) : CoreM Format := stx >>= PrettyPrinter.formatTerm
def fmt (stx : CoreM Syntax) : CoreM Format := stx >>= formatTerm

#eval fmt `(if c then do t else e)
#eval fmt `(if c then do t; t else e)
Expand All @@ -10,3 +10,45 @@ def fmt (stx : CoreM Syntax) : CoreM Format := stx >>= PrettyPrinter.formatTerm
#eval fmt `(if c then do t else if c then do t else do e) -- FIXME: make this cascade better?
#eval fmt `(do if c then t else e)
#eval fmt `(do if c then t else if c then t else e)

#eval fmt `(def foo := by
· skip; skip
· skip; skip
skip
(skip; skip)
(skip; skip
try skip; skip
try skip
skip
skip))

#eval fmt `(by
try
skip
skip)

set_option format.indent 3 in
#eval fmt `(by
try
skip
skip)
set_option format.indent 4 in
#eval fmt `(by
try
skip
skip)
set_option format.indent 4 in
#eval fmt `(by
try
skip
skip
skip)

#eval fmt `({
foo := bar
bar := foo + bar
})

#eval fmt `(let x := { foo := bar
bar := foo + bar }
x)
30 changes: 30 additions & 0 deletions tests/lean/formatTerm.lean.expected.out
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,33 @@ do
t.1
else
e.1
def foo.1 := by
· skip; skip
· skip; skip
skip
(skip; skip)
( skip; skip
try skip; skip
try
skip
skip
skip)
by
try
skip
skip
by
try
skip
skip
by try skip
skip
by try skip
skip
skip
{ foo.1 := bar.1
bar.1 := foo.1 + bar.1 }
let x.1 :=
{ foo.1 := bar.1
bar.1 := foo.1 + bar.1 }
x.1
10 changes: 5 additions & 5 deletions tests/lean/sepByIndentQuot.lean.expected.out
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
frobnicate✝ { x := x, x := x, x := x, x := x }
frobnicate✝ {
x := x
x := x
x := x
x := x }
frobnicate✝
{ x := x
x := x
x := x
x := x }
4 changes: 2 additions & 2 deletions tests/lean/tacUnsolvedGoalsErrors.lean.expected.out
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ p q r : Prop
h2 : p → q
h✝ : q
⊢ q
tacUnsolvedGoalsErrors.lean:26:2-27:8: error: unsolved goals
tacUnsolvedGoalsErrors.lean:26:2-26:3: error: unsolved goals
case inl
p q r : Prop
h2 : p → q
h✝ : p
⊢ q
tacUnsolvedGoalsErrors.lean:28:2-29:8: error: unsolved goals
tacUnsolvedGoalsErrors.lean:28:2-28:3: error: unsolved goals
case inr
p q r : Prop
h2 : p → q
Expand Down
4 changes: 2 additions & 2 deletions tests/lean/traceTacticSteps.lean.expected.out
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
True
[Elab.step.result] True
[Elab.step] expected type: True, term
by
by
skip
trivial
[Elab.step.result] ?m
Expand All @@ -15,7 +15,7 @@
[Elab.step] skip
[Elab.step] trivial
[Elab.step] (apply And.intro✝) <;> trivial
[Elab.step] focus
[Elab.step] focus
apply And.intro✝
with_annotate_state"<;>" skip
all_goals trivial
Expand Down