diff --git a/doc/UsersGuide/Basic.lean b/doc/UsersGuide/Basic.lean index a45f572ed..550896477 100644 --- a/doc/UsersGuide/Basic.lean +++ b/doc/UsersGuide/Basic.lean @@ -1,7 +1,7 @@ import VersoManual import UsersGuide.Markup -open Verso.Genre Manual +open Verso.Genre Manual InlineLean set_option pp.rawOnError true @@ -42,7 +42,68 @@ All genres use the same markup syntax, and they can share extensions to the mark Mixing incompatible features results in an ordinary Lean type error. ::: -# Docstrings +# The Manual Genre + +## Tables + +Tables can be written using the `table` directive. +``` +:::table +* * 1, 1 + * 1, 2 +* * 2, 1 + * 2, 2 +* * 3, 1 + * 3, 2 +::: +``` +produces +:::table +* * 1, 1 + * 1, 2 +* * 2, 1 + * 2, 2 +* * 3, 1 + * 3, 2 +::: + +## Inline Lean Code + +The namespace `Verso.Genre.Manual.InlineLean` contains the `lean` code block: + +```` +```lean (name := twoPlusTwo) +#check 2 + 2 +``` +```` +results in +```lean (name := twoPlusTwo) +#check 2 + 2 +``` + +and + +```` +```leanOutput (name := twoPlusTwo) +#check 2 + 2 +``` +```` + +results in + +```` +```leanOutput twoPlusTwo +2 + 2 : Nat +``` +```` +results in +```leanOutput twoPlusTwo +2 + 2 : Nat +``` + + + +## Docstrings %%% tag := "docstrings" %%% @@ -57,7 +118,7 @@ results in {docstring List.forM} -## More Docstring Examples +### More Docstring Examples %%% shortTitle := "More Docstrings" %%% @@ -96,7 +157,7 @@ They include heuristic elaboration of code items in their Markdown that attempts {docstring Thunk} -# Technical Terminology +## Technical Terminology %%% shortTitle := "Glossary" tag := "tech-terms" diff --git a/examples/textbook/DemoTextbook/Meta/Lean.lean b/examples/textbook/DemoTextbook/Meta/Lean.lean index b6081e137..43510408b 100644 --- a/examples/textbook/DemoTextbook/Meta/Lean.lean +++ b/examples/textbook/DemoTextbook/Meta/Lean.lean @@ -8,8 +8,9 @@ import VersoManual open Verso.Genre Manual open Verso.Doc Elab +open DocElabM open Verso.ArgParse -open Lean +open Lean Meta namespace DemoTextbook @@ -32,11 +33,14 @@ block_extension Block.savedImport (file : String) (source : String) where /-- Lean code that is saved to the examples file. -/ -@[code_block_expander savedLean] -def savedLean : CodeBlockExpander +@[code_block_elab savedLean] +def savedLean : CodeBlockElab | args, code => do let underlying ← InlineLean.lean args code - return #[← ``(Block.other (Block.savedLean $(quote (← getFileName)) $(quote (code.getString))) #[$underlying,*])] + let g ← genreExpr + let b := mkApp2 (.const ``Block.savedLean []) (toExpr (← getFileName)) (toExpr code.getString) + return mkApp3 (.const ``Block.other []) g b (← mkArrayLit (← blockType) [underlying]) + /-- An import of some other module, to be located in the saved code. Not rendered. diff --git a/src/multi-verso/MultiVerso/Slug.lean b/src/multi-verso/MultiVerso/Slug.lean index 84e7ad60a..babde2728 100644 --- a/src/multi-verso/MultiVerso/Slug.lean +++ b/src/multi-verso/MultiVerso/Slug.lean @@ -10,7 +10,7 @@ set_option linter.missingDocs true namespace Verso.Multi open Verso.Method -open Lean (ToJson FromJson) +open Lean (ToJson FromJson ToExpr) open Std (HashSet) /-- The characters allowed in slugs. -/ @@ -71,7 +71,7 @@ theorem String.Pos.add_0_eq_size {c : Char} : (0 : String.Pos) + c = ⟨c.utf8Si show 0 + c.utf8Size = c.utf8Size simp -instance : DecidablePred Slug.WF := fun str => +instance Slug.WF.decide : DecidablePred Slug.WF := fun str => if h : str.toList.all (· ∈ Slug.validChars) then isTrue (by unfold Slug.WF; exact h) else isFalse h @[simp] @@ -172,6 +172,11 @@ Converts a string to a slug. -/ def ofString (str : String) : Slug := str.sluggify +/-- +Converts a string to a slug, performing no conversion because the string is already a valid slug. +-/ +def ofString' (str : String) (wf : WF str := by decide) : Slug := ⟨str, wf⟩ + /-- Returns a slug that's not present in `used`, starting with `slug` and appending consecutive numbers until it becomes unique. diff --git a/src/verso-manual/VersoManual.lean b/src/verso-manual/VersoManual.lean index cc93e73b3..f7fd2ca9f 100644 --- a/src/verso-manual/VersoManual.lean +++ b/src/verso-manual/VersoManual.lean @@ -148,12 +148,14 @@ block_extension Block.paragraph where some <| fun _ go _ _ content => do pure <| {{
{{← content.mapM go}}
}} -@[directive_expander paragraph] -def paragraph : DirectiveExpander +open Lean.Meta in +@[directive_elab paragraph] +def paragraph : DirectiveElab | #[], stxs => do - let args ← stxs.mapM elabBlock - let val ← ``(Block.other Block.paragraph #[ $[ $args ],* ]) - pure #[val] + let g ← DocElabM.genreExpr + let bt ← DocElabM.blockType + let args ← stxs.mapM elabBlock' + return Lean.mkApp3 (.const ``Block.other []) g (← mkAppM ``Block.paragraph #[]) (← mkArrayLit bt args.toList) | _, _ => Lean.Elab.throwUnsupportedSyntax diff --git a/src/verso-manual/VersoManual/Basic.lean b/src/verso-manual/VersoManual/Basic.lean index 3a7dc896d..7509ed09a 100644 --- a/src/verso-manual/VersoManual/Basic.lean +++ b/src/verso-manual/VersoManual/Basic.lean @@ -16,7 +16,9 @@ import Verso.Output.Html import Verso.Output.TeX import Verso.BEq -open Lean (Name Json NameMap ToJson FromJson) +open Lean (Name Json NameMap ToJson FromJson ToExpr Expr) +open Lean.ToExpr +open Lean.Meta open Std (HashSet HashMap TreeSet) open Verso.Doc open Verso.Multi @@ -59,7 +61,17 @@ instance : ToString Tag where instance : Coe String Tag where coe := .provided - +def Tag.toExpr (tag : Tag) : MetaM Expr := do + match tag with + | .provided x => return Expr.app (.const ``Tag.provided []) (ToExpr.toExpr x) + | Tag.internal x => return Expr.app (.const ``Tag.internal []) (ToExpr.toExpr x) + | Tag.external x => + let str := ToExpr.toExpr x.toString + let p := Expr.app (.const ``Slug.WF []) str + -- Should always succeed; we just need to run this due to type erasure + let prf ← mkDecideProof p + let slug := Lean.mkApp2 (.const ``Slug.ofString' []) str prf + return Expr.app (.const ``Tag.external []) slug /-- When rendering multi-page HTML, should splitting pages follow the depth setting? -/ inductive HtmlSplitMode where diff --git a/src/verso-manual/VersoManual/Docstring.lean b/src/verso-manual/VersoManual/Docstring.lean index c0a6b5c6c..85c8bfe01 100644 --- a/src/verso-manual/VersoManual/Docstring.lean +++ b/src/verso-manual/VersoManual/Docstring.lean @@ -8,6 +8,7 @@ import Std.Data.HashSet import VersoManual.Basic import VersoManual.HighlightedCode import VersoManual.Index +import VersoManual.InlineLean.Env import VersoManual.Markdown import VersoManual.Docstring.Config import VersoManual.Docstring.Progress @@ -84,7 +85,7 @@ structure Signature where wide : Highlighted /-- The signature formatted for narrower screens, such as mobile displays -/ narrow : Highlighted -deriving ToJson, FromJson, Quote +deriving ToJson, FromJson, Quote, ToExpr namespace Block @@ -174,17 +175,20 @@ def ppSignature (c : Name) (showNamespace : Bool := true) (openDecls : List Open return ⟨← ppTerm ⟨stx⟩, infos⟩ -- HACK: not a term +deriving instance ToExpr for BinderInfo +deriving instance ToExpr for DefinitionSafety +deriving instance ToExpr for QuotKind structure DocName where name : Name hlName : Highlighted signature : Highlighted docstring? : Option String -deriving ToJson, FromJson, Repr, Quote +deriving ToJson, FromJson, Repr, Quote, ToExpr inductive Visibility where | «public» | «private» | «protected» -deriving Inhabited, Repr, ToJson, FromJson, DecidableEq, Ord, Quote +deriving Inhabited, Repr, ToJson, FromJson, DecidableEq, Ord, Quote, ToExpr def Visibility.of (env : Environment) (n : Name) : Visibility := if isPrivateName n then .private else if isProtected env n then .protected else .public @@ -204,7 +208,7 @@ structure FieldInfo where autoParam : Bool docString? : Option String visibility : Visibility -deriving Inhabited, Repr, ToJson, FromJson, Quote +deriving Inhabited, Repr, ToJson, FromJson, Quote, ToExpr structure ParentInfo where @@ -212,7 +216,7 @@ structure ParentInfo where name : Name parent : Highlighted index : Nat -deriving ToJson, FromJson, Quote +deriving ToJson, FromJson, Quote, ToExpr inductive DeclType where /-- @@ -228,7 +232,7 @@ inductive DeclType where | recursor (safety : DefinitionSafety) | quotPrim (kind : QuotKind) | other -deriving ToJson, FromJson, Quote +deriving ToJson, FromJson, Quote, ToExpr def DeclType.label : DeclType → String | .structure false .. => "structure" @@ -1009,26 +1013,34 @@ def tryElabCodeMetavarTermWith (mk : Highlighted → String → DocElabM α) (st else throwError "Not a doc metavar: {stx}" +private def asInline (hl : Highlighted) : DocElabM Expr := do + let g ← DocElabM.genreExpr + let arr ← Meta.mkArrayLit (.app (.const ``Doc.Inline []) g) [mkApp2 (.const ``Doc.Inline.code []) g (toExpr hl.toString)] + return mkApp3 (.const ``Doc.Inline.other []) g (.app (.const ``Inline.leanFromMarkdown []) (toExpr hl)) arr + +private def asBlock (hl : Highlighted) : DocElabM Expr := do + let g ← DocElabM.genreExpr + let arr ← Meta.mkArrayLit (.app (.const ``Doc.Block []) g) [mkApp2 (.const ``Doc.Block.code []) g (toExpr hl.toString)] + return mkApp3 (.const ``Doc.Block.other []) g (.app (.const ``Block.leanFromMarkdown []) (toExpr hl)) arr + + open Lean Elab Term in -def tryElabInlineCodeTerm (str : String) (ignoreElabErrors := false) (identOnly := false) : DocElabM Term := - tryElabCodeTermWith (ignoreElabErrors := ignoreElabErrors) (identOnly := identOnly) (fun hls str => - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)])) +def tryElabInlineCodeTerm (str : String) (ignoreElabErrors := false) (identOnly := false) : DocElabM Expr := + tryElabCodeTermWith (ignoreElabErrors := ignoreElabErrors) (identOnly := identOnly) (fun hls _ => asInline hls) str open Lean Elab Term in -def tryElabInlineCodeMetavarTerm (str : String) (ignoreElabErrors := false) : DocElabM Term := - tryElabCodeMetavarTermWith (ignoreElabErrors := ignoreElabErrors) (fun hls str => - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)])) +def tryElabInlineCodeMetavarTerm (str : String) (ignoreElabErrors := false) : DocElabM Expr := + tryElabCodeMetavarTermWith (ignoreElabErrors := ignoreElabErrors) (fun hls _ => asInline hls) str open Lean Elab Term in -def tryElabBlockCodeTerm (str : String) (ignoreElabErrors := false) : DocElabM Term := - tryElabCodeTermWith (ignoreElabErrors := ignoreElabErrors) (fun hls str => - ``(Verso.Doc.Block.other (Block.leanFromMarkdown $(quote hls)) #[Verso.Doc.Block.code $(quote str)])) +def tryElabBlockCodeTerm (str : String) (ignoreElabErrors := false) : DocElabM Expr := + tryElabCodeTermWith (ignoreElabErrors := ignoreElabErrors) (fun hls _ => asBlock hls) str open Lean Elab Term in -def tryParseInlineCodeTactic (str : String) : DocElabM Term := do +def tryParseInlineCodeTactic (str : String) : DocElabM Expr := do let loc := (← getRef).getPos?.map (← getFileMap).utf8PosToLspPos let src := if let some ⟨line, col⟩ := loc then s!"" @@ -1038,25 +1050,22 @@ def tryParseInlineCodeTactic (str : String) : DocElabM Term := do | .ok stx => DocElabM.withFileMap (.ofString str) <| do -- TODO try actually running the tactic - if the parameters are simple enough, then it may work -- and give better highlights - let hls ← highlight stx #[] (PersistentArray.empty) - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)]) + asInline =<< highlight stx #[] (PersistentArray.empty) open Lean Elab Term in -def tryInlineOption (str : String) : DocElabM Term := do +def tryInlineOption (str : String) : DocElabM Expr := do let optName := str.trim.toName let optDecl ← getOptionDecl optName - let hl : Highlighted := optTok optName optDecl.declName optDecl.descr - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hl)) #[Verso.Doc.Inline.code $(quote str)]) + asInline <| optTok optName optDecl.declName optDecl.descr where optTok (name declName : Name) (descr : String) : Highlighted := .token ⟨.option name declName descr, name.toString⟩ open Lean Elab in -def tryTacticName (tactics : Array Tactic.Doc.TacticDoc) (str : String) : DocElabM Term := do +def tryTacticName (tactics : Array Tactic.Doc.TacticDoc) (str : String) : DocElabM Expr := do for t in tactics do if t.userName == str then - let hl : Highlighted := tacToken t - return ← ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hl)) #[Verso.Doc.Inline.code $(quote str)]) + return ← asInline <| tacToken t throwError "Not a tactic name: {str}" where tacToken (t : Lean.Elab.Tactic.Doc.TacticDoc) : Highlighted := @@ -1064,7 +1073,7 @@ where open Lean Elab Term in open Lean.Parser in -def tryHighlightKeywords (extraKeywords : Array String) (str : String) : DocElabM Term := do +def tryHighlightKeywords (extraKeywords : Array String) (str : String) : DocElabM Expr := do let loc := (← getRef).getPos?.map (← getFileMap).utf8PosToLspPos let src := if let some ⟨line, col⟩ := loc then s!"" @@ -1073,8 +1082,7 @@ def tryHighlightKeywords (extraKeywords : Array String) (str : String) : DocElab match runParser extraKeywords (← getEnv) (← getOptions) p str src (prec := 0) with | .error _e => throwError "Not keyword-highlightable" | .ok stx => DocElabM.withFileMap (.ofString str) <| do - let hls ← highlight stx #[] (PersistentArray.empty) - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)]) + asInline =<< highlight stx #[] (PersistentArray.empty) where simpleFn := andthenFn whitespace <| nodeFn nullKind <| manyFn tokenFn @@ -1111,7 +1119,7 @@ private def getAttr : Syntax → Syntax | _ => .missing open Lean Elab Term in -def tryParseInlineCodeAttribute (validate := true) (str : String) : DocElabM Term := do +def tryParseInlineCodeAttribute (validate := true) (str : String) : DocElabM Expr := do let loc := (← getRef).getPos?.map (← getFileMap).utf8PosToLspPos let src := if let some ⟨line, col⟩ := loc then s!"" @@ -1131,11 +1139,9 @@ def tryParseInlineCodeAttribute (validate := true) (str : String) : DocElabM Ter match getAttributeImpl (← getEnv) attrName with | .error e => throwError e | .ok _ => - let hls ← highlight stx #[] (PersistentArray.empty) - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)]) + asInline =<< highlight stx #[] (PersistentArray.empty) else - let hls ← highlight stx #[] (PersistentArray.empty) - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hls)) #[Verso.Doc.Inline.code $(quote str)]) + asInline =<< highlight stx #[] (PersistentArray.empty) private def indentColumn (str : String) : Nat := Id.run do @@ -1168,7 +1174,7 @@ private def indentColumn (str : String) : Nat := Id.run do #eval indentColumn " abc\n\n def\n a" open Lean Elab Term in -def tryElabBlockCodeCommand (str : String) (ignoreElabErrors := false) : DocElabM Term := do +def tryElabBlockCodeCommand (str : String) (ignoreElabErrors := false) : DocElabM Expr := do let loc := (← getRef).getPos?.map (← getFileMap).utf8PosToLspPos let src := if let some ⟨line, col⟩ := loc then s!"" @@ -1201,24 +1207,22 @@ def tryElabBlockCodeCommand (str : String) (ignoreElabErrors := false) : DocElab if cmdState.messages.hasErrors then throwError "Errors found in command" - let hls ← DocElabM.withFileMap (.ofString str) do + DocElabM.withFileMap (.ofString str) do let mut hls := Highlighted.empty for cmd in cmds do hls := hls ++ (← highlight cmd cmdState.messages.toArray cmdState.infoState.trees) - pure <| hls.deIndent (indentColumn str) + asBlock <| hls.deIndent (indentColumn str) - ``(Verso.Doc.Block.other (Block.leanFromMarkdown $(quote hls)) #[Verso.Doc.Block.code $(quote str)]) open Lean Elab Term in -def tryElabInlineCodeName (str : String) : DocElabM Term := do +def tryElabInlineCodeName (str : String) : DocElabM Expr := do let str := str.trim let x := str.toName if x.toString == str then let stx := mkIdent x let n ← realizeGlobalConstNoOverload stx - let hl : Highlighted ← constTok n str - ``(Verso.Doc.Inline.other (Inline.leanFromMarkdown $(quote hl)) #[Verso.Doc.Inline.code $(quote str)]) + asInline =<< constTok n str else throwError "Not a name: '{str}'" where @@ -1248,22 +1252,24 @@ private def attempt (str : String) (xs : List (String → DocElabM α)) : DocEla open Lean Elab Term in -def tryElabInlineCodeUsing (elabs : List (String → DocElabM Term)) - (priorWord : Option String) (str : String) : DocElabM Term := do +def tryElabInlineCodeUsing (elabs : List (String → DocElabM Expr)) + (priorWord : Option String) (str : String) : DocElabM Expr := do + let g ← DocElabM.genreExpr -- Don't try to show Lake commands as terms - if "lake ".isPrefixOf str then return (← ``(Verso.Doc.Inline.code $(quote str))) + let code := mkApp2 (.const ``Verso.Doc.Inline.code []) g (toExpr str) + if "lake ".isPrefixOf str then return code try attempt str <| wordElab priorWord ++ elabs catch | .error ref e => logWarningAt ref e - ``(Verso.Doc.Inline.code $(quote str)) + return code | e => if isAutoBoundImplicitLocalException? e |>.isSome then throw e else logWarning m!"Internal exception uncaught: {e.toMessageData}" - ``(Verso.Doc.Inline.code $(quote str)) + return code where wordElab | some "attribute" => [tryParseInlineCodeAttribute (validate := false)] @@ -1272,7 +1278,7 @@ where open Elab in def tryElabInlineCode (allTactics : Array Tactic.Doc.TacticDoc) (extraKeywords : Array String) - (priorWord : Option String) (str : String) : DocElabM Term := + (priorWord : Option String) (str : String) : DocElabM Expr := tryElabInlineCodeUsing [ tryElabInlineCodeName, -- When identifiers have the same name as tactics, prefer the identifiers @@ -1295,7 +1301,7 @@ displaying metavariable-typed terms (e.g., through auto-bound implicits or elaboration failures). -/ def tryElabInlineCodeStrict (allTactics : Array Tactic.Doc.TacticDoc) (extraKeywords : Array String) - (priorWord : Option String) (str : String) : DocElabM Term := + (priorWord : Option String) (str : String) : DocElabM Expr := tryElabInlineCodeUsing [ tryElabInlineCodeName, -- When identifiers have the same name as tactics, prefer the identifiers @@ -1310,25 +1316,28 @@ def tryElabInlineCodeStrict (allTactics : Array Tactic.Doc.TacticDoc) (extraKeyw ] priorWord str open Lean Elab Term in -def tryElabBlockCode (_info? _lang? : Option String) (str : String) : DocElabM Term := do +def tryElabBlockCode (_info? _lang? : Option String) (str : String) : DocElabM Expr := do + let g ← DocElabM.genreExpr + let code := mkApp2 (.const ``Verso.Doc.Block.code []) g (mkStrLit str) try attempt str [ - tryElabBlockCodeCommand, - tryElabBlockCodeTerm, - tryElabBlockCodeCommand (ignoreElabErrors := true), - withTheReader Term.Context (fun ctx => {ctx with autoBoundImplicit := true}) ∘ - tryElabBlockCodeTerm (ignoreElabErrors := true) - ] + tryElabBlockCodeCommand, + tryElabBlockCodeTerm, + tryElabBlockCodeCommand (ignoreElabErrors := true), + withTheReader Term.Context (fun ctx => {ctx with autoBoundImplicit := true}) ∘ + tryElabBlockCodeTerm (ignoreElabErrors := true) + ] + catch | .error ref e => logWarningAt ref e - ``(Verso.Doc.Block.code $(quote str)) + return code | e => if isAutoBoundImplicitLocalException? e |>.isSome then throw e else logWarning m!"Internal exception uncaught: {e.toMessageData}" - ``(Verso.Doc.Block.code $(quote str)) + return code open Lean Elab Term in /-- @@ -1336,9 +1345,10 @@ Heuristically elaborate Lean fragments in Markdown code. The provided names are from left to right, with the names bound by the signature being available in the local scope in which the Lean fragments are elaborated. -/ -def blockFromMarkdownWithLean (names : List Name) (b : MD4Lean.Block) : DocElabM Term := do +def blockFromMarkdownWithLean (names : List Name) (b : MD4Lean.Block) : DocElabM Expr := do + let g ← DocElabM.genreExpr unless (← Docstring.getElabMarkdown) do - return (← Markdown.blockFromMarkdown b (handleHeaders := Markdown.strongEmphHeaders)) + return (← Markdown.blockFromMarkdown'' g b (handleHeaders := Markdown.strongEmphHeaders'' g)) let tactics ← Elab.Tactic.Doc.allTacticDocs let keywords := tactics.map (·.userName) try @@ -1349,13 +1359,13 @@ def blockFromMarkdownWithLean (names : List Name) (b : MD4Lean.Block) : DocElabM blockFromMarkdownWithLean decls b | [] => -- It'd be silly for some weird edge case to block on this feature... - let rec loop (max : Nat) (s : SavedState) : DocElabM Term := do + let rec loop (max : Nat) (s : SavedState) : DocElabM Expr := do match max with | k + 1 => try let res ← - Markdown.blockFromMarkdown b - (handleHeaders := Markdown.strongEmphHeaders) + Markdown.blockFromMarkdown'' g b + (handleHeaders := Markdown.strongEmphHeaders'' g) (elabInlineCode := tryElabInlineCode tactics keywords) (elabBlockCode := tryElabBlockCode) synthesizeSyntheticMVarsUsingDefault @@ -1377,8 +1387,60 @@ def blockFromMarkdownWithLean (names : List Name) (b : MD4Lean.Block) : DocElabM finally (s.restore : TermElabM _) catch _ => - Markdown.blockFromMarkdown b - (handleHeaders := Markdown.strongEmphHeaders) + Markdown.blockFromMarkdown'' g b + (handleHeaders := Markdown.strongEmphHeaders'' g) + +open Lean Elab Term in +/-- +Heuristically elaborate Lean fragments in Markdown code. The provided names are used as signatures, +from left to right, with the names bound by the signature being available in the local scope in +which the Lean fragments are elaborated. +-/ +-- TODO: replace Term version completely +def blockFromMarkdownWithLean' (names : List Name) (b : MD4Lean.Block) : DocElabM Expr := do + let g ← DocElabM.genreExpr + unless (← Docstring.getElabMarkdown) do + return (← Markdown.blockFromMarkdown'' g b (handleHeaders := Markdown.strongEmphHeaders'' g)) + let tactics ← Elab.Tactic.Doc.allTacticDocs + let keywords := tactics.map (·.userName) + try + match names with + | decl :: decls => + -- This brings the parameters into scope, so the term elaboration version catches them! + Meta.forallTelescopeReducing (← getConstInfo decl).type fun _ _ => + blockFromMarkdownWithLean' decls b + | [] => + -- It'd be silly for some weird edge case to block on this feature... + let rec loop (max : Nat) (s : SavedState) : DocElabM Expr := do + match max with + | k + 1 => + try + let res ← + Markdown.blockFromMarkdown'' g b + (handleHeaders := Markdown.strongEmphHeaders'' g) + (elabInlineCode := tryElabInlineCode tactics keywords) + (elabBlockCode := tryElabBlockCode) + synthesizeSyntheticMVarsUsingDefault + + discard <| addAutoBoundImplicits #[] (inlayHintPos? := none) + + return res + catch e => + if let some n := isAutoBoundImplicitLocalException? e then + s.restore (restoreInfo := true) + Meta.withLocalDecl n .implicit (← Meta.mkFreshTypeMVar) fun x => + withTheReader Term.Context (fun ctx => { ctx with autoBoundImplicits := ctx.autoBoundImplicits.push x } ) do + loop k (← (saveState : TermElabM _)) + else throw e + | 0 => throwError "Ran out of local name attempts" + let s ← (saveState : TermElabM _) + try + loop 40 s + finally + (s.restore : TermElabM _) + catch _ => + Markdown.blockFromMarkdown'' g b + (handleHeaders := Markdown.strongEmphHeaders'' g) structure DocstringConfig where name : Ident × Name @@ -1407,9 +1469,12 @@ def DocstringConfig.parse : ArgParse m DocstringConfig := end -@[block_role_expander docstring] -def docstring : BlockRoleExpander +open DocElabM in +open Meta in +@[block_role_elab docstring] +def docstring : BlockRoleElab | args, #[] => do + let g ← DocElabM.genreExpr let ⟨(x, name), allowMissing, hideFields, hideCtor, customLabel⟩ ← DocstringConfig.parse.run args let opts : Options → Options := allowMissing.map (fun b opts => verso.docstring.allowMissing.set opts b) |>.getD id @@ -1434,50 +1499,66 @@ def docstring : BlockRoleExpander let signature ← Signature.forName name let extras ← getExtras name declType - pure #[← ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstring $(quote name) $(quote declType) $(quote signature) $(quote customLabel)) #[$(blockStx ++ extras),*])] + let docBlock := mkApp4 (.const ``Manual.Block.docstring []) (toExpr name) (toExpr declType) (toExpr signature) (toExpr customLabel) + + return mkApp3 (.const ``Verso.Doc.Block.other []) g docBlock (← mkArrayLit (← blockType) (blockStx ++ extras).toList) | _, more => throwErrorAt more[0]! "Unexpected block argument" where - getExtras (name : Name) (declType : Block.Docstring.DeclType) : DocElabM (Array Term) := + getExtras (name : Name) (declType : Block.Docstring.DeclType) : DocElabM (Array Expr) := do + let g ← DocElabM.genreExpr match declType with | .structure isClass constructor? _ fieldInfo parents _ => do - let ctorRow : Option Term ← constructor?.mapM fun constructor => do + let ctorRow : Option Expr ← constructor?.mapM fun constructor => do let header := if isClass then "Instance Constructor" else "Constructor" - let sigDesc : Array Term ← + let sigDesc : Array Expr ← if let some docs := constructor.docstring? then let some mdAst := MD4Lean.parse docs | throwError "Failed to parse docstring as Markdown" mdAst.blocks.mapM (blockFromMarkdownWithLean [name, constructor.name]) - else pure (#[] : Array Term) - let sig ← `(Verso.Doc.Block.other (Verso.Genre.Manual.Block.internalSignature $(quote constructor.hlName) none) #[$sigDesc,*]) - ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstringSection $(quote header)) #[$sig]) + else pure (#[] : Array Expr) + let sigBlk := mkApp2 (.const ``Block.internalSignature []) (toExpr constructor.hlName) (toExpr (none : Option Highlighted)) + let sig := mkApp3 (.const ``Verso.Doc.Block.other []) g sigBlk (← Meta.mkArrayLit (← DocElabM.blockType) sigDesc.toList) + let sec := .app (.const ``Block.docstringSection []) (toExpr header) + return mkApp3 (.const ``Verso.Doc.Block.other []) g sec (← Meta.mkArrayLit (← DocElabM.blockType) [sig]) + - let parentsRow : Option Term ← do + let parentsRow : Option Expr ← do if parents.isEmpty then pure none else let header := "Extends" - let inh ← ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.inheritance $(quote name) $(quote parents)) #[]) - some <$> ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstringSection $(quote header)) #[$inh]) + let sec : Expr := .app (.const ``Block.docstringSection []) (toExpr header) + let inh := mkApp2 (.const ``Verso.Genre.Manual.Block.inheritance []) (toExpr name) (toExpr parents) + let inh := mkApp3 (.const ``Verso.Doc.Block.other []) g inh (← Meta.mkArrayLit (← DocElabM.blockType) []) + pure <| some <| mkApp3 (.const ``Verso.Doc.Block.other []) g sec (← Meta.mkArrayLit (← DocElabM.blockType) [inh]) - let fieldsRow : Option Term ← do + + let fieldsRow : Option Expr ← do let header := if isClass then "Methods" else "Fields" let fieldInfo := fieldInfo.filter (·.subobject?.isNone) - let fieldSigs : Array Term ← fieldInfo.mapM fun i => do + let fieldSigs : Array Expr ← fieldInfo.mapM fun i => do let inheritedFrom : Option Nat := i.fieldFrom.head?.bind (fun n => parents.findIdx? (·.name == n.name)) - let sigDesc : Array Term ← + let sigDesc : Array Expr ← if let some docs := i.docString? then let some mdAst := MD4Lean.parse docs | throwError "Failed to parse docstring as Markdown" mdAst.blocks.mapM (blockFromMarkdownWithLean <| name :: (constructor?.map ([·.name])).getD []) else - pure (#[] : Array Term) - ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.fieldSignature $(quote i.visibility) $(quote i.fieldName) $(quote i.type) $(quote inheritedFrom) $(quote <| parents.map (·.parent))) #[$sigDesc,*]) + pure (#[] : Array Expr) + + let fieldSig : Expr := + mkApp5 (.const ``Verso.Genre.Manual.Block.fieldSignature []) + (toExpr i.visibility) (toExpr i.fieldName) (toExpr i.type) (toExpr inheritedFrom) (toExpr (parents.map (·.parent))) + return mkApp3 (.const ``Verso.Doc.Block.other []) g fieldSig (← Meta.mkArrayLit (← DocElabM.blockType) sigDesc.toList) if fieldSigs.isEmpty then pure none - else some <$> ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstringSection $(quote header)) #[$fieldSigs,*]) + -- ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstringSection $(quote header)) #[$fieldSigs,*]) + else + let sec := .app (.const ``Block.docstringSection []) (toExpr header) + pure <| some <| mkApp3 (.const ``Verso.Doc.Block.other []) g sec (← Meta.mkArrayLit (← DocElabM.blockType) fieldSigs.toList) pure <| ctorRow.toArray ++ parentsRow.toArray ++ fieldsRow.toArray | .inductive ctors .. => do - let ctorSigs : Array Term ← + let ctorSigs : Array Expr ← -- Elaborate constructor docs in the type's NS ctors.mapM fun c => withTheReader Core.Context ({· with currNamespace := name}) do let sigDesc ← @@ -1485,9 +1566,12 @@ where let some mdAst := MD4Lean.parse docs | throwError "Failed to parse docstring as Markdown" mdAst.blocks.mapM (blockFromMarkdownWithLean [name, c.name]) - else pure (#[] : Array Term) - ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.constructorSignature $(quote c.signature)) #[$sigDesc,*]) - pure #[← ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.docstringSection "Constructors") #[$ctorSigs,*])] + else pure (#[] : Array Expr) + --``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.constructorSignature $(quote c.signature)) #[$sigDesc,*]) + let sig := .app (.const ``Block.constructorSignature []) (toExpr c.signature) + pure <| mkApp3 (.const ``Verso.Doc.Block.other []) g sig (← mkArrayLit (← blockType) sigDesc.toList) + let sec := .app (.const ``Block.docstringSection []) (toExpr "Constructors") + pure #[mkApp3 (.const ``Verso.Doc.Block.other []) g sec (← mkArrayLit (← blockType) ctorSigs.toList)] | _ => pure #[] section @@ -1505,16 +1589,19 @@ def IncludeDocstringOpts.parse : ArgParse m IncludeDocstringOpts := end -@[block_role_expander includeDocstring] -def includeDocstring : BlockRoleExpander +open DocElabM in +open Meta in +@[block_role_elab includeDocstring] +def includeDocstring : BlockRoleElab | args, #[] => do + let g ← DocElabM.genreExpr let {name, elaborate} ← IncludeDocstringOpts.parse.run args let fromMd := if elaborate then blockFromMarkdownWithLean [name] else - Markdown.blockFromMarkdown (handleHeaders := Markdown.strongEmphHeaders) + Markdown.blockFromMarkdown'' g (handleHeaders := Markdown.strongEmphHeaders'' g) let blockStx ← match ← getDocString? (← getEnv) name with @@ -1524,7 +1611,7 @@ def includeDocstring : BlockRoleExpander | throwError "Failed to parse docstring as Markdown" ast.blocks.mapM fromMd - pure blockStx + pure <| mkApp2 (.const ``Verso.Doc.Block.concat []) g (← mkArrayLit (← blockType) blockStx.toList) | _args, more => throwErrorAt more[0]! "Unexpected block argument" @@ -1563,8 +1650,10 @@ def highlightDataValue (v : DataValue) : Highlighted := | .ofSyntax (v : Syntax) => ⟨.unknown, toString v⟩ -- TODO -@[block_role_expander optionDocs] -def optionDocs : BlockRoleExpander +open DocElabM in +open Meta in +@[block_role_elab optionDocs] +def optionDocs : BlockRoleElab | args, #[] => do let #[.anon (.name x)] := args | throwError "Expected exactly one positional argument that is a name" @@ -1573,7 +1662,9 @@ def optionDocs : BlockRoleExpander let some mdAst := MD4Lean.parse optDecl.descr | throwErrorAt x "Failed to parse docstring as Markdown" let contents ← mdAst.blocks.mapM (blockFromMarkdownWithLean []) - pure #[← ``(Verso.Doc.Block.other (Verso.Genre.Manual.Block.optionDocs $(quote x.getId) $(quote <| highlightDataValue optDecl.defValue)) #[$contents,*])] + let g ← genreExpr + let optDocs := mkApp2 (.const ``Block.optionDocs []) (toExpr x.getId) (toExpr <| some <| highlightDataValue optDecl.defValue) + return mkApp3 (.const ``Verso.Doc.Block.other []) g optDocs (← mkArrayLit (← blockType) contents.toList) | _, more => throwErrorAt more[0]! "Unexpected block argument" @@ -1672,11 +1763,21 @@ private def getTactic? (name : String ⊕ Name) : TermElabM (Option TacticDoc) : return some t return none -@[directive_expander tactic] -def tactic : DirectiveExpander + +instance : ToExpr NameSet where + toTypeExpr := .const ``NameSet [] + toExpr xs := + mkApp3 (.const ``RBTree.fromList [0]) (.const ``Name []) (toExpr xs.toList) (.const ``Name.quickCmp []) + +deriving instance ToExpr for Elab.Tactic.Doc.TacticDoc + +open DocElabM in +open Meta in +@[directive_elab tactic] +def tactic : DirectiveElab | args, more => do let opts ← TacticDocsOptions.parse.run args - let tactic ← getTactic opts.name + let tactic ← InlineLean.usingExamplesEnv <| getTactic opts.name Doc.PointOfInterest.save (← getRef) tactic.userName if tactic.userName == tactic.internalName.toString && opts.show.isNone then throwError "No `show` option provided, but the tactic has no user-facing token name" @@ -1686,8 +1787,11 @@ def tactic : DirectiveExpander let some mdAst := tactic.docString >>= MD4Lean.parse | throwError "Failed to parse docstring as Markdown" mdAst.blocks.mapM (blockFromMarkdownWithLean []) - let userContents ← more.mapM elabBlock - pure #[← ``(Verso.Doc.Block.other (Block.tactic $(quote tactic) $(quote opts.show)) #[$(contents ++ userContents),*])] + let userContents ← more.mapM elabBlock' + let g ← genreExpr + let tac := mkApp2 (.const ``Block.tactic []) (toExpr tactic) (toExpr opts.show) + return mkApp3 (.const ``Verso.Doc.Block.other []) g tac (← mkArrayLit (← blockType) (contents ++ userContents).toList) + --pure #[← ``(Verso.Doc.Block.other (Block.tactic $(quote tactic) $(quote opts.show)) #[$(contents ++ userContents),*])] @@ -1761,7 +1865,9 @@ def tacticInline : RoleExpander | throwErrorAt arg "Expected code literal with the tactic name" let tacTok := tac.getString let tacName := tac.getString.toName - let some tacticDoc := (← getTactic? (.inl tacTok)) <|> (← getTactic? (.inr tacName)) + let some tacticDoc := + (← InlineLean.usingExamplesEnv <| getTactic? (.inl tacTok)) <|> + (← InlineLean.usingExamplesEnv <| getTactic? (.inr tacName)) | throwErrorAt tac "Didn't find tactic named {tac}" let hl : Highlighted := tacToken tacticDoc «show» @@ -1814,8 +1920,10 @@ def getConvTactic (name : String ⊕ Name) (allowMissing : Option Bool) : TermEl return ⟨k, ← getDocString? (← getEnv) k⟩ throwError m!"Conv tactic not found: {kind}" -@[directive_expander conv] -def conv : DirectiveExpander +open DocElabM in +open Meta in +@[directive_elab conv] +def conv : DirectiveElab | args, more => do let opts ← TacticDocsOptions.parse.run args let tactic ← getConvTactic opts.name opts.allowMissing @@ -1825,10 +1933,13 @@ def conv : DirectiveExpander | throwError "Failed to parse docstring as Markdown" mdAst.blocks.mapM (blockFromMarkdownWithLean []) else pure #[] - let userContents ← more.mapM elabBlock + let userContents ← more.mapM elabBlock' let some toShow := opts.show | throwError "An explicit 'show' is mandatory for conv docs (for now)" - pure #[← ``(Verso.Doc.Block.other (Block.conv $(quote tactic.name) $(quote toShow) $(quote tactic.docs?)) #[$(contents ++ userContents),*])] + let g ← genreExpr + let blk := mkApp3 (.const ``Block.conv []) (toExpr tactic.name) (toExpr toShow) (toExpr tactic.docs?) + return mkApp3 (.const ``Verso.Doc.Block.other []) g blk (← mkArrayLit (← blockType) (contents ++ userContents).toList) + open Verso.Genre.Manual.Markdown in open Lean Elab Term Parser Tactic Doc in diff --git a/src/verso-manual/VersoManual/InlineLean.lean b/src/verso-manual/VersoManual/InlineLean.lean index 92079cdb8..afb2d4921 100644 --- a/src/verso-manual/VersoManual/InlineLean.lean +++ b/src/verso-manual/VersoManual/InlineLean.lean @@ -14,6 +14,7 @@ import Verso import VersoManual.Basic import VersoManual.InlineLean.Block +import VersoManual.InlineLean.Env import VersoManual.InlineLean.IO import VersoManual.InlineLean.LongLines import VersoManual.InlineLean.Option @@ -23,8 +24,8 @@ import VersoManual.InlineLean.Signature import VersoManual.InlineLean.SyntaxError -open Lean Elab -open Verso ArgParse Doc Elab Genre.Manual Html Code Highlighted.WebAssets ExpectString +open Lean Elab Meta +open Verso ArgParse Doc Elab Genre.Manual Html Code Highlighted.WebAssets ExpectString DocElabM open SubVerso.Highlighting Highlighted open Verso.SyntaxUtils (parserInputString runParserCategory' SyntaxError) @@ -33,6 +34,8 @@ open Lean.Elab.Tactic.GuardMsgs namespace Verso.Genre.Manual.InlineLean + + inline_extension Inline.lean (hls : Highlighted) where data := let defined := hls.definedNames.toArray @@ -162,12 +165,15 @@ def reportMessages {m} [Monad m] [MonadLog m] [MonadError m] if messages.hasErrors then throwErrorAt blame "No error expected in code block, one occurred" +deriving instance ToExpr for Lsp.Position +deriving instance ToExpr for Lsp.Range + /-- Elaborates the provided Lean command in the context of the current Verso module. -/ -@[code_block_expander lean] -def lean : CodeBlockExpander - | args, str => withoutAsync <| do +@[code_block_elab lean] +def lean : CodeBlockElab + | args, str => withoutAsync <| usingExamplesEnv do let config ← LeanBlockConfig.parse.run args PointOfInterest.save (← getRef) ((config.name.map (·.toString)).getD (abbrevFirstLine 20 str.getString)) @@ -224,9 +230,14 @@ def lean : CodeBlockExpander if config.show.getD true then let range := Syntax.getRange? str let range := range.map (← getFileMap).utf8RangeToLspRange - pure #[← ``(Block.other (Block.lean $(quote hls) (some $(quote (← getFileName))) $(quote range)) #[Block.code $(quote str.getString)])] + + let g ← genreExpr + let bt ← blockType + let leanBlock := mkApp3 (.const ``Block.lean []) (toExpr hls) (toExpr (some ((← getFileName) : System.FilePath))) (toExpr range) + let code := mkApp2 (.const ``Block.code []) g (toExpr str.getString) + pure <| mkApp3 (.const ``Block.other []) (← genreExpr) leanBlock (← mkArrayLit bt [code]) else - pure #[] + emptyBlock finally if !config.keep.getD true then setEnv origEnv @@ -270,7 +281,7 @@ Elaborates the provided Lean term in the context of the current Verso module. -/ @[code_block_expander leanTerm] def leanTerm : CodeBlockExpander - | args, str => withoutAsync <| do + | args, str => withoutAsync <| usingExamplesEnv do let config ← LeanInlineConfig.parse.run args let altStr ← parserInputString str @@ -379,7 +390,7 @@ Elaborates the provided Lean term in the context of the current Verso module. @[role_expander lean] def leanInline : RoleExpander -- Async elab is turned off to make sure that info trees and messages are available when highlighting - | args, inlines => withoutAsync do + | args, inlines => withoutAsync <| usingExamplesEnv do let config ← LeanInlineConfig.parse.run args let #[arg] := inlines | throwError "Expected exactly one argument" @@ -496,7 +507,7 @@ Elaborates the provided term in the current Verso context, then ensures that it' -/ @[role_expander inst] def inst : RoleExpander - | args, inlines => withoutAsync <| do + | args, inlines => withoutAsync <| usingExamplesEnv do let config ← LeanBlockConfig.parse.run args let #[arg] := inlines | throwError "Expected exactly one argument" @@ -639,11 +650,12 @@ where end +deriving instance ToExpr for MessageSeverity open SubVerso.Examples.Messages in -@[code_block_expander leanOutput] -def leanOutput : CodeBlockExpander - | args, str => do +@[code_block_elab leanOutput] +def leanOutput : CodeBlockElab + | args, str => usingExamplesEnv <| do let config ← LeanOutputConfig.parser.run args let col? := (← getRef).getPos? |>.map (← getFileMap).utf8PosToLspPos |>.map (·.character) @@ -671,9 +683,16 @@ def leanOutput : CodeBlockExpander if s != sev then throwErrorAt str s!"Expected severity {sevStr s}, but got {sevStr sev}" if config.show then - let content ← `(Block.other {Block.leanOutput with data := ToJson.toJson ($(quote sev), $(quote txt), $(quote config.summarize))} #[Block.code $(quote str.getString)]) - return #[content] - else return #[] + let ⟨name, _, _⟩ := Block.leanOutput + let name := toExpr name + let noId := .app (.const ``Option.none [0]) (.const ``InternalId []) + let data ← mkAppM ``Prod.mk #[toExpr sev, ← mkAppM ``Prod.mk #[toExpr txt, toExpr config.summarize]] + let data ← mkAppM ``ToJson.toJson #[data] + let out : Expr := mkApp3 (.const ``Manual.Block.mk []) name noId data + let g ← genreExpr + let codeBlock := mkApp2 (.const ``Block.code []) g (toExpr str.getString) + return mkApp3 (.const ``Block.other []) g out (← mkArrayLit (← blockType) [codeBlock]) + else return (← emptyBlock) else let mut best : Option (Nat × String × MessageSeverity × String) := none for (sev, txt) in msgs do @@ -693,9 +712,17 @@ def leanOutput : CodeBlockExpander Log.logSilentInfo m!"Diff is {d} lines:\n{d'}" if config.show then - let content ← `(Block.other {Block.leanOutput with data := ToJson.toJson ($(quote sev), $(quote txt), $(quote config.summarize))} #[Block.code $(quote str.getString)]) - return #[content] - else return #[] + + let ⟨name, _, _⟩ := Block.leanOutput + let name := toExpr name + let noId := .app (.const ``Option.none [0]) (.const ``InternalId []) + let data ← mkAppM ``Prod.mk #[toExpr sev, ← mkAppM ``Prod.mk #[toExpr txt, toExpr config.summarize]] + let data ← mkAppM ``ToJson.toJson #[data] + let out : Expr := mkApp3 (.const ``Manual.Block.mk []) name noId data + let g ← genreExpr + let codeBlock := mkApp2 (.const ``Block.code []) g (toExpr str.getString) + return mkApp3 (.const ``Block.other []) g out (← mkArrayLit (← blockType) [codeBlock]) + else return (← emptyBlock) for (_, m) in msgs do let m := "".pushn ' ' (col?.getD 0) ++ if m.endsWith "\n" then m else m ++ "\n" @@ -770,7 +797,7 @@ def constTok [Monad m] [MonadEnv m] [MonadLiftT MetaM m] [MonadLiftT IO m] @[role_expander name] def name : RoleExpander - | args, #[arg] => do + | args, #[arg] => usingExamplesEnv do let cfg ← NameConfig.parse.run args let `(inline|code( $name:str )) := arg | throwErrorAt arg "Expected code literal with the example name" diff --git a/src/verso-manual/VersoManual/InlineLean/Env.lean b/src/verso-manual/VersoManual/InlineLean/Env.lean new file mode 100644 index 000000000..c6c646d33 --- /dev/null +++ b/src/verso-manual/VersoManual/InlineLean/Env.lean @@ -0,0 +1,55 @@ +/- +Copyright (c) 2025 Lean FRO LLC. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Author: David Thrane Christiansen +-/ +import Lean.Environment +import Lean.Exception +import Verso.Doc.Elab.Monad + +open Lean +open Verso Doc Elab DocElabM + +namespace Verso.Genre.Manual.InlineLean + +def envParameter : Name := decl_name% + +variable [Monad m] [MonadError m] [MonadReaderOf DocElabContext m] [MonadWithReaderOf DocElabContext m] +variable [MonadEnv m] [MonadFinally m] [MonadLiftT (ST IO.RealWorld) m] [MonadLiftT BaseIO m] + +structure EnvRef where + val : IO.Ref Environment + +deriving instance TypeName for EnvRef + +/-- +If a special examples environment is specified, use it. +-/ +def usingExamplesEnv (act : m α) : m α := do + if let some (envRef : EnvRef) := (← parameterValue? envParameter) then + let env ← envRef.val.get + let realEnv ← getEnv + try + modifyEnv (fun _ => env) + try + act + finally + let env' ← getEnv + envRef.val.set env' + finally + modifyEnv (fun _ => realEnv) + else act + +def getExamplesEnv : m Environment := do + if let some (envRef : EnvRef) := (← parameterValue? envParameter) then + envRef.val.get + else getEnv + +/-- +Run `act` with the examples environment set to the current environment for rollback. +-/ +def withIsolatedExamplesEnv (act : m α) : m α := do + let env ← getExamplesEnv + let ref ← IO.mkRef env + let envRef := EnvRef.mk ref + withParameter envParameter envRef act diff --git a/src/verso-manual/VersoManual/InlineLean/IO.lean b/src/verso-manual/VersoManual/InlineLean/IO.lean index f7e43498b..dfb3f0567 100644 --- a/src/verso-manual/VersoManual/InlineLean/IO.lean +++ b/src/verso-manual/VersoManual/InlineLean/IO.lean @@ -231,8 +231,7 @@ private def getSubversoDir : IO System.FilePath := do else throw <| IO.userError s!"SubVerso directory {p} not found" - -def startExample [Monad m] [MonadEnv m] [MonadError m] [MonadQuotation m] [MonadRef m] : m Unit := do +def startExample [Monad m] [MonadEnv m] [MonadError m] [MonadLiftT CoreM m] [MonadLiftT MetaM m] [MonadQuotation m] [MonadRef m] : m Unit := do match ioExampleCtx.getState (← getEnv) with | some _ => throwError "Can't initialize - already in a context" | none => @@ -240,6 +239,7 @@ def startExample [Monad m] [MonadEnv m] [MonadError m] [MonadQuotation m] [Monad modifyEnv fun env => ioExampleCtx.setState env (some {leanCodeName}) + def saveLeanCode (src : StrLit) : DocElabM Ident := do match ioExampleCtx.getState (← getEnv) with | none => throwError "Can't set Lean code - not in an IO example" @@ -284,7 +284,8 @@ def saveStderr [Monad m] [MonadEnv m] [MonadError m] (contents : StrLit) : m Uni | none => throwError "Can't save stderr - not in an IO example" | some st => match st.stderr with - | none => modifyEnv fun env => ioExampleCtx.setState env (some {st with stderr := some contents}) + | none => + modifyEnv fun env => ioExampleCtx.setState env (some {st with stderr := some contents}) | some _ => throwError "stderr already specified" @@ -398,20 +399,26 @@ where shorten (str : String) : String := if str.length < 30 then str else str.take 30 ++ "…" -def endExample (body : TSyntax `term) : DocElabM (TSyntax `term) := do +def endExampleSetup : DocElabM Unit:= do match ioExampleCtx.getState (← getEnv) with - | none => throwErrorAt body "Can't end example - never started" + | none => throwError "Can't end example setup - never started" | some {code, leanCodeName, inputFiles, outputFiles, stdin, stdout, stderr} => do - modifyEnv fun env => - ioExampleCtx.setState env none - let some leanCode := code - | throwErrorAt body "No code specified" + | throwError "No code specified" let hlLean ← check leanCode leanCodeName.getId inputFiles outputFiles stdin stdout stderr - - `(let $leanCodeName : Highlighted := $(quote hlLean) - $body) + addAndCompile <| .defnDecl { + name := leanCodeName.getId + levelParams := [], + type := .const ``Highlighted [], + value := toExpr hlLean, + hints := .opaque, + safety := .safe + } + if ((← getEnv).find? leanCodeName.getId).isSome then + return + else + throwError "Failed to save highlighted code as {leanCodeName}" structure Config where tag : Option String := none @@ -493,27 +500,86 @@ def stderr : CodeBlockExpander else pure #[] +deriving instance ToExpr for Lsp.Position +deriving instance ToExpr for Lsp.Range open IOExample in -@[code_block_expander ioLean] -def ioLean : CodeBlockExpander +@[code_block_elab ioLean] +def ioLean : CodeBlockElab | args, str => do let opts ← Config.parse.run args let x ← saveLeanCode str + let g ← DocElabM.genreExpr + if opts.show then let range := Syntax.getRange? str let range := range.map (← getFileMap).utf8RangeToLspRange - pure #[← ``(Block.other (Block.lean $x (some $(quote (← getFileName))) $(quote range)) #[Block.code $(quote str.getString)])] + --pure #[← ``(Block.other (Block.lean $x (some $(quote (← getFileName))) $(quote range)) #[Block.code $(quote str.getString)])] + let fn := some ((← getFileName) : System.FilePath) + let leanBlock : Expr := mkApp3 (.const ``Block.lean []) (.const x.getId []) (toExpr fn) (toExpr range) + return mkApp3 (.const ``Block.other []) g leanBlock (← Meta.mkArrayLit (← DocElabM.blockType) [mkApp2 (.const ``Block.code []) g (mkStrLit str.getString)]) else - pure #[] - + return mkApp2 (.const ``Block.concat []) g (← Meta.mkArrayLit (← DocElabM.blockType) []) + + +private partial def isSpecialBlock : TSyntax `block → DocElabM Bool + | `(block|```$nameStx:ident $_args*|$_contents:str```) => do + let name ← realizeGlobalConstNoOverloadWithInfo nameStx + return name ∈ [``ioLean, ``stdin, ``stdout, ``stderr, ``exampleFile, ``outputFile, ``inputFile] + | `(block|:::$_nameStx:ident $_args*{$bs*} ) => + bs.anyM isSpecialBlock + | _ => pure false + +@[directive_elab ref] +private def ref : DirectiveElab + | args, #[] => do + let (x : Ident) ← ArgParse.run (.positional' `name) args + Term.elabTerm x (some (← DocElabM.blockType)) + | _, _ => throwError "Invalid use of `ref`" + +private partial def liftSpecialBlocks (stx : Syntax) : StateT (Array (Ident × TSyntax `block)) DocElabM Syntax := + stx.replaceM fun s => + match s with + | `(block|```$nameStx:ident $_args*|$_contents:str```) => do + let name ← realizeGlobalConstNoOverloadWithInfo nameStx + if name ∈ [``ioLean, ``stdin, ``stdout, ``stderr, ``exampleFile, ``outputFile, ``inputFile] then + let x ← mkFreshUserName <| name.componentsRev.find? (· matches .str _ _) |>.getD `x + let x := mkIdentFrom s x + modify (·.push (x, ⟨s⟩)) + let b' ← `(block|:::ref $x:ident {}) + pure (some b') + else pure none + | _ => pure none + +-- TODO lift extract special blocks from nested contexts and elaborate that way open IOExample in -@[directive_expander ioExample] -def ioExample : DirectiveExpander +@[directive_elab ioExample] +def ioExample : DirectiveElab | args, blocks => do ArgParse.done.run args - startExample - let body ← blocks.mapM elabBlock - let body' ← `(Verso.Doc.Block.concat #[$body,*]) >>= endExample - pure #[body'] + let g ← DocElabM.genreExpr + try + startExample + let (blocks, lifted) ← StateT.run (blocks.mapM (liftSpecialBlocks ·.raw)) #[] + let bt ← DocElabM.blockType + -- First elaborate the special blocks, which has a side effect on the environment extension + let lifted ← lifted.mapM fun (x, b) => do + let b ← elabBlock' b >>= instantiateMVars + pure (x, b) + -- Use the extension's data to create helper definitions + endExampleSetup + -- Create definitions for the lifted blocks + for (x, b) in lifted do + addAndCompile <| .defnDecl { + name := x.getId, levelParams := [], type := bt, value := b, hints := .opaque, safety := .safe + } + -- Block until all helpers are ready before elaborating the final document + for (x, _) in lifted do + if (← getEnv).find? x.getId |>.isSome then continue else break + + let body ← blocks.mapM (elabBlock' ⟨·⟩) + return mkApp2 (.const ``Block.concat []) g (← Meta.mkArrayLit (← DocElabM.blockType) body.toList) + finally + modifyEnv fun env => + ioExampleCtx.setState env none diff --git a/src/verso-manual/VersoManual/InlineLean/Scopes.lean b/src/verso-manual/VersoManual/InlineLean/Scopes.lean index ec60a9994..1f2729ea5 100644 --- a/src/verso-manual/VersoManual/InlineLean/Scopes.lean +++ b/src/verso-manual/VersoManual/InlineLean/Scopes.lean @@ -13,7 +13,7 @@ open Lean Elab Command namespace Verso.Genre.Manual.InlineLean.Scopes initialize leanSampleScopes : Lean.EnvExtension (List Scope) ← - Lean.registerEnvExtension (pure []) + Lean.registerEnvExtension (pure []) (asyncMode := .sync) def initScopes [Monad m] [MonadEnv m] [MonadOptions m] [MonadResolveName m] : m Unit := do if leanSampleScopes.getState (← getEnv) |>.isEmpty then diff --git a/src/verso-manual/VersoManual/InlineLean/Signature.lean b/src/verso-manual/VersoManual/InlineLean/Signature.lean index e5bcf5e5c..d8adca1ee 100644 --- a/src/verso-manual/VersoManual/InlineLean/Signature.lean +++ b/src/verso-manual/VersoManual/InlineLean/Signature.lean @@ -7,6 +7,7 @@ import Lean.Elab.InfoTree.Types import Verso import VersoManual.Basic +import VersoManual.InlineLean.Env import SubVerso.Examples open SubVerso.Highlighting @@ -54,7 +55,7 @@ def SignatureConfig.parse [Monad m] [MonadError m] [MonadLiftT CoreM m] : ArgPar @[code_block_expander signature] def signature : CodeBlockExpander - | args, str => withoutAsync do + | args, str => usingExamplesEnv <| withoutAsync do let {«show»} ← SignatureConfig.parse.run args let altStr ← parserInputString str let col? := (← getRef).getPos? |>.map (← getFileMap).utf8PosToLspPos |>.map (·.character) diff --git a/src/verso-manual/VersoManual/InlineLean/SyntaxError.lean b/src/verso-manual/VersoManual/InlineLean/SyntaxError.lean index ea9af9e47..340be2f9f 100644 --- a/src/verso-manual/VersoManual/InlineLean/SyntaxError.lean +++ b/src/verso-manual/VersoManual/InlineLean/SyntaxError.lean @@ -7,6 +7,7 @@ import Lean.Elab.InfoTree.Types import Verso import VersoManual.Basic +import VersoManual.InlineLean.Env import VersoManual.InlineLean.Outputs import SubVerso.Examples @@ -130,7 +131,7 @@ def SyntaxErrorConfig.parse [Monad m] [MonadInfoTree m] [MonadLiftT CoreM m] [Mo open Lean.Parser in @[code_block_expander syntaxError] def syntaxError : CodeBlockExpander - | args, str => withoutAsync do + | args, str => withoutAsync <| usingExamplesEnv do let config ← SyntaxErrorConfig.parse.run args PointOfInterest.save (← getRef) config.name.toString diff --git a/src/verso-manual/VersoManual/Markdown.lean b/src/verso-manual/VersoManual/Markdown.lean index 1fcc962b4..d811c569f 100644 --- a/src/verso-manual/VersoManual/Markdown.lean +++ b/src/verso-manual/VersoManual/Markdown.lean @@ -7,6 +7,7 @@ Author: David Thrane Christiansen import MD4Lean import Lean.Exception +import Lean.Meta import Verso.Doc @@ -47,6 +48,11 @@ def attr' (val : Array AttrText) : Except String String := do | .error e => .error e | .ok s => pure s +def attr'' [Monad m] [MonadError m] (val : Array AttrText) : m Expr := do + match val.mapM attrText |>.map Array.toList |>.map String.join with + | .error e => throwError e + | .ok s => pure (toExpr s) + private structure MDState where /-- A mapping from document header levels to actual nesting levels -/ inHeaders : List (Nat × Nat) := [] @@ -112,6 +118,54 @@ partial def inlineFromMarkdown' : Text → Except String (Doc.Inline g) | .img .. => .error s!"Unexpected image in parsed Markdown" | .wikiLink .. => .error s!"Unexpected wiki-style link in parsed Markdown" +private def inlineType [Monad m] (g : Expr) : MDT m Expr Expr Expr := do + pure <| .app (.const ``Verso.Doc.Inline []) g + +private def blockType [Monad m] (g : Expr) : MDT m Expr Expr Expr := do + pure <| .app (.const ``Verso.Doc.Block []) g + +private def inlineArray [Monad m] [MonadLiftT MetaM m] (g : Expr) (xs : Array Expr) : MDT m Expr Expr Expr := do + let it ← inlineType g + Meta.mkArrayLit it xs.toList + +private def blockArray [Monad m] [MonadLiftT MetaM m] (g : Expr) (xs : Array Expr) : MDT m Expr Expr Expr := do + let it ← blockType g + Meta.mkArrayLit it xs.toList + +partial def inlineFromMarkdown'' [Monad m] [MonadLiftT MetaM m] [MonadQuotation m] [AddMessageContext m] [MonadError m] (g : Expr) : Text → StateT (Option String) (MDT m Expr Expr) Expr + | .normal str | .br str | .softbr str => do + (lastWord str).forM fun w => do + set (some w.toLower) + return mkApp2 (.const ``Verso.Doc.Inline.text []) g (toExpr str) + | .nullchar => throwError "Unexpected null character in parsed Markdown" + | .del _ => throwError "Unexpected strikethrough in parsed Markdown" + | .em txt => do + let txt ← txt.mapM (inlineFromMarkdown'' g) + return mkApp2 (.const ``Verso.Doc.Inline.emph []) g (← inlineArray (m := m) g txt) + | .strong txt => do + let txt ← txt.mapM (inlineFromMarkdown'' g) + return mkApp2 (.const ``Verso.Doc.Inline.bold []) g (← inlineArray (m := m) g txt) + | .a href _ _ txt => do + let txt ← txt.mapM (inlineFromMarkdown'' g) + let txt ← inlineArray (m := m) g txt + return mkApp3 (.const ``Verso.Doc.Inline.link []) g txt (← attr'' href) + | .latexMath m => do + set (none : Option String) + return mkApp3 (.const ``Verso.Doc.Inline.math []) g (toExpr Verso.Doc.MathMode.inline) (toExpr (String.join m.toList)) + | .latexMathDisplay m => do + set (none : Option String) + return mkApp3 (.const ``Verso.Doc.Inline.math []) g (toExpr Verso.Doc.MathMode.display) (toExpr (String.join m.toList)) + | .u txt => throwError "Unexpected underline around {repr txt} in parsed Markdown" + | .code strs => do + let str := String.join strs.toList + if let some f := (← read).elabInlineCode then + f (← get) str + else + return mkApp2 (.const ``Verso.Doc.Inline.code []) g (toExpr str) + | .entity ent => throwError s!"Unsupported entity {ent} in parsed Markdown" + | .img .. => throwError s!"Unexpected image in parsed Markdown" + | .wikiLink .. => throwError s!"Unexpected wiki-style link in parsed Markdown" + instance [Monad m] [MonadError m] : MonadError (MDT m b i) where throw ex := fun _ρ _σ => throw ex tryCatch act handler := fun ρ σ => tryCatch (act ρ σ) (fun e => handler e ρ σ) @@ -172,6 +226,47 @@ where if item.isTask then throwError "Tasks unsupported" else ``(Verso.Doc.ListItem.mk #[$[$(← item.contents.mapM blockFromMarkdownAux)],*]) +private partial def blockFromMarkdownAux'' [Monad m] [MonadLiftT MetaM m] [AddMessageContext m] [MonadQuotation m] [MonadError m] (g : Expr) : MD4Lean.Block → MDT m Expr Expr Expr + | .p txt => do + let inlines ← (txt.mapM (inlineFromMarkdown'' g ·)).run' none + return mkApp2 (.const ``Verso.Doc.Block.para []) g (← inlineArray g inlines) + | .blockquote bs => do + let bs ← bs.mapM (blockFromMarkdownAux'' g) + return mkApp2 (.const ``Verso.Doc.Block.blockquote []) g (← blockArray g bs) + | .code info lang _ strs => do + let info? := (attr' info).toOption + let lang? := (attr' lang).toOption + let str := String.join strs.toList + if let some f := (← read).elabBlockCode then + f info? lang? str + else + return mkApp2 (.const ``Verso.Doc.Block.code []) g (toExpr str) + | .ul _ _ items => do + let items ← items.mapM itemFromMarkdown + let itemType := .app (.const ``Verso.Doc.ListItem [0]) (← blockType g) + return mkApp2 (.const ``Verso.Doc.Block.ul []) g (← Meta.mkArrayLit itemType items.toList) + | .ol _ i _ items => do + let items ← items.mapM itemFromMarkdown + let itemType := .app (.const ``Verso.Doc.ListItem [0]) (← blockType g) + return mkApp3 (.const ``Verso.Doc.Block.ol []) g (toExpr (Int.ofNat i)) (← Meta.mkArrayLit itemType items.toList) + | .header level txt => do + match (← getHeader level) with + | .error e => throwError e + | .ok h => do + let inlines ← (txt.mapM (inlineFromMarkdown'' g ·)).run' none + h inlines + | .html .. => throwError "Unexpected literal HTML in parsed Markdown" + | .hr => throwError "Unexpected horizontal rule (thematic break) in parsed Markdown" + | .table .. => throwError "Unexpected table in parsed Markdown" +where + itemFromMarkdown [Monad m] [MonadQuotation m] [MonadError m] (item : MD4Lean.Li MD4Lean.Block) : MDT m Expr Expr Expr := do + if item.isTask then throwError "Tasks unsupported" + else + let items ← item.contents.mapM (blockFromMarkdownAux'' g) + let items ← blockArray g items + return mkApp2 (.const ``Verso.Doc.ListItem.mk [0]) (← blockType g) items + + def blockFromMarkdown [Monad m] [MonadQuotation m] [MonadError m] [AddMessageContext m] (md : MD4Lean.Block) @@ -181,11 +276,31 @@ def blockFromMarkdown [Monad m] [MonadQuotation m] [MonadError m] [AddMessageCon let ctxt := {headerHandlers := ⟨handleHeaders⟩, elabInlineCode, elabBlockCode} (·.fst) <$> blockFromMarkdownAux md ctxt {} +def blockFromMarkdown'' [Monad m] [MonadQuotation m] [MonadError m] [AddMessageContext m] [MonadLiftT MetaM m] + (genre : Expr) + (md : MD4Lean.Block) + (handleHeaders : List (Array Expr → m Expr) := []) + (elabInlineCode : Option (Option String → String → m Expr) := none) + (elabBlockCode : Option ((info? lang? : Option String) → String → m Expr) := none) : m Expr := + let ctxt := {headerHandlers := ⟨handleHeaders⟩, elabInlineCode, elabBlockCode} + (·.fst) <$> blockFromMarkdownAux'' genre md ctxt {} + + def strongEmphHeaders [Monad m] [MonadQuotation m] : List (Array Term → m Term) := [ fun stxs => ``(Verso.Doc.Block.para #[Verso.Doc.Inline.bold #[$stxs,*]]), fun stxs => ``(Verso.Doc.Block.para #[Verso.Doc.Inline.emph #[$stxs,*]]) ] +def strongEmphHeaders'' [Monad m] [MonadLiftT MetaM m] (genre : Expr) : List (Array Expr → m Expr) := + let it : Expr := .app (.const ``Verso.Doc.Inline []) genre + [ + fun (exprs : Array Expr) => do + return mkApp2 (.const ``Verso.Doc.Block.para []) genre (← Meta.mkArrayLit it [mkApp2 (.const ``Verso.Doc.Inline.bold []) genre (← Meta.mkArrayLit it exprs.toList)]), + fun (exprs : Array Expr) => do + return mkApp2 (.const ``Verso.Doc.Block.para []) genre (← Meta.mkArrayLit it [mkApp2 (.const ``Verso.Doc.Inline.emph []) genre (← Meta.mkArrayLit it exprs.toList)]) +] + + private partial def blockFromMarkdownAux' : MD4Lean.Block → MDT (Except String) (Doc.Block g) (Doc.Inline g) (Doc.Block g) | .p txt => .para <$> txt.mapM (inlineFromMarkdown' ·) | .blockquote bs => .blockquote <$> bs.mapM blockFromMarkdownAux' diff --git a/src/verso-manual/VersoManual/Table.lean b/src/verso-manual/VersoManual/Table.lean index ce262d939..a1d1cf83f 100644 --- a/src/verso-manual/VersoManual/Table.lean +++ b/src/verso-manual/VersoManual/Table.lean @@ -10,6 +10,7 @@ import Verso.Doc.ArgParse open Verso Doc Elab open Verso.Genre Manual open Verso.ArgParse +open DocElabM open Lean Elab @@ -19,7 +20,7 @@ namespace Verso.Genre.Manual inductive TableConfig.Alignment where | left | right | center -deriving ToJson, FromJson, DecidableEq, Repr, Ord +deriving ToJson, FromJson, DecidableEq, Repr, Ord, ToExpr open Syntax in open TableConfig.Alignment in @@ -149,8 +150,11 @@ where } -@[directive_expander table] -def table : DirectiveExpander +/-- +Constructs a table from a list of lists. Each item in the outer list is a row, and each inner list is the contents of the row. +-/ +@[directive_elab table] +def table : DirectiveElab | args, contents => do let cfg ← TableConfig.parse.run args -- The table should be a list of lists. Extract them! @@ -174,9 +178,17 @@ def table : DirectiveExpander if rows.any (·.size != columns) then throwErrorAt oneBlock s!"Expected all rows to have same number of columns, but got {rows.map (·.size)}" + let g ← genreExpr + let bt ← blockType + let flattened := rows.flatten - let blocks : Array (Syntax.TSepArray `term ",") ← flattened.mapM (·.mapM elabBlock) - pure #[← ``(Block.other (Block.table $(quote columns) $(quote cfg.header) $(quote cfg.name) $(quote cfg.alignment)) #[Block.ul #[$[Verso.Doc.ListItem.mk #[$blocks,*]],*]])] + let rows : Array Expr ← flattened.mapM fun r => do + let cells ← r.mapM elabBlock' + pure <| mkApp2 (.const ``Doc.ListItem.mk [0]) bt (← Meta.mkArrayLit bt cells.toList) + let noTag : Expr := .app (.const ``Option.none [0]) (.const ``Tag []) + let table ← Meta.mkAppM ``Block.table #[toExpr columns, toExpr cfg.header, toExpr cfg.name, toExpr cfg.alignment, noTag] + let ul := mkApp2 (.const ``Block.ul []) g (← Meta.mkArrayLit (.app (.const ``Doc.ListItem [0]) bt) rows.toList) + return mkApp3 (.const ``Block.other []) g table (← Meta.mkArrayLit bt [ul]) where getLi diff --git a/src/verso/Verso/Code/Highlighted.lean b/src/verso/Verso/Code/Highlighted.lean index 46f15bf0f..1954aa47f 100644 --- a/src/verso/Verso/Code/Highlighted.lean +++ b/src/verso/Verso/Code/Highlighted.lean @@ -49,6 +49,14 @@ where | .point p s => return .point p s | .tactics gs x y hl => .tactics gs x y <$> remove hl +deriving instance Lean.ToExpr for Lean.LevelMVarId +deriving instance Lean.ToExpr for Lean.Level +deriving instance Lean.ToExpr for Token.Kind +deriving instance Lean.ToExpr for Token +deriving instance Lean.ToExpr for Highlighted.Span.Kind +deriving instance Lean.ToExpr for Highlighted.Goal +deriving instance Lean.ToExpr for Highlighted + end SubVerso.Highlighting diff --git a/src/verso/Verso/Doc.lean b/src/verso/Verso/Doc.lean index 48701f366..e750d7e05 100644 --- a/src/verso/Verso/Doc.lean +++ b/src/verso/Verso/Doc.lean @@ -5,6 +5,7 @@ Author: David Thrane Christiansen -/ import Lean.Data.Json +import Lean.ToExpr import Verso.Doc.Name namespace Verso @@ -12,7 +13,7 @@ namespace Verso namespace Doc open Std (Format) -open Lean (Name Json ToJson FromJson) +open Lean (Name Json ToJson FromJson ToExpr) open Lean.Json (getObj?) structure Genre : Type 1 where @@ -34,7 +35,7 @@ instance : Repr Genre.none.PartMetadata where reprPrec e _ := nomatch e inductive MathMode where | inline | display -deriving Repr, BEq, Hashable, Ord, ToJson, FromJson +deriving Repr, BEq, Hashable, Ord, ToJson, FromJson, ToExpr private def arrayEq (eq : α → α → Bool) (xs ys : Array α) : Bool := Id.run do if h : xs.size = ys.size then diff --git a/src/verso/Verso/Doc/Concrete.lean b/src/verso/Verso/Doc/Concrete.lean index 0eaeb7a49..35d189ec0 100644 --- a/src/verso/Verso/Doc/Concrete.lean +++ b/src/verso/Verso/Doc/Concrete.lean @@ -91,7 +91,7 @@ elab "#docs" "(" genre:term ")" n:ident title:str ":=" ":::::::" text:document " let mut docState := st for hook in (← documentFinishedHooks) do - let ((), docState') ← runTermElabM fun _ => hook ⟨genre, g⟩ st' docState + let ((), docState') ← runTermElabM fun _ => hook ⟨genre, g, {}⟩ st' docState docState := docState' elab "#doc" "(" genre:term ")" title:str "=>" text:completeDocument eoi : term => open Lean Elab Term PartElabM DocElabM in do @@ -211,7 +211,7 @@ def finishDoc (genre : Term) (title : StrLit) : CommandElabM Unit:= do let mut docState := docStateExt.getState (← getEnv) let genreExpr ← runTermElabM fun _ => Term.elabTerm genre (some (.const ``Doc.Genre [])) >>= instantiateExprMVars for hook in (← documentFinishedHooks) do - let ((), docState') ← runTermElabM fun _ => hook ⟨genre, genreExpr⟩ partState docState + let ((), docState') ← runTermElabM fun _ => hook ⟨genre, genreExpr, {}⟩ partState docState docState := docState' syntax (name := replaceDoc) "#doc" "(" term ")" str "=>" : command @@ -301,7 +301,7 @@ elab (name := completeDoc) "#old_doc" "(" genre:term ")" title:str "=>" text:com elabCommand (← `($titleStr:docComment def $docName : Part $genre := $(← finished.toSyntax' genre))) let mut docState := st for hook in (← documentFinishedHooks) do - let ((), docState') ← runTermElabM fun _ => hook ⟨genre, g⟩ st' docState + let ((), docState') ← runTermElabM fun _ => hook ⟨genre, g, {}⟩ st' docState docState := docState') -- The heartbeat count is reset for each top-level Verso block because they are analogous to Lean commands. diff --git a/src/verso/Verso/Doc/Concrete/InlineString.lean b/src/verso/Verso/Doc/Concrete/InlineString.lean index 8696719ec..7f9c9e7a0 100644 --- a/src/verso/Verso/Doc/Concrete/InlineString.lean +++ b/src/verso/Verso/Doc/Concrete/InlineString.lean @@ -12,6 +12,8 @@ import Verso.Doc.Lsp import Verso.Parser import Verso.SyntaxUtils +set_option guard_msgs.diff true + namespace Verso.Doc.Concrete open Lean Parser @@ -30,9 +32,8 @@ elab_rules : term | `(inlines!%$tk$s) => do let inls ← stringToInlines s let g ← Meta.mkFreshExprMVar (some (.const ``Verso.Doc.Genre [])) - let (tms, _) ← DocElabM.run tk g {} (.init (← `(foo))) <| inls.mapM (elabInline ⟨·⟩) - elabTerm (← `(term|Inline.concat #[ $[$tms],* ] )) none - + let (tms, _) ← DocElabM.run tk g {} (.init (← `(foo))) <| inls.mapM (elabInline' ⟨·⟩) + Meta.mkAppM ``Inline.concat #[← Meta.mkArrayLit (.app (.const ``Inline []) g) tms.toList] set_option pp.rawOnError true diff --git a/src/verso/Verso/Doc/Elab.lean b/src/verso/Verso/Doc/Elab.lean index 14195fa2d..3fda927d0 100644 --- a/src/verso/Verso/Doc/Elab.lean +++ b/src/verso/Verso/Doc/Elab.lean @@ -17,6 +17,47 @@ open Verso.Syntax def throwUnexpected [Monad m] [MonadError m] (stx : Syntax) : m α := throwErrorAt stx "unexpected syntax{indentD stx}" +partial def elabInline' (inline : TSyntax `inline) : DocElabM Expr := + withRef inline <| withFreshMacroScope <| withIncRecDepth <| do + let t ← inlineType + match inline.raw with + | .missing => + Meta.mkLabeledSorry t true true + | stx@(.node _ kind _) => + let env ← getEnv + let result ← match (← liftMacroM (expandMacroImpl? env stx)) with + | some (_decl, stxNew?) => -- TODO terminfo here? Right now, we suppress most uses of it. + let stxNew ← liftMacroM <| liftExcept stxNew? + withMacroExpansionInfo stx stxNew <| + withRef stxNew <| + elabInline' ⟨stxNew⟩ + | none => + let exp ← inlineExpandersFor kind + for e in exp do + try + let termStx ← withFreshMacroScope <| e stx + return (← Term.elabTerm termStx (some t)) + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + + let exp ← inlineElabsFor kind + for e in exp do + try + let expr ← withFreshMacroScope <| e stx + return expr + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + + throwUnexpected stx + | other => + throwUnexpected other + partial def elabInline (inline : TSyntax `inline) : DocElabM (TSyntax `term) := withRef inline <| withFreshMacroScope <| withIncRecDepth <| do match inline.raw with @@ -41,40 +82,54 @@ partial def elabInline (inline : TSyntax `inline) : DocElabM (TSyntax `term) := if id == unsupportedSyntaxExceptionId then pure () else throw ex | ex => throw ex + + let exp ← inlineElabsFor kind + for e in exp do + try + let expr ← withFreshMacroScope <| e stx + let n ← defineInline expr + return mkIdentFrom stx n + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + throwUnexpected stx | other => throwUnexpected other -@[inline_expander Verso.Syntax.text] -partial def _root_.Verso.Syntax.text.expand : InlineExpander := fun x => +private def mkInline (ctor : Name) : DocElabM Expr := do + let g ← genreExpr + return .app (.const ctor []) g + +private def mkInlineArr (ctor : Name) (xs : Array Expr) : DocElabM Expr := do + let g ← genreExpr + return mkApp2 (.const ctor []) g (← Meta.mkArrayLit (← inlineType) xs.toList) + +@[inline_elab Verso.Syntax.text] +partial def _root_.Verso.Syntax.text.elab : InlineElab := fun x => match x with | `(inline| $s:str) => do - -- Erase the source locations from the string literal to prevent unwanted hover info - ``(Inline.text $(⟨deleteInfo s.raw⟩)) + return .app (← mkInline ``Inline.text) (toExpr s.getString) | _ => throwUnsupportedSyntax - where - deleteInfo : Syntax → Syntax - | .node _ k args => .node .none k (args.map deleteInfo) - | .atom _ val => .atom .none val - | .ident _ rawVal val preres => .ident .none rawVal val preres - | .missing => .missing - -@[inline_expander Verso.Syntax.linebreak] -def _root_.linebreak.expand : InlineExpander - | `(inline|line! $s:str) => - ``(Inline.linebreak $(quote s.getString)) + +@[inline_elab Verso.Syntax.linebreak] +def _root_.linebreak.elab : InlineElab + | `(inline|line! $s:str) => do + return .app (← mkInline ``Inline.linebreak) (toExpr s.getString) | _ => throwUnsupportedSyntax -@[inline_expander Verso.Syntax.emph] -def _root_.Verso.Syntax.emph.expand : InlineExpander +@[inline_elab Verso.Syntax.emph] +def _root_.Verso.Syntax.emph.elab : InlineElab | `(inline| _[ $args* ]) => do - ``(Inline.emph #[$[$(← args.mapM elabInline)],*]) + mkInlineArr ``Inline.emph (← args.mapM elabInline') | _ => throwUnsupportedSyntax -@[inline_expander Verso.Syntax.bold] -def _root_.Verso.Syntax.bold.expand : InlineExpander +@[inline_elab Verso.Syntax.bold] +def _root_.Verso.Syntax.bold.elab : InlineElab | `(inline| *[ $args* ]) => do - ``(Inline.bold #[$[$(← args.mapM elabInline)],*]) + mkInlineArr ``Inline.bold (← args.mapM elabInline') | _ => throwUnsupportedSyntax def parseArgVal (val : TSyntax `arg_val) : DocElabM ArgVal := do @@ -119,44 +174,60 @@ def appFallback f (.node .none nullKind <| arrArg ++ argStx) return ⟨appStx⟩ + + open Lean.Parser.Term in -@[inline_expander Verso.Syntax.role] -def _root_.Verso.Syntax.role.expand : InlineExpander +@[inline_elab Verso.Syntax.role] +def _root_.Verso.Syntax.role.elab : InlineElab | inline@`(inline| role{$name $args*} [$subjects*]) => do withRef inline <| withFreshMacroScope <| withIncRecDepth <| do - let ⟨genre, _⟩ ← readThe DocElabContext + let ⟨genre, _, _⟩ ← readThe DocElabContext + let it ← inlineType let resolvedName ← realizeGlobalConstNoOverloadWithInfo name let exp ← roleExpandersFor resolvedName + let exp' ← roleElabsFor resolvedName let argVals ← parseArgs args - if exp.isEmpty then + if exp.isEmpty && exp'.isEmpty then -- If no expanders are registered, then try elaborating just as a -- function application node - return ← appFallback inline name resolvedName argVals subjects + let appStx ← appFallback inline name resolvedName argVals subjects + return (← Term.elabTerm appStx (some it)) + for e in exp do try let termStxs ← withFreshMacroScope <| e argVals subjects let termStxs ← termStxs.mapM fun t => (``(($t : Inline $(⟨genre⟩)))) - return (← ``(Inline.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) + return (← Term.elabTerm (← ``(Inline.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) (some it)) catch | ex@(.internal id) => if id == unsupportedSyntaxExceptionId then pure () else throw ex | ex => throw ex + + for e in exp' do + try + return (← withFreshMacroScope <| e argVals subjects) + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + throwUnsupportedSyntax | _ => throwUnsupportedSyntax -@[inline_expander Verso.Syntax.link] -def _root_.Verso.Syntax.link.expand : InlineExpander +@[inline_elab Verso.Syntax.link] +def _root_.Verso.Syntax.link.expand : InlineElab | `(inline| link[ $txt* ] $dest:link_target) => do - let url : TSyntax `term ← + let url : Expr ← match dest with | `(link_target| ( $url )) => - pure (↑ url) + Term.elabTerm (↑ url) none | `(link_target| [ $ref ]) => do -- Round-trip through quote to get rid of source locations, preventing unwanted IDE info - addLinkRef ref + Term.elabTerm (← addLinkRef ref) none | _ => throwErrorAt dest "Couldn't parse link destination" - ``(Inline.link #[$[$(← txt.mapM elabInline)],*] $url) + return .app (← mkInlineArr ``Inline.link (← txt.mapM elabInline')) url | _ => throwUnsupportedSyntax @[inline_expander Verso.Syntax.footnote] @@ -207,6 +278,50 @@ def decorateClosing : TSyntax `block → DocElabM Unit | `(block|%%%%$s $_* %%%%$e) => closes s e | _ => pure () +open Lean.Elab.Term in +partial def elabBlock' (block : TSyntax `block) : DocElabM Expr := + withTraceNode `Elab.Verso.block (fun _ => pure m!"Block {block}") <| + withRef block <| withFreshMacroScope <| withIncRecDepth <| do + let g ← genreExpr + let type : Expr := .app (.const ``Doc.Block []) g + decorateClosing block + match block.raw with + | .missing => + Meta.mkSorry type true + | stx@(.node _ kind _) => + let env ← getEnv + let result ← match (← liftMacroM (expandMacroImpl? env stx)) with + | some (_decl, stxNew?) => -- TODO terminfo here? Right now, we suppress most uses of it. + let stxNew ← liftMacroM <| liftExcept stxNew? + withMacroExpansionInfo stx stxNew <| + withRef stxNew <| + elabBlock' ⟨stxNew⟩ + | none => + let exp ← blockExpandersFor kind + for e in exp do + try + let termStx ← withFreshMacroScope <| e stx + return (← elabTerm termStx (some type)) + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then continue + else throw ex + | ex => throw ex + let exp ← blockElabsFor kind + for e in exp do + try + let b ← withFreshMacroScope <| e stx + return b + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then continue + else throw ex + | ex => throw ex + + throwUnexpected block + | _ => + throwUnexpected block + open Lean.Elab.Term in partial def elabBlock (block : TSyntax `block) : DocElabM (TSyntax `term) := withTraceNode `Elab.Verso.block (fun _ => pure m!"Block {block}") <| @@ -234,6 +349,17 @@ partial def elabBlock (block : TSyntax `block) : DocElabM (TSyntax `term) := if id == unsupportedSyntaxExceptionId then continue else throw ex | ex => throw ex + let exp ← blockElabsFor kind + for e in exp do + try + let b ← withFreshMacroScope <| e stx + let n ← defineBlock b + return mkIdentFrom stx n + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then continue + else throw ex + | ex => throw ex throwUnexpected block | _ => throwUnexpected block @@ -260,8 +386,8 @@ where fallback : PartElabM Unit := do if (← getThe PartElabM.State).partContext.priorParts.size > 0 then throwErrorAt cmd "Unexpected block" - let blk ← liftDocElabM <| elabBlock cmd - addBlock blk + let blk ← liftDocElabM <| elabBlock' cmd + addBlockExpr blk @[part_command Verso.Syntax.footnote_ref] partial def _root_.Verso.Syntax.footnote_ref.command : PartCommand @@ -354,55 +480,61 @@ def includeSection : PartCommand where resolved id := mkIdentFrom id <$> realizeGlobalConstNoOverloadWithInfo (mkIdentFrom id (docName id.getId)) -@[block_expander Verso.Syntax.block_role] -def _root_.Verso.Syntax.block_role.expand : BlockExpander := fun block => +@[block_elab Verso.Syntax.block_role] +def _root_.Verso.Syntax.block_role.elab : BlockElab := fun block => match block with | `(block|block_role{$name $args*}) => do withTraceNode `Elab.Verso.block (fun _ => pure m!"Block role {name}") <| withRef block <| withFreshMacroScope <| withIncRecDepth <| do - let ⟨genre, _⟩ ← readThe DocElabContext + let ⟨genre, _, _⟩ ← readThe DocElabContext let resolvedName ← realizeGlobalConstNoOverloadWithInfo name let exp ← blockRoleExpandersFor resolvedName + let elabs ← blockRoleElabsFor resolvedName let argVals ← parseArgs args - if exp.isEmpty then - return ← appFallback block name resolvedName argVals none + if exp.isEmpty && elabs.isEmpty then + return (← Term.elabTerm (← appFallback block name resolvedName argVals none) (some (← blockType))) for e in exp do try let termStxs ← withFreshMacroScope <| e argVals #[] - return (← ``(Block.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) + return (← Term.elabTerm (← ``(Block.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) (some (← blockType))) catch | ex@(.internal id) => if id == unsupportedSyntaxExceptionId then pure () else throw ex | ex => throw ex + for e in elabs do + try + return (← withFreshMacroScope <| e argVals #[]) + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + throwUnsupportedSyntax | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.para] -partial def _root_.Verso.Syntax.para.expand : BlockExpander +@[block_elab Verso.Syntax.para] +partial def _root_.Verso.Syntax.para.elab : BlockElab | `(block| para[ $args:inline* ]) => do - let ⟨genre, _⟩ ← readThe DocElabContext - ``(Block.para (genre := $(⟨genre⟩)) #[$[$(← args.mapM elabInline)],*]) + Meta.mkAppM ``Block.para #[← Meta.mkArrayLit (← inlineType) (← args.mapM elabInline').toList] | _ => throwUnsupportedSyntax - -def elabLi (block : Syntax) : DocElabM (Syntax × TSyntax `term) := +def elabLi (block : Syntax) : DocElabM (Syntax × Expr) := withRef block <| match block with | `(list_item|*%$dot $contents:block*) => do - let ⟨genre, _⟩ ← readThe DocElabContext - let item ← ``(ListItem.mk (α := Block $(⟨genre⟩)) #[$[$(← contents.mapM elabBlock)],*]) + let item ← Meta.mkAppM ``ListItem.mk #[(← Meta.mkArrayLit (← DocElabM.blockType) (← contents.mapM elabBlock').toList)] pure (dot, item) | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.ul] -def _root_.Verso.Syntax.ul.expand : BlockExpander +@[block_elab Verso.Syntax.ul] +def _root_.Verso.Syntax.ul.elab : BlockElab | `(block|ul{$itemStxs*}) => do - let ⟨genre, _⟩ ← readThe DocElabContext let mut bullets : Array Syntax := #[] - let mut items : Array (TSyntax `term) := #[] + let mut items : Array Expr := #[] for i in itemStxs do let (b, item) ← elabLi i bullets := bullets.push b @@ -410,16 +542,16 @@ def _root_.Verso.Syntax.ul.expand : BlockExpander let info := DocListInfo.mk bullets itemStxs for b in bullets do pushInfoLeaf <| .ofCustomInfo {stx := b, value := Dynamic.mk info} - ``(Block.ul (genre := $(⟨genre⟩)) #[$items,*]) + let t ← Meta.mkAppM ``ListItem #[← DocElabM.blockType] + Meta.mkAppM ``Block.ul #[← Meta.mkArrayLit t items.toList] | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.ol] -def _root_.Verso.Syntax.ol.expand : BlockExpander +@[block_elab Verso.Syntax.ol] +def _root_.Verso.Syntax.ol.elab : BlockElab | `(block|ol($start:num){$itemStxs*}) => do - let ⟨genre, _⟩ ← readThe DocElabContext let mut bullets : Array Syntax := #[] - let mut items : Array (TSyntax `term) := #[] + let mut items : Array Expr := #[] for i in itemStxs do let (b, item) ← elabLi i bullets := bullets.push b @@ -427,26 +559,29 @@ def _root_.Verso.Syntax.ol.expand : BlockExpander let info := DocListInfo.mk bullets itemStxs for b in bullets do pushInfoLeaf <| .ofCustomInfo {stx := b, value := Dynamic.mk info} - ``(Block.ol (genre := $(⟨genre⟩)) $start #[$items,*]) + let t ← Meta.mkAppM ``ListItem #[← DocElabM.blockType] + Meta.mkAppM ``Block.ol #[toExpr (Int.ofNat start.getNat), ← Meta.mkArrayLit t items.toList] | _ => throwUnsupportedSyntax -def elabDesc (block : Syntax) : DocElabM (Syntax × TSyntax `term) := +def elabDesc (block : Syntax) : DocElabM (Syntax × Expr) := withRef block <| match block with | `(desc_item|:%$colon $dts* => $dds*) => do - let ⟨genre, _⟩ ← readThe DocElabContext - let item ← ``(DescItem.mk (α := Inline $(⟨genre⟩)) (β := Block $(⟨genre⟩)) #[$[$(← dts.mapM elabInline)],*] #[$[$(← dds.mapM elabBlock)],*]) + let g ← genreExpr + let it := .app (.const ``Inline []) g + let bt := .app (.const ``Block []) g + let item := mkApp4 (.const ``DescItem.mk [0, 0]) it bt (← Meta.mkArrayLit it (← dts.mapM elabInline').toList) (← Meta.mkArrayLit bt (← dds.mapM elabBlock').toList) pure (colon, item) | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.dl] -def _root_.Verso.Syntax.dl.expand : BlockExpander +@[block_elab Verso.Syntax.dl] +def _root_.Verso.Syntax.dl.elab : BlockElab | `(block|dl{$itemStxs*}) => do - let ⟨genre, _⟩ ← readThe DocElabContext + let g ← genreExpr let mut colons : Array Syntax := #[] - let mut items : Array (TSyntax `term) := #[] + let mut items : Array Expr := #[] for i in itemStxs do let (b, item) ← elabDesc i colons := colons.push b @@ -454,57 +589,91 @@ def _root_.Verso.Syntax.dl.expand : BlockExpander let info := DocListInfo.mk colons itemStxs for b in colons do pushInfoLeaf <| .ofCustomInfo {stx := b, value := Dynamic.mk info} - ``(Block.dl (genre := $(⟨genre⟩)) #[$[$items],*]) + let it := .app (.const ``Inline []) g + let bt := .app (.const ``Block []) g + let itemType := mkApp2 (.const ``DescItem [0, 0]) it bt + return mkApp2 (.const ``Block.dl []) g (← Meta.mkArrayLit itemType items.toList) | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.blockquote] -def _root_.Verso.Syntax.blockquote.expand : BlockExpander +@[block_elab Verso.Syntax.blockquote] +def _root_.Verso.Syntax.blockquote.elab : BlockElab | `(block|> $innerBlocks*) => do - ``(Block.blockquote #[$[$(← innerBlocks.mapM elabBlock)],*]) + let g ← genreExpr + let bt ← blockType + return mkApp2 (.const ``Block.blockquote []) g (← Meta.mkArrayLit bt (← innerBlocks.mapM elabBlock').toList) | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.codeblock] -def _root_.Verso.Syntax.codeblock.expand : BlockExpander +@[block_elab Verso.Syntax.codeblock] +def _root_.Verso.Syntax.codeblock.expand : BlockElab | `(block|``` $nameStx:ident $argsStx* | $contents:str ```) => do - let ⟨genre, _⟩ ← readThe DocElabContext + let g ← genreExpr let name ← realizeGlobalConstNoOverloadWithInfo nameStx - let exp ← codeBlockExpandersFor name -- TODO typed syntax here let args ← parseArgs <| argsStx.map (⟨·⟩) + let bt ← blockType + + let exp ← codeBlockExpandersFor name for e in exp do try let termStxs ← withFreshMacroScope <| e args contents - return (← ``(Block.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) + let blks ← termStxs.mapM (Term.elabTerm · (some bt)) + return mkApp2 (.const ``Block.concat []) g (← Meta.mkArrayLit bt blks.toList) catch | ex@(.internal id) => if id == unsupportedSyntaxExceptionId then pure () else throw ex | ex => throw ex + + let exp ← codeBlockElabsFor name + for e in exp do + try + let blk ← withFreshMacroScope <| e args contents + return blk + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + throwUnsupportedSyntax | `(block|``` | $contents:str ```) => do - ``(Block.code $(quote contents.getString)) + let g ← genreExpr + return mkApp2 (.const ``Block.code []) g (toExpr contents.getString) | _ => throwUnsupportedSyntax -@[block_expander Verso.Syntax.directive] -def _root_.Verso.Syntax.directive.expand : BlockExpander +@[block_elab Verso.Syntax.directive] +def _root_.Verso.Syntax.directive.expand : BlockElab | `(block| ::: $nameStx:ident $argsStx* { $contents:block* } ) => do - let ⟨genre, _⟩ ← readThe DocElabContext + + let ⟨genre, _, _⟩ ← readThe DocElabContext let name ← realizeGlobalConstNoOverloadWithInfo nameStx - let exp ← directiveExpandersFor name let args ← parseArgs argsStx + + let exp ← directiveExpandersFor name for e in exp do try let termStxs ← withFreshMacroScope <| e args contents - return (← ``(Block.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) + return (← Term.elabTerm (← ``(Block.concat (genre := $(⟨genre⟩)) #[$[$termStxs],*])) (← blockType)) + catch + | ex@(.internal id) => + if id == unsupportedSyntaxExceptionId then pure () + else throw ex + | ex => throw ex + + let exp ← directiveElabsFor name + for e in exp do + try + return (← withFreshMacroScope <| e args contents) catch | ex@(.internal id) => if id == unsupportedSyntaxExceptionId then pure () else throw ex | ex => throw ex + throwUnsupportedSyntax | _ => throwUnsupportedSyntax diff --git a/src/verso/Verso/Doc/Elab/Monad.lean b/src/verso/Verso/Doc/Elab/Monad.lean index 6c5b75a9c..810a4d27f 100644 --- a/src/verso/Verso/Doc/Elab/Monad.lean +++ b/src/verso/Verso/Doc/Elab/Monad.lean @@ -91,9 +91,54 @@ def headerStxToString (env : Environment) : Syntax → String | headerStx => dbg_trace "didn't understand {headerStx} for string" "" +/-- Parameters that have dynamic extent with respect to Verso elaboration. -/ +structure DocElabParameters where + parameters : NameMap Dynamic := {} + structure DocElabContext where genreSyntax : Syntax genre : Expr + parameters : DocElabParameters := {} + +section +variable [Monad m] [MonadError m] [MonadReaderOf DocElabContext m] [MonadWithReaderOf DocElabContext m] [TypeName α] + +def withModifiedParameter (x : Name) (f : α → α) (act : m β) : m β := do + let ⟨params⟩ := (← read).parameters + if let some v := params.find? x then + if let some v := v.get? α then + withReader ({ · with parameters := ⟨params.insert x (.mk (f v))⟩ }) act + else throwError m!"Internal error: expected a {TypeName.typeName α} for {x}, but got a {v.typeName}" + else throwError m!"Internal error: no value for parameter {x} of type {TypeName.typeName α}" + +def withParameter (x : Name) (value : α) (act : m β) : m β := do + let ⟨params⟩ := (← read).parameters + if let some v := params.find? x then + if v.typeName ≠ TypeName.typeName α then + throwError m!"Internal error: expected a {TypeName.typeName α} for {x}, but a {v.typeName} was already present." + withReader ({ · with parameters := ⟨params.insert x (.mk value)⟩ }) act + +def parameterValue! (x : Name) : m α := do + let ⟨params⟩ := (← read).parameters + if let some v := params.find? x then + if let some v := v.get? α then + return v + else + throwError m!"Internal error: expected a {TypeName.typeName α} for {x}, but found a {v.typeName}." + else + throwError m!"Internal error: no value for {x}. Expected a {TypeName.typeName α} but no value was present." + +def parameterValue? (x : Name) : m (Option α) := do + let ⟨params⟩ := (← read).parameters + if let some v := params.find? x then + if let some v := v.get? α then + return (some v) + else + throwError m!"Internal error: expected a {TypeName.typeName α} for {x}, but found a {v.typeName}." + else + return none + +end /-- References that must be local to the current blob of concrete document syntax -/ structure DocDef (α : Type) where @@ -281,8 +326,11 @@ def PartElabM.State.init (title : Syntax) (expandedTitle : Option (String × Arr def PartElabM (α : Type) : Type := ReaderT DocElabContext (StateT DocElabM.State (StateT PartElabM.State TermElabM)) α -def PartElabM.run (genreSyntax : Syntax) (genre : Expr) (st : DocElabM.State) (st' : PartElabM.State) (act : PartElabM α) : TermElabM (α × DocElabM.State × PartElabM.State) := do - let ((res, st), st') ← act ⟨genreSyntax, genre⟩ st st' +def PartElabM.run (genreSyntax : Syntax) (genre : Expr) + (st : DocElabM.State) (st' : PartElabM.State) + (act : PartElabM α) + (params : DocElabParameters := {}) : TermElabM (α × DocElabM.State × PartElabM.State) := do + let ((res, st), st') ← act ⟨genreSyntax, genre, params⟩ st st' pure (res, st, st') instance : Alternative PartElabM := inferInstanceAs <| Alternative (ReaderT DocElabContext (StateT DocElabM.State (StateT PartElabM.State TermElabM))) @@ -316,13 +364,19 @@ instance : MonadWithReaderOf Term.Context PartElabM := inferInstanceAs <| MonadW instance : MonadReaderOf DocElabContext PartElabM := inferInstanceAs <| MonadReaderOf DocElabContext (ReaderT DocElabContext (StateT DocElabM.State (StateT PartElabM.State TermElabM))) +instance : MonadWithReaderOf DocElabContext PartElabM := inferInstanceAs <| MonadWithReaderOf DocElabContext (ReaderT DocElabContext (StateT DocElabM.State (StateT PartElabM.State TermElabM))) + + def PartElabM.withFileMap (fileMap : FileMap) (act : PartElabM α) : PartElabM α := fun ρ ρ' σ ctxt σ' mctxt rw cctxt => act ρ ρ' σ ctxt σ' mctxt rw {cctxt with fileMap := fileMap} def DocElabM (α : Type) : Type := ReaderT DocElabContext (ReaderT PartElabM.State (StateT DocElabM.State TermElabM)) α -def DocElabM.run (genreSyntax : Syntax) (genre : Expr) (st : DocElabM.State) (st' : PartElabM.State) (act : DocElabM α) : TermElabM (α × DocElabM.State) := do - StateT.run (act ⟨genreSyntax, genre⟩ st') st +def DocElabM.run (genreSyntax : Syntax) (genre : Expr) + (st : DocElabM.State) (st' : PartElabM.State) + (act : DocElabM α) + (params : DocElabParameters := {}) : TermElabM (α × DocElabM.State) := do + StateT.run (act ⟨genreSyntax, genre, params⟩ st') st instance : Inhabited (DocElabM α) := ⟨fun _ _ _ => default⟩ @@ -375,6 +429,8 @@ instance : MonadWithReaderOf Term.Context DocElabM := inferInstanceAs <| MonadWi instance : MonadReaderOf DocElabContext DocElabM := inferInstanceAs <| MonadReaderOf DocElabContext (ReaderT DocElabContext (ReaderT PartElabM.State (StateT DocElabM.State TermElabM))) +instance : MonadWithReaderOf DocElabContext DocElabM := inferInstanceAs <| MonadWithReaderOf DocElabContext (ReaderT DocElabContext (ReaderT PartElabM.State (StateT DocElabM.State TermElabM))) + instance : MonadReaderOf PartElabM.State DocElabM := inferInstanceAs <| MonadReaderOf PartElabM.State (ReaderT DocElabContext (ReaderT PartElabM.State (StateT DocElabM.State TermElabM))) def DocElabM.withFileMap (fileMap : FileMap) (act : DocElabM α) : DocElabM α := @@ -386,7 +442,7 @@ instance : MonadRecDepth DocElabM where getMaxRecDepth := fun _ _ st' => do return (← MonadRecDepth.getMaxRecDepth, st') def PartElabM.liftDocElabM (act : DocElabM α) : PartElabM α := do - let ⟨gStx, g⟩ ← readThe DocElabContext + let ⟨gStx, g, _⟩ ← readThe DocElabContext let (out, st') ← act.run gStx g (← getThe DocElabM.State) (← getThe PartElabM.State) set st' pure out @@ -410,14 +466,78 @@ def findLinksAndNotes : Expr → MetaM (Array (Expr × Expr)) | .mdata _ e | .proj _ _ e => findLinksAndNotes e | .sort .. | .fvar .. | .bvar .. | .const .. | .lit .. => pure #[] +def DocElabM.genreExpr : DocElabM Expr := do + let ⟨_, g, _⟩ ← read + return g + +def DocElabM.blockType : DocElabM Expr := do + let g ← genreExpr + return .app (.const ``Doc.Block []) g + +def DocElabM.blockArray (blocks : Array Expr) : DocElabM Expr := do + let bt ← blockType + Meta.mkArrayLit bt blocks.toList + + +def DocElabM.inlineType : DocElabM Expr := do + let g ← genreExpr + return .app (.const ``Doc.Inline []) g + +def DocElabM.inlineArray (inlines : Array Expr) : DocElabM Expr := do + let bt ← inlineType + Meta.mkArrayLit bt inlines.toList + +def DocElabM.emptyBlock : DocElabM Expr := do + pure <| mkApp2 (.const ``Block.concat []) (← genreExpr) (← Meta.mkArrayLit (← blockType) []) + open Lean Meta Elab Term in -def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := withRef block <| do - let ⟨_, g⟩ ← readThe DocElabContext +def DocElabM.defineInline (inline : Expr) : DocElabM Name := do + let g ← genreExpr + + let n ← mkFreshUserName `inline + + let type : Expr := .app (.const ``Doc.Inline []) g + let t ← ensureHasType (some type) inline + let t ← instantiateMVars t + let links ← findLinksAndNotes t + let t ← links.foldrM (init := t) fun (mv, mvty) t => + (.lam `inst mvty · .instImplicit) <$> t.abstractM #[mv] + let t ← instantiateMVars t + + let xs ← Term.addAutoBoundImplicits #[] none + let type ← instantiateMVars type + let type ← mkForallFVars (links.map (·.1)) type (binderInfoForMVars := .instImplicit) + let type ← mkForallFVars xs type + let type ← levelMVarToParam type + let usedParams := collectLevelParams {} type |>.params + + match sortDeclLevelParams [] [] usedParams with + | Except.error msg => throwError msg + | Except.ok levelParams => + synthesizeSyntheticMVarsNoPostponing + let t ← instantiateMVars t + let type ← instantiateMVars type + let t ← ensureHasType (some type) t + let decl := Declaration.defnDecl { + name := n, + levelParams := levelParams, + type := type, + value := t, + hints := .abbrev, + safety := .safe + } + Term.ensureNoUnassignedMVars decl + addAndCompile decl + return n + +open Lean Meta Elab Term in +def DocElabM.defineBlock (block : Expr) : DocElabM Name := do + let g ← genreExpr let n ← mkFreshUserName `block let type : Expr := .app (.const ``Doc.Block []) g - let t ← elabTerm block (some type) + let t ← ensureHasType (some type) block let t ← instantiateMVars t let links ← findLinksAndNotes t let t ← links.foldrM (init := t) fun (mv, mvty) t => @@ -432,7 +552,7 @@ def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := withRef block let usedParams := collectLevelParams {} type |>.params match sortDeclLevelParams [] [] usedParams with - | Except.error msg => throwErrorAt block msg + | Except.error msg => throwError msg | Except.ok levelParams => synthesizeSyntheticMVarsNoPostponing let t ← instantiateMVars t @@ -448,9 +568,22 @@ def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := withRef block } Term.ensureNoUnassignedMVars decl addAndCompile decl + return n + +open Lean Meta Elab Term in +def PartElabM.addBlockExpr (block : Expr) : PartElabM Unit := do + let n ← DocElabM.defineBlock block modifyThe State fun st => { st with partContext.blocks := st.partContext.blocks.push (mkIdent n) } +open Lean Meta Elab Term in +def PartElabM.addBlock (block : TSyntax `term) : PartElabM Unit := withRef block <| do + let g ← DocElabM.genreExpr + let type : Expr := .app (.const ``Doc.Block []) g + let t ← elabTerm block (some type) + addBlockExpr t + + def PartElabM.addPart (finished : FinishedPart) : PartElabM Unit := modifyThe State fun st => {st with partContext.priorParts := st.partContext.priorParts.push finished} @@ -489,7 +622,7 @@ def DocElabM.addLinkRef (refName : TSyntax `str) : DocElabM (TSyntax `term) := d def PartElabM.addFootnoteDef (refName : TSyntax `str) (content : Array (TSyntax `term)) : PartElabM Unit := do let strName := refName.getString let docName ← currentDocName - let ⟨_, genre⟩ ← readThe DocElabContext + let ⟨_, genre, _⟩ ← readThe DocElabContext match (← getThe State).footnoteDefs[strName]? with | none => let t := mkApp3 (.const ``HasNote []) (toExpr strName) (toExpr docName) genre @@ -512,7 +645,7 @@ def PartElabM.addFootnoteDef (refName : TSyntax `str) (content : Array (TSyntax def DocElabM.addFootnoteRef (refName : TSyntax `str) : DocElabM (TSyntax `term) := do let strName := refName.getString - let ⟨genre, _⟩ ← readThe DocElabContext + let ⟨genre, _, _⟩ ← readThe DocElabContext match (← getThe State).footnoteRefs[strName]? with | none => modifyThe State fun st => {st with footnoteRefs := st.footnoteRefs.insert strName ⟨#[refName]⟩} @@ -549,6 +682,46 @@ def closes (openTok closeTok : Syntax) : DocElabM Unit := do let lineStr := if lineStr.startsWith "`" || lineStr.endsWith "`" then " " ++ lineStr ++ " " else lineStr Hover.addCustomHover closeTok (.markdown s!"Closes line {line + 1}: ``````````{lineStr}``````````") +/-- +Elaborates those values selected by `prioritize`, then runs `between`, and finally elaborates the +rest. + +They are returned in their original order. +-/ +def prioritizedElab [Monad m] + (prioritize : α → m Bool) (act : α → m β) + (between : m Unit) + (xs : Array α) : m (Array β) := do + let mut out := #[] + let mut later := #[] + + for h:i in [0:xs.size] do + let x := xs[i] + if ← prioritize x then + out := out.push (i, (← act x)) + else later := later.push (i, x) + + between + + for (i, x) in later do + out := out.push (i, (← act x)) + + out := out.qsort (fun (i, _) (j, _) => i < j) + return out.map (·.2) + + +abbrev InlineElab := Syntax → DocElabM Expr + +initialize inlineElabAttr : KeyedDeclsAttribute InlineElab ← + mkDocExpanderAttribute `inline_elab ``InlineElab "Indicates that this function elaborates inline elements of a given name" `inlineElabAttr + +unsafe def inlineElabsForUnsafe (x : Name) : DocElabM (Array InlineElab) := do + let expanders := inlineElabAttr.getEntries (← getEnv) x + return expanders.map (·.value) |>.toArray + +@[implemented_by inlineElabsForUnsafe] +opaque inlineElabsFor (x : Name) : DocElabM (Array InlineElab) + abbrev InlineExpander := Syntax → DocElabM (TSyntax `term) @@ -563,6 +736,18 @@ unsafe def inlineExpandersForUnsafe (x : Name) : DocElabM (Array InlineExpander) opaque inlineExpandersFor (x : Name) : DocElabM (Array InlineExpander) +abbrev BlockElab := Syntax → DocElabM Expr + +initialize blockElabAttr : KeyedDeclsAttribute BlockElab ← + mkDocExpanderAttribute `block_elab ``BlockElab "Indicates that this function expands block elements of a given name" `blockElabAttr + +unsafe def blockElabsForUnsafe (x : Name) : DocElabM (Array BlockElab) := do + let expanders := blockElabAttr.getEntries (← getEnv) x + return expanders.map (·.value) |>.toArray + +@[implemented_by blockElabsForUnsafe] +opaque blockElabsFor (x : Name) : DocElabM (Array BlockElab) + abbrev BlockExpander := Syntax → DocElabM (TSyntax `term) @@ -591,6 +776,19 @@ unsafe def partCommandsForUnsafe (x : Name) : PartElabM (Array PartCommand) := d opaque partCommandsFor (x : Name) : PartElabM (Array PartCommand) +abbrev RoleElab := Array Arg → TSyntaxArray `inline → DocElabM Expr + +initialize roleElabAttr : KeyedDeclsAttribute RoleElab ← + mkDocExpanderAttribute `role_elab ``RoleElab "Indicates that this function is used to elaborate a given role" `roleElabAttr + +unsafe def roleElabsForUnsafe (x : Name) : DocElabM (Array RoleElab) := do + let elabs := roleElabAttr.getEntries (← getEnv) x + return elabs.map (·.value) |>.toArray + +@[implemented_by roleElabsForUnsafe] +opaque roleElabsFor (x : Name) : DocElabM (Array RoleElab) + + abbrev RoleExpander := Array Arg → TSyntaxArray `inline → DocElabM (Array (TSyntax `term)) initialize roleExpanderAttr : KeyedDeclsAttribute RoleExpander ← @@ -604,6 +802,19 @@ unsafe def roleExpandersForUnsafe (x : Name) : DocElabM (Array RoleExpander) := opaque roleExpandersFor (x : Name) : DocElabM (Array RoleExpander) +abbrev CodeBlockElab := Array Arg → TSyntax `str → DocElabM Expr + +initialize codeBlockElabAttr : KeyedDeclsAttribute CodeBlockElab ← + mkDocExpanderAttribute `code_block_elab ``CodeBlockElab "Indicates that this function is used to elaborate a given code block" `codeBlockElabAttr + +unsafe def codeBlockElabsForUnsafe (x : Name) : DocElabM (Array CodeBlockElab) := do + let elabs := codeBlockElabAttr.getEntries (← getEnv) x + return elabs.map (·.value) |>.toArray + +@[implemented_by codeBlockElabsForUnsafe] +opaque codeBlockElabsFor (x : Name) : DocElabM (Array CodeBlockElab) + + abbrev CodeBlockExpander := Array Arg → TSyntax `str → DocElabM (Array (TSyntax `term)) initialize codeBlockExpanderAttr : KeyedDeclsAttribute CodeBlockExpander ← @@ -617,6 +828,18 @@ unsafe def codeBlockExpandersForUnsafe (x : Name) : DocElabM (Array CodeBlockExp opaque codeBlockExpandersFor (x : Name) : DocElabM (Array CodeBlockExpander) +abbrev DirectiveElab := Array Arg → TSyntaxArray `block → DocElabM Expr + +initialize directiveElabAttr : KeyedDeclsAttribute DirectiveElab ← + mkDocExpanderAttribute `directive_elab ``DirectiveElab "Indicates that this function is used to elaborate a given directive" `directiveElabAttr + +unsafe def directiveElabsForUnsafe (x : Name) : DocElabM (Array DirectiveElab) := do + let expanders := directiveElabAttr.getEntries (← getEnv) x + return expanders.map (·.value) |>.toArray + +@[implemented_by directiveElabsForUnsafe] +opaque directiveElabsFor (x : Name) : DocElabM (Array DirectiveElab) + abbrev DirectiveExpander := Array Arg → TSyntaxArray `block → DocElabM (Array (TSyntax `term)) @@ -631,6 +854,19 @@ unsafe def directiveExpandersForUnsafe (x : Name) : DocElabM (Array DirectiveExp opaque directiveExpandersFor (x : Name) : DocElabM (Array DirectiveExpander) +abbrev BlockRoleElab := Array Arg → Array Syntax → DocElabM Expr + +initialize blockRoleElabAttr : KeyedDeclsAttribute BlockRoleElab ← + mkDocExpanderAttribute `block_role_elab ``BlockRoleElab "Indicates that this function is used to implement a given blockRole" `blockRoleElabAttr + +unsafe def blockRoleElabsForUnsafe (x : Name) : DocElabM (Array BlockRoleElab) := do + let elabs := blockRoleElabAttr.getEntries (← getEnv) x + return elabs.map (·.value) |>.toArray + +@[implemented_by blockRoleElabsForUnsafe] +opaque blockRoleElabsFor (x : Name) : DocElabM (Array BlockRoleElab) + + abbrev BlockRoleExpander := Array Arg → Array Syntax → DocElabM (Array (TSyntax `term))