|
| 1 | +/- |
| 2 | +Copyright (c) 2025 Lean FRO LLC. All rights reserved. |
| 3 | +Released under Apache 2.0 license as described in the file LICENSE. |
| 4 | +Author: Joachim Breitner |
| 5 | +-/ |
| 6 | + |
| 7 | +import Verso |
| 8 | + |
| 9 | +import Manual.Meta.Attribute |
| 10 | +import Manual.Meta.Basic |
| 11 | +import Manual.Meta.CustomStyle |
| 12 | +import Manual.Meta.Lean |
| 13 | +import Manual.Meta.Table |
| 14 | + |
| 15 | +open Lean Meta Elab |
| 16 | +open Verso Doc Elab Manual |
| 17 | +open Verso.Genre.Manual |
| 18 | +open SubVerso.Highlighting Highlighted |
| 19 | + |
| 20 | + |
| 21 | +namespace Manual |
| 22 | + |
| 23 | +/-- |
| 24 | +A table for monotonicity lemmas. Likely some of this logic can be extracted to a helper |
| 25 | +in `Manual/Meta/Table.lean`. |
| 26 | +-/ |
| 27 | +private def mkInlineTable (rows : Array (Array Term)) (tag : Option String := none) : TermElabM Term := do |
| 28 | + if h : rows.size = 0 then |
| 29 | + throwError "Expected at least one row" |
| 30 | + else |
| 31 | + let columns := rows[0].size |
| 32 | + if columns = 0 then |
| 33 | + throwError "Expected at least one column" |
| 34 | + if rows.any (·.size != columns) then |
| 35 | + throwError s!"Expected all rows to have same number of columns, but got {rows.map (·.size)}" |
| 36 | + |
| 37 | + let blocks : Array Term := |
| 38 | + #[ ← ``(Inline.text "Theorem"), ← ``(Inline.text "Pattern") ] ++ |
| 39 | + rows.flatten |
| 40 | + -- The tag down here is relying on the coercion from `String` to `Tag` |
| 41 | + ``(Block.other (Block.table $(quote columns) (header := true) Option.none Option.none (tag := $(quote tag))) |
| 42 | + #[Block.ul #[$[Verso.Doc.ListItem.mk #[Block.para #[$blocks]]],*]]) |
| 43 | + |
| 44 | + |
| 45 | +section delabhelpers |
| 46 | + |
| 47 | +/-! |
| 48 | +To format the monotonicy lemma patterns, I’d like to clearly mark the monotone arguments from |
| 49 | +the other arguments. So I define two gadgets with custom delaborators. |
| 50 | +-/ |
| 51 | + |
| 52 | +def monoArg := @id |
| 53 | +def otherArg := @id |
| 54 | + |
| 55 | +open PrettyPrinter.Delaborator |
| 56 | + |
| 57 | +@[app_delab monoArg] def delabMonoArg : Delab := |
| 58 | + PrettyPrinter.Delaborator.withOverApp 2 `(·) |
| 59 | +@[app_delab otherArg] def delabOtherArg : Delab := |
| 60 | + PrettyPrinter.Delaborator.withOverApp 2 `(_) |
| 61 | + |
| 62 | +end delabhelpers |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +@[block_role_expander monotonicityLemmas] |
| 67 | +def monotonicityLemmas : BlockRoleExpander |
| 68 | + | #[], #[] => do |
| 69 | + let names := (Meta.Monotonicity.monotoneExt.getState (← getEnv)).values |
| 70 | + let names := names.qsort (toString · < toString ·) |
| 71 | + |
| 72 | + let rows : Array (Array Term) ← names.mapM fun name => do |
| 73 | + -- Extract the target pattern |
| 74 | + let ci ← getConstInfo name |
| 75 | + |
| 76 | + -- Omit the `Lean.Order` namespace, if present, to keep the table concise |
| 77 | + let nameStr := (name.replacePrefix `Lean.Order .anonymous).getString! |
| 78 | + let hl : Highlighted ← constTok name nameStr |
| 79 | + let nameStx ← `(Inline.other {Inline.name with data := ToJson.toJson $(quote hl)} |
| 80 | + #[Inline.code $(quote nameStr)]) |
| 81 | + |
| 82 | + let patternStx : TSyntax `term ← |
| 83 | + forallTelescope ci.type fun _ concl => do |
| 84 | + unless concl.isAppOfArity ``Lean.Order.monotone 5 do |
| 85 | + throwError "Unexpected conclusion of {name}" |
| 86 | + let f := concl.appArg! |
| 87 | + unless f.isLambda do |
| 88 | + throwError "Unexpected conclusion of {name}" |
| 89 | + lambdaBoundedTelescope f 1 fun x call => do |
| 90 | + -- Monotone arguments are the free variables applied to `x`, |
| 91 | + -- Other arguments the other |
| 92 | + -- This is an ad-hoc transformation and may fail in cases more complex |
| 93 | + -- than we need right now (e.g. binders in the goal). |
| 94 | + let call' ← Meta.transform call (pre := fun e => do |
| 95 | + if e.isApp && e.appFn!.isFVar && e.appArg! == x[0]! then |
| 96 | + .done <$> mkAppM ``monoArg #[e] |
| 97 | + else if e.isFVar then |
| 98 | + .done <$> mkAppM ``otherArg #[e] |
| 99 | + else |
| 100 | + pure .continue) |
| 101 | + |
| 102 | + let hlCall ← withOptions (·.setBool `pp.tagAppFns true) do |
| 103 | + let fmt ← Lean.Widget.ppExprTagged call' |
| 104 | + renderTagged none fmt ⟨{}, false⟩ |
| 105 | + let fmt ← ppExpr call' |
| 106 | + ``(Inline.other (Inline.lean $(quote hlCall)) #[(Inline.code $(quote fmt.pretty))]) |
| 107 | + |
| 108 | + pure #[nameStx, patternStx] |
| 109 | + |
| 110 | + let tableStx ← mkInlineTable rows (tag := "--monotonicity-lemma-table") |
| 111 | + let extraCss ← `(Block.other {Block.CSS with data := $(quote css)} #[]) |
| 112 | + return #[extraCss, tableStx] |
| 113 | + | _, _ => throwError "Unexpected arguments" |
| 114 | +where |
| 115 | + css := r#" |
| 116 | +table#--monotonicity-lemma-table { |
| 117 | + border-collapse: collapse; |
| 118 | +} |
| 119 | +table#--monotonicity-lemma-table th { |
| 120 | + text-align: center; |
| 121 | +} |
| 122 | +table#--monotonicity-lemma-table th, table#--monotonicity-lemma-table th p { |
| 123 | + font-family: var(--verso-structure-font-family); |
| 124 | +} |
| 125 | +table#--monotonicity-lemma-table td:first-child { |
| 126 | + padding-bottom: 0.25em; |
| 127 | + padding-top: 0.25em; |
| 128 | + padding-left: 0; |
| 129 | + padding-right: 1.5em; |
| 130 | +} |
| 131 | + "# |
| 132 | + |
| 133 | +-- #eval do |
| 134 | +-- let (ss, _) ← (monotonicityLemmas #[] #[]).run {} (.init .missing) |
| 135 | +-- logInfo (ss[0]!.raw.prettyPrint) |
0 commit comments