feat: improvements to Verso docstrings (#10479)
This PR implements module docstrings in Verso syntax, as well as adding a number of improvements and fixes to Verso docstrings in general. In particular, they now have language server support and are parsed at parse time rather than elaboration time, so the snapshot's syntax tree includes the parsed documentation.
This commit is contained in:
parent
35764213fc
commit
cee2886154
19 changed files with 1713 additions and 478 deletions
|
|
@ -50,23 +50,20 @@ def validateDocComment
|
|||
else
|
||||
logError err
|
||||
|
||||
|
||||
open Lean.Doc in
|
||||
open Parser in
|
||||
open Lean.Parser Command in
|
||||
/--
|
||||
Adds a Verso docstring to the specified declaration, which should already be present in the
|
||||
environment.
|
||||
|
||||
`binders` should be the syntax of the parameters to the constant that is being documented, as a null
|
||||
node that contains a sequence of bracketed binders. It is used to allow interactive features such as
|
||||
document highlights and “find references” to work for documented parameters. If no parameter binders
|
||||
are available, pass `Syntax.missing` or an empty null node.
|
||||
Parses a docstring as Verso, returning the syntax if successful.
|
||||
|
||||
When not successful, parser errors are logged.
|
||||
-/
|
||||
def versoDocString
|
||||
(declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) :
|
||||
TermElabM (Array (Doc.Block ElabInline ElabBlock) × Array (Doc.Part ElabInline ElabBlock Empty)) := do
|
||||
|
||||
def parseVersoDocString
|
||||
[Monad m] [MonadFileMap m] [MonadError m] [MonadEnv m] [MonadOptions m] [MonadLog m]
|
||||
[MonadResolveName m]
|
||||
(docComment : TSyntax [``docComment, ``moduleDoc]) : m (Option Syntax) := do
|
||||
if docComment.raw.getKind == ``docComment then
|
||||
match docComment.raw[0] with
|
||||
| docStx@(.node _ ``versoCommentBody _) => return docStx[1]?
|
||||
| _ => pure ()
|
||||
let text ← getFileMap
|
||||
-- TODO fallback to string version without nice interactivity
|
||||
let some startPos := docComment.raw[1].getPos? (canonicalOnly := true)
|
||||
|
|
@ -93,7 +90,7 @@ def versoDocString
|
|||
}
|
||||
let s := mkParserState text.source |>.setPos startPos
|
||||
-- TODO parse one block at a time for error recovery purposes
|
||||
let s := (Doc.Parser.document).run ictx pmctx (getTokenTable env) s
|
||||
let s := Doc.Parser.document.run ictx pmctx (getTokenTable env) s
|
||||
|
||||
if !s.allErrors.isEmpty then
|
||||
for (pos, _, err) in s.allErrors do
|
||||
|
|
@ -103,11 +100,42 @@ def versoDocString
|
|||
-- TODO end position
|
||||
data := err.toString
|
||||
}
|
||||
return (#[], #[])
|
||||
else
|
||||
let stx := s.stxStack.back
|
||||
let stx := stx.getArgs
|
||||
Doc.elabBlocks (stx.map (⟨·⟩)) |>.exec declName binders
|
||||
return none
|
||||
return some s.stxStack.back
|
||||
|
||||
|
||||
|
||||
open Lean.Doc in
|
||||
open Lean.Parser.Command in
|
||||
/--
|
||||
Elaborates a Verso docstring for the specified declaration, which should already be present in the
|
||||
environment.
|
||||
|
||||
`binders` should be the syntax of the parameters to the constant that is being documented, as a null
|
||||
node that contains a sequence of bracketed binders. It is used to allow interactive features such as
|
||||
document highlights and “find references” to work for documented parameters. If no parameter binders
|
||||
are available, pass `Syntax.missing` or an empty null node.
|
||||
-/
|
||||
|
||||
def versoDocString
|
||||
(declName : Name) (binders : Syntax) (docComment : TSyntax ``docComment) :
|
||||
TermElabM (Array (Doc.Block ElabInline ElabBlock) × Array (Doc.Part ElabInline ElabBlock Empty)) := do
|
||||
if let some stx ← parseVersoDocString docComment then
|
||||
Doc.elabBlocks (stx.getArgs.map (⟨·⟩)) |>.exec declName binders
|
||||
else return (#[], #[])
|
||||
|
||||
open Lean.Doc Parser in
|
||||
open Lean.Parser.Command in
|
||||
/--
|
||||
Parses and elaborates a Verso module docstring.
|
||||
-/
|
||||
def versoModDocString
|
||||
(range : DeclarationRange) (doc : TSyntax ``document) :
|
||||
TermElabM VersoModuleDocs.Snippet := do
|
||||
let level := getVersoModuleDocs (← getEnv) |>.terminalNesting |>.map (· + 1)
|
||||
Doc.elabModSnippet range (doc.raw.getArgs.map (⟨·⟩)) (level.getD 0) |>.execForModule
|
||||
|
||||
|
||||
|
||||
open Lean.Doc in
|
||||
open Parser in
|
||||
|
|
@ -185,6 +213,19 @@ def addVersoDocStringCore [Monad m] [MonadEnv m] [MonadLiftT BaseIO m] [MonadErr
|
|||
modifyEnv fun env =>
|
||||
versoDocStringExt.insert env declName docs
|
||||
|
||||
/--
|
||||
Adds an elaborated Verso module docstring to the environment.
|
||||
-/
|
||||
def addVersoModDocStringCore [Monad m] [MonadEnv m] [MonadLiftT BaseIO m] [MonadError m]
|
||||
(docs : VersoModuleDocs.Snippet) : m Unit := do
|
||||
if (getMainModuleDoc (← getEnv)).isEmpty then
|
||||
match addVersoModuleDocSnippet (← getEnv) docs with
|
||||
| .error e => throwError "Error adding module docs: {indentD <| toMessageData e}"
|
||||
| .ok env' => setEnv env'
|
||||
else
|
||||
throwError m!"Can't add Verso-format module docs because there is already Markdown-format content present."
|
||||
|
||||
open Lean.Parser.Command in
|
||||
/--
|
||||
Adds a Verso docstring to the environment.
|
||||
|
||||
|
|
@ -194,7 +235,7 @@ document highlights and “find references” to work for documented parameters.
|
|||
are available, pass `Syntax.missing` or an empty null node.
|
||||
-/
|
||||
def addVersoDocString
|
||||
(declName : Name) (binders : Syntax) (docComment : TSyntax `Lean.Parser.Command.docComment) :
|
||||
(declName : Name) (binders : Syntax) (docComment : TSyntax ``docComment) :
|
||||
TermElabM Unit := do
|
||||
unless (← getEnv).getModuleIdxFor? declName |>.isNone do
|
||||
throwError s!"invalid doc string, declaration '{declName}' is in an imported module"
|
||||
|
|
@ -278,3 +319,20 @@ def addDocString'
|
|||
match docString? with
|
||||
| some docString => addDocString declName binders docString
|
||||
| none => return ()
|
||||
|
||||
|
||||
open Lean.Parser.Command in
|
||||
open Lean.Doc.Parser in
|
||||
/--
|
||||
Adds a Verso docstring to the environment.
|
||||
|
||||
`binders` should be the syntax of the parameters to the constant that is being documented, as a null
|
||||
node that contains a sequence of bracketed binders. It is used to allow interactive features such as
|
||||
document highlights and “find references” to work for documented parameters. If no parameter binders
|
||||
are available, pass `Syntax.missing` or an empty null node.
|
||||
-/
|
||||
def addVersoModDocString
|
||||
(range : DeclarationRange) (docComment : TSyntax ``document) :
|
||||
TermElabM Unit := do
|
||||
let snippet ← versoModDocString range docComment
|
||||
addVersoModDocStringCore snippet
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ public import Lean.DocString.Links
|
|||
public import Lean.MonadEnv
|
||||
public import Init.Data.String.Extra
|
||||
public import Lean.DocString.Types
|
||||
import Lean.DocString.Markdown
|
||||
public import Lean.DocString.Markdown
|
||||
|
||||
public section
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ instance : Repr ElabInline where
|
|||
.group (.nestD ("{ name :=" ++ .line ++ repr v.name)) ++ .line ++
|
||||
.group (.nestD ("val :=" ++ .line ++ "Dynamic.mk " ++ repr v.val.typeName ++ " _ }"))
|
||||
|
||||
private instance : Doc.MarkdownInline ElabInline where
|
||||
instance : Doc.MarkdownInline ElabInline where
|
||||
-- TODO extensibility
|
||||
toMarkdown go _i content := content.forM go
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ instance : Repr ElabBlock where
|
|||
|
||||
|
||||
-- TODO extensible toMarkdown
|
||||
private instance : Doc.MarkdownBlock ElabInline ElabBlock where
|
||||
instance : Doc.MarkdownBlock ElabInline ElabBlock where
|
||||
toMarkdown _goI goB _b content := content.forM goB
|
||||
|
||||
structure VersoDocString where
|
||||
|
|
@ -214,5 +214,211 @@ def getModuleDoc? (env : Environment) (moduleName : Name) : Option (Array Module
|
|||
|
||||
def getDocStringText [Monad m] [MonadError m] (stx : TSyntax `Lean.Parser.Command.docComment) : m String :=
|
||||
match stx.raw[1] with
|
||||
| Syntax.atom _ val => return val.extract 0 (val.endPos - ⟨2⟩)
|
||||
| _ => throwErrorAt stx "unexpected doc string{indentD stx.raw[1]}"
|
||||
| Syntax.atom _ val =>
|
||||
return val.extract 0 (val.endPos - ⟨2⟩)
|
||||
| Syntax.node _ `Lean.Parser.Command.versoCommentBody _ =>
|
||||
match stx.raw[1][0] with
|
||||
| Syntax.atom _ val =>
|
||||
return val.extract 0 (val.endPos - ⟨2⟩)
|
||||
| _ =>
|
||||
throwErrorAt stx "unexpected doc string{indentD stx}"
|
||||
| _ =>
|
||||
throwErrorAt stx "unexpected doc string{indentD stx}"
|
||||
|
||||
|
||||
|
||||
/--
|
||||
A snippet of a Verso module text.
|
||||
|
||||
A snippet consists of text followed by subsections. Because the sequence of snippets that occur in a
|
||||
source file are conceptually a single document, they have a consistent header nesting structure.
|
||||
This means that initial textual content of a snippet is a continuation of the text at the end of the
|
||||
prior snippet.
|
||||
|
||||
The actual hierarchical structure of the document is reconstructed from the sequence of snippets.
|
||||
|
||||
The _terminal nesting_ of a sequence of snippets is 0 if there are no sections in the sequence.
|
||||
Otherwise, it is one greater than the nesting of the last snippet's last section. The module
|
||||
docstring elaborator maintains the invariant that each snippet's first section's level is at most
|
||||
the terminal nesting of the preceding snippets, and that the level of each section within a snippet
|
||||
is at most one greater than the preceding section's level.
|
||||
-/
|
||||
structure VersoModuleDocs.Snippet where
|
||||
/-- Text to be inserted after the prior snippet's ending text. -/
|
||||
text : Array (Doc.Block ElabInline ElabBlock) := #[]
|
||||
/--
|
||||
A sequence of parts with their absolute document nesting levels and header positions.
|
||||
None of these parts may contain sub-parts.
|
||||
-/
|
||||
sections : Array (Nat × DeclarationRange × Doc.Part ElabInline ElabBlock Empty) := #[]
|
||||
/--
|
||||
The location of the snippet in the source file.
|
||||
-/
|
||||
declarationRange : DeclarationRange
|
||||
deriving Inhabited, Repr
|
||||
|
||||
namespace VersoModuleDocs.Snippet
|
||||
|
||||
def canNestIn (level : Nat) (snippet : Snippet) : Bool :=
|
||||
if let some s := snippet.sections[0]? then s.1 ≤ level + 1 else true
|
||||
|
||||
def terminalNesting (snippet : Snippet) : Option Nat :=
|
||||
if let some s := snippet.sections.back? then s.1
|
||||
else none
|
||||
|
||||
def addBlock (snippet : Snippet) (block : Doc.Block ElabInline ElabBlock) : Snippet :=
|
||||
if h : snippet.sections.size = 0 then
|
||||
{ snippet with text := snippet.text.push block }
|
||||
else
|
||||
{ snippet with
|
||||
sections[snippet.sections.size - 1].2.2.content :=
|
||||
snippet.sections[snippet.sections.size - 1].2.2.content.push block }
|
||||
|
||||
def addPart (snippet : Snippet) (level : Nat) (range : DeclarationRange) (part : Doc.Part ElabInline ElabBlock Empty) : Snippet :=
|
||||
{ snippet with
|
||||
sections := snippet.sections.push (level, range, part) }
|
||||
|
||||
end VersoModuleDocs.Snippet
|
||||
|
||||
open Lean Doc ToMarkdown MarkdownM in
|
||||
instance : ToMarkdown VersoModuleDocs.Snippet where
|
||||
toMarkdown
|
||||
| {text, sections, ..} => do
|
||||
text.forM toMarkdown
|
||||
endBlock
|
||||
for (level, _, part) in sections do
|
||||
push ("".pushn '#' (level + 1))
|
||||
push " "
|
||||
for i in part.title do toMarkdown i
|
||||
endBlock
|
||||
for b in part.content do toMarkdown b
|
||||
endBlock
|
||||
|
||||
structure VersoModuleDocs where
|
||||
snippets : PersistentArray VersoModuleDocs.Snippet := {}
|
||||
terminalNesting : Option Nat := snippets.findSomeRev? (·.terminalNesting)
|
||||
deriving Inhabited
|
||||
|
||||
instance : Repr VersoModuleDocs where
|
||||
reprPrec v _ :=
|
||||
.group <| .nest 2 <|
|
||||
"{ " ++
|
||||
(.group <| .nest 2 <| "snippets := " ++ .line ++ repr v.snippets.toArray) ++ .line ++
|
||||
(.group <| .nest 2 <| "snippets := " ++ .line ++ repr v.snippets.toArray) ++
|
||||
" }"
|
||||
|
||||
namespace VersoModuleDocs
|
||||
|
||||
def isEmpty (docs : VersoModuleDocs) : Bool := docs.snippets.isEmpty
|
||||
|
||||
def canAdd (docs : VersoModuleDocs) (snippet : Snippet) : Bool :=
|
||||
if let some level := docs.terminalNesting then
|
||||
snippet.canNestIn level
|
||||
else true
|
||||
|
||||
|
||||
def add (docs : VersoModuleDocs) (snippet : Snippet) : Except String VersoModuleDocs := do
|
||||
unless docs.canAdd snippet do
|
||||
throw "Can't nest this snippet here"
|
||||
|
||||
return { docs with
|
||||
snippets := docs.snippets.push snippet,
|
||||
terminalNesting := snippet.terminalNesting
|
||||
}
|
||||
|
||||
def add! (docs : VersoModuleDocs) (snippet : Snippet) : VersoModuleDocs :=
|
||||
let ok :=
|
||||
if let some level := docs.terminalNesting then
|
||||
snippet.canNestIn level
|
||||
else true
|
||||
if not ok then
|
||||
panic! "Can't nest this snippet here"
|
||||
else
|
||||
{ docs with
|
||||
snippets := docs.snippets.push snippet,
|
||||
terminalNesting := snippet.terminalNesting
|
||||
}
|
||||
|
||||
|
||||
private structure DocFrame where
|
||||
content : Array (Doc.Block ElabInline ElabBlock)
|
||||
priorParts : Array (Doc.Part ElabInline ElabBlock Empty)
|
||||
titleString : String
|
||||
title : Array (Doc.Inline ElabInline)
|
||||
|
||||
private structure DocContext where
|
||||
content : Array (Doc.Block ElabInline ElabBlock)
|
||||
priorParts : Array (Doc.Part ElabInline ElabBlock Empty)
|
||||
context : Array DocFrame
|
||||
|
||||
private def DocContext.level (ctx : DocContext) : Nat := ctx.context.size
|
||||
|
||||
private def DocContext.close (ctx : DocContext) : Except String DocContext := do
|
||||
if h : ctx.context.size = 0 then
|
||||
throw "Can't close a section: none are open"
|
||||
else
|
||||
let last := ctx.context.back
|
||||
pure {
|
||||
content := last.content,
|
||||
priorParts := last.priorParts.push {
|
||||
title := last.title,
|
||||
titleString := last.titleString,
|
||||
metadata := none,
|
||||
content := ctx.content,
|
||||
subParts := ctx.priorParts,
|
||||
},
|
||||
context := ctx.context.pop
|
||||
}
|
||||
|
||||
private partial def DocContext.closeAll (ctx : DocContext) : Except String DocContext := do
|
||||
if ctx.context.size = 0 then
|
||||
return ctx
|
||||
else
|
||||
(← ctx.close).closeAll
|
||||
|
||||
private partial def DocContext.addPart (ctx : DocContext) (partLevel : Nat) (part : Doc.Part ElabInline ElabBlock Empty) : Except String DocContext := do
|
||||
if partLevel > ctx.level then throw s!"Invalid nesting: expected at most {ctx.level} but got {partLevel}"
|
||||
else if partLevel = ctx.level then pure { ctx with priorParts := ctx.priorParts.push part }
|
||||
else
|
||||
let ctx ← ctx.close
|
||||
ctx.addPart partLevel part
|
||||
|
||||
private def DocContext.addBlocks (ctx : DocContext) (blocks : Array (Doc.Block ElabInline ElabBlock)) : Except String DocContext := do
|
||||
if ctx.priorParts.isEmpty then pure { ctx with content := ctx.content ++ blocks }
|
||||
else throw "Can't add content after sub-parts"
|
||||
|
||||
private def DocContext.addSnippet (ctx : DocContext) (snippet : Snippet) : Except String DocContext := do
|
||||
let mut ctx ← ctx.addBlocks snippet.text
|
||||
for (l, _, p) in snippet.sections do
|
||||
ctx ← ctx.addPart l p
|
||||
return ctx
|
||||
|
||||
def assemble (docs : VersoModuleDocs) : Except String VersoDocString := do
|
||||
let mut ctx : DocContext := {content := #[], priorParts := #[], context := #[]}
|
||||
for snippet in docs.snippets do
|
||||
ctx ← ctx.addSnippet snippet
|
||||
ctx ← ctx.closeAll
|
||||
return { text := ctx.content, subsections := ctx.priorParts }
|
||||
|
||||
end VersoModuleDocs
|
||||
|
||||
private builtin_initialize versoModuleDocExt :
|
||||
SimplePersistentEnvExtension VersoModuleDocs.Snippet VersoModuleDocs ← registerSimplePersistentEnvExtension {
|
||||
addImportedFn := fun _ => {}
|
||||
addEntryFn := fun s e => s.add! e
|
||||
exportEntriesFnEx? := some fun _ _ es level =>
|
||||
if level < .server then
|
||||
#[]
|
||||
else
|
||||
es.toArray
|
||||
}
|
||||
|
||||
|
||||
def getVersoModuleDocs (env : Environment) : VersoModuleDocs :=
|
||||
versoModuleDocExt.getState env
|
||||
|
||||
def addVersoModuleDocSnippet (env : Environment) (snippet : VersoModuleDocs.Snippet) : Except String Environment :=
|
||||
let docs := getVersoModuleDocs env
|
||||
if docs.canAdd snippet then
|
||||
pure <| versoModuleDocExt.addEntry env snippet
|
||||
else throw s!"Can't add - incorrect nesting {docs.terminalNesting.map (s!"(expected at most {·})") |>.getD ""})"
|
||||
|
|
|
|||
224
src/Lean/DocString/Formatter.lean
Normal file
224
src/Lean/DocString/Formatter.lean
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: David Thrane Christiansen
|
||||
-/
|
||||
|
||||
module
|
||||
prelude
|
||||
public import Lean.PrettyPrinter.Formatter
|
||||
import Lean.DocString.Syntax
|
||||
|
||||
|
||||
namespace Lean.Doc.Parser
|
||||
|
||||
open Lean.PrettyPrinter Formatter
|
||||
open Lean.Syntax.MonadTraverser
|
||||
|
||||
open Lean.Doc.Syntax
|
||||
|
||||
def atomString : Syntax → String
|
||||
| .node _ _ #[x] => atomString x
|
||||
| .atom _ x => x
|
||||
| stx => s!"NON-ATOM {stx}"
|
||||
|
||||
def pushAtomString : Formatter := do
|
||||
push <| atomString (← getCur)
|
||||
goLeft
|
||||
|
||||
def pushAtomStrLit : Formatter := do
|
||||
push <| (Syntax.decodeStrLit (atomString (← getCur))).getD ""
|
||||
goLeft
|
||||
|
||||
|
||||
def identString : Syntax → String
|
||||
| .node _ _ #[x] => identString x
|
||||
| .ident _ _ x _ => toString x
|
||||
| stx => s!"NON-IDENT {stx}"
|
||||
|
||||
def pushIdent : Formatter := do
|
||||
push <| identString (← getCur)
|
||||
goLeft
|
||||
|
||||
def rep (f : Formatter) : Formatter := concat do
|
||||
let count := (← getCur).getArgs.size
|
||||
visitArgs do count.forM fun _ _ => do f
|
||||
|
||||
partial def versoSyntaxToString' (stx : Syntax) : ReaderT Nat (StateM String) Unit := do
|
||||
if stx.getKind == nullKind then
|
||||
stx.getArgs.forM versoSyntaxToString'
|
||||
else
|
||||
match stx with
|
||||
| `(arg_val|$s:str) => out <| atomString s
|
||||
| `(arg_val|$n:num) => out <| atomString n
|
||||
| `(arg_val|$x:ident) => out <| identString x
|
||||
| `(doc_arg|($x := $v)) | `(doc_arg|$x:ident := $v) =>
|
||||
out "("
|
||||
out <| identString x
|
||||
out " := "
|
||||
versoSyntaxToString' v
|
||||
out ")"
|
||||
| `(doc_arg|+%$tk$x:ident) | `(doc_arg|-%$tk$x:ident) =>
|
||||
out <| atomString tk
|
||||
out <| identString x
|
||||
| `(doc_arg|$x:arg_val) => versoSyntaxToString' x
|
||||
| `(inline|$s:str) => out s.getString
|
||||
| `(inline|_[%$tk1 $inl* ]%$tk2) | `(inline|*[%$tk1 $inl* ]%$tk2) =>
|
||||
out <| atomString tk1
|
||||
inl.forM versoSyntaxToString'
|
||||
out <| atomString tk2
|
||||
| `(inline|link[%$tk1 $inl* ]%$tk2 $tgt) =>
|
||||
out <| atomString tk1
|
||||
inl.forM versoSyntaxToString'
|
||||
out <| atomString tk2
|
||||
versoSyntaxToString' tgt
|
||||
| `(inline|image(%$tk1 $alt )%$tk2 $tgt) =>
|
||||
out <| atomString tk1
|
||||
out <| (Syntax.decodeStrLit (atomString alt)).getD ""
|
||||
out <| atomString tk2
|
||||
versoSyntaxToString' tgt
|
||||
| `(inline|role{$name $args*}[$inls*]) =>
|
||||
out "{"
|
||||
out <| identString name
|
||||
for arg in args do
|
||||
out " "
|
||||
versoSyntaxToString' arg
|
||||
out "}["
|
||||
inls.forM versoSyntaxToString'
|
||||
out "]"
|
||||
| `(inline|code(%$tk1$s)%$tk2) =>
|
||||
out <| atomString tk1
|
||||
out <| (Syntax.decodeStrLit (atomString s)).getD ""
|
||||
out <| atomString tk2
|
||||
| `(inline|footnote(%$tk1 $ref )%$tk2) =>
|
||||
out <| atomString tk1
|
||||
out <| (Syntax.decodeStrLit (atomString ref)).getD ""
|
||||
out <| atomString tk2
|
||||
| `(inline|line! $s) =>
|
||||
out <| (Syntax.decodeStrLit (atomString s)).getD ""
|
||||
| `(inline|\math%$tk1 code(%$tk2 $s )%$tk3)
|
||||
| `(inline|\displaymath%$tk1 code(%$tk2 $s )%$tk3) =>
|
||||
out <| atomString tk1
|
||||
out <| atomString tk2
|
||||
out <| (Syntax.decodeStrLit (atomString s)).getD ""
|
||||
out <| atomString tk3
|
||||
| `(link_target|[%$tk1 $ref ]%$tk2) =>
|
||||
out <| atomString tk1
|
||||
out <| (Syntax.decodeStrLit (atomString ref)).getD ""
|
||||
out <| atomString tk2
|
||||
| `(link_target|(%$tk1 $url )%$tk2) =>
|
||||
out <| atomString tk1
|
||||
out <| (Syntax.decodeStrLit (atomString url)).getD ""
|
||||
out <| atomString tk2
|
||||
| `(block|header($n){$inl*}) =>
|
||||
out <| "#".pushn '#' n.getNat ++ " "
|
||||
inl.forM versoSyntaxToString'
|
||||
endBlock
|
||||
| `(block|para[$inl*]) =>
|
||||
startBlock
|
||||
inl.forM versoSyntaxToString'
|
||||
endBlock
|
||||
| `(block|ul{$items*}) =>
|
||||
startBlock
|
||||
items.forM fun
|
||||
| `(list_item|* $blks*) => do
|
||||
out "* "
|
||||
withReader (· + 2) (blks.forM versoSyntaxToString')
|
||||
endBlock
|
||||
| _ => pure ()
|
||||
endBlock
|
||||
| `(block|ol($n){$items*}) =>
|
||||
startBlock
|
||||
let mut n := n.getNat
|
||||
for item in items do
|
||||
match item with
|
||||
| `(list_item|* $blks*) => do
|
||||
out s!"{n}. "
|
||||
withReader (· + 2) (blks.forM versoSyntaxToString')
|
||||
endBlock
|
||||
n := n + 1
|
||||
| _ => pure ()
|
||||
endBlock
|
||||
| `(block| > $blks*) =>
|
||||
startBlock
|
||||
out "> "
|
||||
withReader (· + 2) (blks.forM versoSyntaxToString')
|
||||
endBlock
|
||||
| `(block| ```%$tk1 $name $args* | $s ```%$tk2) =>
|
||||
startBlock
|
||||
out <| atomString tk1
|
||||
out <| identString name
|
||||
for arg in args do
|
||||
out " "
|
||||
versoSyntaxToString' arg
|
||||
out "\n"
|
||||
let i ← read
|
||||
let s := Syntax.decodeStrLit (atomString s) |>.getD ""
|
||||
|>.split (· == '\n')
|
||||
|>.map ("".pushn ' ' i ++ · ) |> "\n".intercalate
|
||||
out s
|
||||
out <| "".pushn ' ' i
|
||||
out <| atomString tk2
|
||||
endBlock
|
||||
| `(block| :::%$tk1 $name $args* {$blks*}%$tk2) =>
|
||||
startBlock
|
||||
out <| atomString tk1
|
||||
out " "
|
||||
out <| identString name
|
||||
for arg in args do
|
||||
out " "
|
||||
versoSyntaxToString' arg
|
||||
out "\n"
|
||||
blks.forM versoSyntaxToString'
|
||||
let i ← read
|
||||
out <| "".pushn ' ' i
|
||||
out <| atomString tk2
|
||||
endBlock
|
||||
| `(block|command{ $name $args* }) =>
|
||||
startBlock
|
||||
out <| "{"
|
||||
out <| identString name
|
||||
for arg in args do
|
||||
out " "
|
||||
versoSyntaxToString' arg
|
||||
out "}"
|
||||
endBlock
|
||||
|
||||
| other => out (toString other)
|
||||
where
|
||||
out (s : String) : ReaderT Nat (StateM String) Unit := modify (· ++ s)
|
||||
nl : ReaderT Nat (StateM String) Unit := read >>= fun n => modify (· ++ "\n".pushn ' ' n)
|
||||
startBlock : ReaderT Nat (StateM String) Unit := do
|
||||
let s ← get
|
||||
if s.endsWith "\n" then
|
||||
let i ← read
|
||||
out ("".pushn ' ' i)
|
||||
endBlock : ReaderT Nat (StateM String) Unit := do
|
||||
let s ← get
|
||||
if s.endsWith "\n\n" then return
|
||||
else if s.endsWith "\n" then out "\n"
|
||||
else out "\n\n"
|
||||
|
||||
def formatMetadata : Formatter := do
|
||||
visitArgs do
|
||||
pushLine
|
||||
visitAtom .anonymous
|
||||
pushLine
|
||||
metadataContents.formatter
|
||||
pushLine
|
||||
visitAtom .anonymous
|
||||
|
||||
def versoSyntaxToString (stx : Syntax) : String :=
|
||||
versoSyntaxToString' stx |>.run 0 |>.run "" |>.2
|
||||
|
||||
public def document.formatter : Formatter := concat do
|
||||
let stx ← getCur
|
||||
let i := stx.getArgs.size
|
||||
visitArgs do
|
||||
for _ in [0:i] do
|
||||
let blk ← getCur
|
||||
if blk.getKind == ``Doc.Syntax.metadata_block then
|
||||
formatMetadata
|
||||
else
|
||||
push (versoSyntaxToString blk)
|
||||
goLeft
|
||||
|
|
@ -85,11 +85,20 @@ Generates Markdown, rendering the result from the final state, without producing
|
|||
public def MarkdownM.run' (act : MarkdownM Unit) (context : Context := {}) (state : State := {}) : String :=
|
||||
act.run context state |>.2
|
||||
|
||||
private def MarkdownM.push (txt : String) : MarkdownM Unit := modify (·.push txt)
|
||||
/--
|
||||
Adds a string to the current Markdown output.
|
||||
-/
|
||||
public def MarkdownM.push (txt : String) : MarkdownM Unit := modify (·.push txt)
|
||||
|
||||
private def MarkdownM.endBlock : MarkdownM Unit := modify (·.endBlock)
|
||||
/--
|
||||
Terminates the current block.
|
||||
-/
|
||||
public def MarkdownM.endBlock : MarkdownM Unit := modify (·.endBlock)
|
||||
|
||||
private def MarkdownM.indent: MarkdownM α → MarkdownM α :=
|
||||
/--
|
||||
Increases the indentation level by one.
|
||||
-/
|
||||
public def MarkdownM.indent: MarkdownM α → MarkdownM α :=
|
||||
withReader fun st => { st with linePrefix := st.linePrefix ++ " " }
|
||||
|
||||
/--
|
||||
|
|
@ -159,6 +168,41 @@ private def quoteCode (str : String) : String := Id.run do
|
|||
let str := if str.startsWith "`" || str.endsWith "`" then " " ++ str ++ " " else str
|
||||
backticks ++ str ++ backticks
|
||||
|
||||
private partial def trimLeft (inline : Inline i) : (String × Inline i) := go [inline]
|
||||
where
|
||||
go : List (Inline i) → String × Inline i
|
||||
| [] => ("", .empty)
|
||||
| .text s :: more =>
|
||||
if s.all (·.isWhitespace) then
|
||||
let (pre, post) := go more
|
||||
(s ++ pre, post)
|
||||
else
|
||||
let s1 := s.takeWhile (·.isWhitespace)
|
||||
let s2 := s.drop s1.length
|
||||
(s1, .text s2 ++ .concat more.toArray)
|
||||
| .concat xs :: more => go (xs.toList ++ more)
|
||||
| here :: more => ("", here ++ .concat more.toArray)
|
||||
|
||||
private partial def trimRight (inline : Inline i) : (Inline i × String) := go [inline]
|
||||
where
|
||||
go : List (Inline i) → Inline i × String
|
||||
| [] => (.empty, "")
|
||||
| .text s :: more =>
|
||||
if s.all (·.isWhitespace) then
|
||||
let (pre, post) := go more
|
||||
(pre, post ++ s)
|
||||
else
|
||||
let s1 := s.takeRightWhile (·.isWhitespace)
|
||||
let s2 := s.dropRight s1.length
|
||||
(.concat more.toArray.reverse ++ .text s2, s1)
|
||||
| .concat xs :: more => go (xs.reverse.toList ++ more)
|
||||
| here :: more => (.concat more.toArray.reverse ++ here, "")
|
||||
|
||||
private def trim (inline : Inline i) : (String × Inline i × String) :=
|
||||
let (pre, more) := trimLeft inline
|
||||
let (mid, post) := trimRight more
|
||||
(pre, mid, post)
|
||||
|
||||
open MarkdownM in
|
||||
private partial def inlineMarkdown [MarkdownInline i] : Inline i → MarkdownM Unit
|
||||
| .text s =>
|
||||
|
|
@ -166,19 +210,25 @@ private partial def inlineMarkdown [MarkdownInline i] : Inline i → MarkdownM U
|
|||
| .linebreak s => do
|
||||
push <| s.replace "\n" ("\n" ++ (← read).linePrefix )
|
||||
| .emph xs => do
|
||||
let (pre, mid, post) := trim (.concat xs)
|
||||
push pre
|
||||
unless (← read).inEmph do
|
||||
push "*"
|
||||
withReader (fun ρ => { ρ with inEmph := true }) do
|
||||
for i in xs do inlineMarkdown i
|
||||
inlineMarkdown mid
|
||||
unless (← read).inEmph do
|
||||
push "*"
|
||||
push post
|
||||
| .bold xs => do
|
||||
let (pre, mid, post) := trim (.concat xs)
|
||||
push pre
|
||||
unless (← read).inBold do
|
||||
push "**"
|
||||
withReader (fun ρ => { ρ with inEmph := true }) do
|
||||
for i in xs do inlineMarkdown i
|
||||
inlineMarkdown mid
|
||||
unless (← read).inBold do
|
||||
push "**"
|
||||
push post
|
||||
| .concat xs =>
|
||||
for i in xs do inlineMarkdown i
|
||||
| .link content url => do
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ module
|
|||
prelude
|
||||
public import Lean.Parser.Types
|
||||
public import Lean.DocString.Syntax
|
||||
import Lean.PrettyPrinter.Formatter
|
||||
import Lean.Parser.Term.Basic
|
||||
|
||||
set_option linter.missingDocs true
|
||||
|
||||
|
|
@ -231,6 +233,16 @@ public def fakeAtom (str : String) (info : SourceInfo := SourceInfo.none) : Pars
|
|||
let atom := .atom info str
|
||||
s.pushSyntax atom
|
||||
|
||||
/--
|
||||
Construct a “fake” atom with the given string content, with zero-width source information at the
|
||||
current position.
|
||||
|
||||
Normally, atoms are always substrings of the original input; however, Verso's concrete syntax is
|
||||
different enough from Lean's that this isn't always a good match.
|
||||
-/
|
||||
private def fakeAtomHere (str : String) : ParserFn :=
|
||||
withInfoSyntaxFn skip.fn (fun info => fakeAtom str (info := info))
|
||||
|
||||
private def pushMissing : ParserFn := fun _c s =>
|
||||
s.pushSyntax .missing
|
||||
|
||||
|
|
@ -586,7 +598,7 @@ A linebreak that isn't a block break (that is, there's non-space content on the
|
|||
def linebreak (ctxt : InlineCtxt) : ParserFn :=
|
||||
if ctxt.allowNewlines then
|
||||
nodeFn ``linebreak <|
|
||||
andthenFn (withInfoSyntaxFn skip.fn (fun info => fakeAtom "line!" info)) <|
|
||||
andthenFn (fakeAtomHere "line!") <|
|
||||
nodeFn strLitKind <|
|
||||
asStringFn (quoted := true) <|
|
||||
atomicFn (chFn '\n' >> lookaheadFn (manyFn (chFn ' ') >> notFollowedByFn (chFn '\n' <|> blockOpener) "newline"))
|
||||
|
|
@ -795,6 +807,9 @@ open Lean.Parser.Term in
|
|||
def metadataContents : Parser :=
|
||||
structInstFields (sepByIndent structInstField ", " (allowTrailingSep := true))
|
||||
|
||||
def withPercents : ParserFn → ParserFn := fun p =>
|
||||
adaptUncacheableContextFn (fun c => {c with tokens := c.tokens.insert "%%%" "%%%"}) p
|
||||
|
||||
open Lean.Parser.Term in
|
||||
/--
|
||||
Parses a metadata block, which contains the contents of a Lean structure initialization but is
|
||||
|
|
@ -803,8 +818,7 @@ surrounded by `%%%` on each side.
|
|||
public def metadataBlock : ParserFn :=
|
||||
nodeFn ``metadata_block <|
|
||||
opener >>
|
||||
metadataContents.fn >>
|
||||
takeWhileFn (·.isWhitespace) >>
|
||||
withPercents metadataContents.fn >>
|
||||
closer
|
||||
where
|
||||
opener := atomicFn (bolThen (eatSpaces >> strFn "%%%") "%%% (at line beginning)") >> eatSpaces >> ignoreFn (chFn '\n')
|
||||
|
|
@ -956,35 +970,35 @@ mutual
|
|||
nodeFn ``ul <|
|
||||
lookaheadUnorderedListIndicator ctxt fun type =>
|
||||
withCurrentColumn fun c =>
|
||||
fakeAtom "ul{" >>
|
||||
fakeAtomHere "ul{" >>
|
||||
many1Fn (listItem {ctxt with minIndent := c + 1 , inLists := ⟨c, .inr type⟩ :: ctxt.inLists}) >>
|
||||
fakeAtom "}"
|
||||
fakeAtomHere "}"
|
||||
|
||||
/-- Parses an ordered list. -/
|
||||
public partial def orderedList (ctxt : BlockCtxt) : ParserFn :=
|
||||
nodeFn ``ol <|
|
||||
fakeAtom "ol(" >>
|
||||
fakeAtomHere "ol(" >>
|
||||
lookaheadOrderedListIndicator ctxt fun type _start => -- TODO? Validate list numbering?
|
||||
withCurrentColumn fun c =>
|
||||
fakeAtom ")" >> fakeAtom "{" >>
|
||||
fakeAtomHere ")" >> fakeAtomHere "{" >>
|
||||
many1Fn (listItem {ctxt with minIndent := c + 1 , inLists := ⟨c, .inl type⟩ :: ctxt.inLists}) >>
|
||||
fakeAtom "}"
|
||||
fakeAtomHere "}"
|
||||
|
||||
/-- Parses a definition list. -/
|
||||
public partial def definitionList (ctxt : BlockCtxt) : ParserFn :=
|
||||
nodeFn ``dl <|
|
||||
atomicFn (onlyBlockOpeners >> takeWhileFn (· == ' ') >> ignoreFn (lookaheadFn (chFn ':' >> chFn ' ')) >> guardMinColumn ctxt.minIndent) >>
|
||||
withInfoSyntaxFn skip.fn (fun info => fakeAtom "dl{" info) >>
|
||||
fakeAtomHere "dl{" >>
|
||||
withCurrentColumn (fun c => many1Fn (descItem {ctxt with minIndent := c})) >>
|
||||
withInfoSyntaxFn skip.fn (fun info => fakeAtom "}" info)
|
||||
fakeAtomHere "}"
|
||||
|
||||
/-- Parses a paragraph (that is, a sequence of otherwise-undecorated inlines). -/
|
||||
public partial def para (ctxt : BlockCtxt) : ParserFn :=
|
||||
nodeFn ``para <|
|
||||
atomicFn (takeWhileFn (· == ' ') >> notFollowedByFn blockOpener "block opener" >> guardMinColumn ctxt.minIndent) >>
|
||||
withInfoSyntaxFn skip.fn (fun info => fakeAtom "para{" (info := info)) >>
|
||||
fakeAtomHere "para{" >>
|
||||
textLine >>
|
||||
withInfoSyntaxFn skip.fn (fun info => fakeAtom "}" (info := info))
|
||||
fakeAtomHere "}"
|
||||
|
||||
/-- Parses a header. -/
|
||||
public partial def header (ctxt : BlockCtxt) : ParserFn :=
|
||||
|
|
@ -999,7 +1013,7 @@ mutual
|
|||
fakeAtom ")") >>
|
||||
fakeAtom "{" >>
|
||||
textLine (allowNewlines := false) >>
|
||||
fakeAtom "}"
|
||||
fakeAtomHere "}"
|
||||
|
||||
/--
|
||||
Parses a code block. The resulting string literal has already had the fences' leading indentation
|
||||
|
|
@ -1170,3 +1184,56 @@ mutual
|
|||
-/
|
||||
public partial def document (blockContext : BlockCtxt := {}) : ParserFn := ignoreFn (manyFn blankLine) >> blocks blockContext
|
||||
end
|
||||
|
||||
section
|
||||
open Lean.PrettyPrinter
|
||||
|
||||
/--
|
||||
Parses as `ifVerso` if the option `doc.verso` is `true`, or as `ifNotVerso` otherwise.
|
||||
-/
|
||||
public def ifVersoFn (ifVerso ifNotVerso : ParserFn) : ParserFn := fun c s =>
|
||||
if c.options.getBool `doc.verso then ifVerso c s
|
||||
else ifNotVerso c s
|
||||
|
||||
@[inherit_doc ifVersoFn]
|
||||
public def ifVerso (ifVerso ifNotVerso : Parser) : Parser where
|
||||
fn :=
|
||||
ifVersoFn ifVerso.fn ifNotVerso.fn
|
||||
|
||||
/--
|
||||
Formatter for `ifVerso`—formats according to the underlying formatters.
|
||||
-/
|
||||
@[combinator_formatter ifVerso, expose]
|
||||
public def ifVerso.formatter (f1 f2 : Formatter) : Formatter := f1 <|> f2
|
||||
|
||||
/--
|
||||
Parenthesizer for `ifVerso`—parenthesizes according to the underlying parenthesizers.
|
||||
-/
|
||||
@[combinator_parenthesizer ifVerso, expose]
|
||||
public def ifVerso.parenthesizer (p1 p2 : Parenthesizer) : Parenthesizer := p1 <|> p2
|
||||
|
||||
/--
|
||||
Disables the option `doc.verso` while running a parser.
|
||||
-/
|
||||
public def withoutVersoSyntax (p : Parser) : Parser where
|
||||
fn :=
|
||||
adaptUncacheableContextFn
|
||||
(fun c => { c with options := c.options.setBool `doc.verso false })
|
||||
p.fn
|
||||
info := p.info
|
||||
|
||||
/--
|
||||
Formatter for `withoutVersoSyntax`—formats according to the underlying formatter.
|
||||
-/
|
||||
@[combinator_formatter withoutVersoSyntax, expose]
|
||||
public def withoutVersoSyntax.formatter (p : Formatter) : Formatter := p
|
||||
/--
|
||||
Parenthesizer for `withoutVersoSyntax`—parenthesizes according to the underlying parenthesizer.
|
||||
-/
|
||||
@[combinator_parenthesizer withoutVersoSyntax, expose]
|
||||
public def withoutVersoSyntax.parenthesizer (p : Parenthesizer) : Parenthesizer := p
|
||||
|
||||
end
|
||||
|
||||
builtin_initialize
|
||||
register_parser_alias withoutVersoSyntax
|
||||
|
|
|
|||
|
|
@ -7,14 +7,8 @@ Author: David Thrane Christiansen
|
|||
module
|
||||
|
||||
prelude
|
||||
|
||||
import Init.Prelude
|
||||
import Init.Notation
|
||||
public import Lean.Parser.Types
|
||||
import Lean.Syntax
|
||||
import Lean.Parser.Extra
|
||||
public import Lean.Parser.Term
|
||||
meta import Lean.Parser.Term
|
||||
public import Lean.Parser.Term.Basic
|
||||
meta import Lean.Parser.Term.Basic
|
||||
|
||||
|
||||
/-!
|
||||
|
|
@ -63,22 +57,28 @@ scoped syntax (name:=arg_num) num : arg_val
|
|||
|
||||
/-- Arguments -/
|
||||
declare_syntax_cat doc_arg
|
||||
/-- Anonymous positional arguments -/
|
||||
/-- Anonymous positional argument -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=anon) arg_val : doc_arg
|
||||
/-- Named arguments -/
|
||||
/-- Named argument -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=named) "(" ident " := " arg_val ")": doc_arg
|
||||
/-- Named arguments, without parentheses. -/
|
||||
@[inherit_doc named, builtin_doc]
|
||||
scoped syntax (name:=named_no_paren) ident " := " arg_val : doc_arg
|
||||
/-- Boolean flags, turned on -/
|
||||
/-- Boolean flag, turned on -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=flag_on) "+" ident : doc_arg
|
||||
/-- Boolean flags, turned off -/
|
||||
/-- Boolean flag, turned off -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=flag_off) "-" ident : doc_arg
|
||||
|
||||
/-- Link targets, which may be URLs or named references -/
|
||||
declare_syntax_cat link_target
|
||||
/-- A reference to a URL -/
|
||||
/-- A URL target, written explicitly. Use square brackets for a named target. -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=url) "(" str ")" : link_target
|
||||
/-- A named reference -/
|
||||
/-- A named reference to a URL defined elsewhere. Use parentheses to write the URL here. -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=ref) "[" str "]" : link_target
|
||||
|
||||
/--
|
||||
|
|
@ -91,26 +91,87 @@ This syntax uses the following conventions:
|
|||
-/
|
||||
declare_syntax_cat inline
|
||||
scoped syntax (name:=text) str : inline
|
||||
/-- Emphasis (often rendered as italics) -/
|
||||
/--
|
||||
Emphasis, often rendered as italics.
|
||||
|
||||
Emphasis may be nested by using longer sequences of `_` for the outer delimiters. For example:
|
||||
```
|
||||
Remember: __always butter the _rugbrød_ before adding toppings!__
|
||||
```
|
||||
Here, the outer `__` is used to emphasize the instructions, while the inner `_` indicates the use of
|
||||
a non-English word.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=emph) "_[" inline* "]" : inline
|
||||
/-- Bold emphasis -/
|
||||
/--
|
||||
Bold emphasis.
|
||||
|
||||
A single `*` suffices to make text bold. Using `_` for emphasis.
|
||||
|
||||
Bold text may be nested by using longer sequences of `*` for the outer delimiters.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=bold) "*[" inline* "]" : inline
|
||||
/-- Link -/
|
||||
/--
|
||||
A link. The link's target may either be a concrete URL (written in parentheses) or a named URL
|
||||
(written in square brackets).
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=link) "link[" inline* "]" link_target : inline
|
||||
/-- Image -/
|
||||
/--
|
||||
An image, with alternate text and a URL.
|
||||
|
||||
The alternate text is a plain string, rather than Verso markup.
|
||||
|
||||
The image URL may either be a concrete URL (written in parentheses) or a named URL (written in
|
||||
square brackets).
|
||||
-/
|
||||
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=image) "image(" str ")" link_target : inline
|
||||
/-- A footnote use -/
|
||||
/--
|
||||
A footnote use site.
|
||||
|
||||
Footnotes must be defined elsewhere using the `[^NAME]: TEXT` syntax.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=footnote) "footnote(" str ")" : inline
|
||||
/-- Line break -/
|
||||
scoped syntax (name:=linebreak) "line!" str : inline
|
||||
/-- Literal code. If the first and last characters are space, and it contains at least one non-space
|
||||
character, then the resulting string has a single space stripped from each end.-/
|
||||
/--
|
||||
Literal code.
|
||||
|
||||
Code may begin with any non-zero number of backticks. It must be terminated with the same number,
|
||||
and it may not contain a sequence of backticks that is at least as long as its starting or ending
|
||||
delimiters.
|
||||
|
||||
If the first and last characters are space, and it contains at least one non-space character, then
|
||||
the resulting string has a single space stripped from each end. Thus, ``` `` `x `` ``` represents
|
||||
``"`x"``, not ``" `x "``.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=code) "code(" str ")" : inline
|
||||
/-- A _role_: an extension to the Verso document language in an inline position -/
|
||||
/--
|
||||
A _role_: an extension to the Verso document language in an inline position.
|
||||
|
||||
Text is given a role using the following syntax: `{NAME ARGS*}[CONTENT]`. The `NAME` is an
|
||||
identifier that determines which role is being used, akin to a function name. Each of the `ARGS` may
|
||||
have the following forms:
|
||||
* A value, which is a string literal, natural number, or identifier
|
||||
* A named argument, of the form `(NAME := VALUE)`
|
||||
* A flag, of the form `+NAME` or `-NAME`
|
||||
|
||||
The `CONTENT` is a sequence of inline content. If there is only one piece of content and it has
|
||||
beginning and ending delimiters (e.g. code literals, links, or images, but not ordinary text), then
|
||||
the `[` and `]` may be omitted. In particular, `` {NAME ARGS*}`x` `` is equivalent to
|
||||
``{NAME ARGS*}[`x`]``.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=role) "role{" ident doc_arg* "}" "[" inline* "]" : inline
|
||||
/-- Inline mathematical notation (equivalent to LaTeX's `$` notation) -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=inline_math) "\\math" code : inline
|
||||
/-- Display-mode mathematical notation -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=display_math) "\\displaymath" code : inline
|
||||
|
||||
/--
|
||||
|
|
@ -132,40 +193,130 @@ declare_syntax_cat block
|
|||
|
||||
/-- Items from both ordered and unordered lists -/
|
||||
declare_syntax_cat list_item
|
||||
/-- List item -/
|
||||
/-- A list item -/
|
||||
@[builtin_doc]
|
||||
syntax (name:=li) "*" block* : list_item
|
||||
|
||||
/-- A description of an item -/
|
||||
declare_syntax_cat desc_item
|
||||
/-- A description of an item -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=desc) ":" inline* "=>" block* : desc_item
|
||||
|
||||
/-- Paragraph -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=para) "para[" inline+ "]" : block
|
||||
/-- Unordered List -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=ul) "ul{" list_item* "}" : block
|
||||
/-- Definition list -/
|
||||
/-- Description list -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=dl) "dl{" desc_item* "}" : block
|
||||
/-- Ordered list -/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=ol) "ol(" num ")" "{" list_item* "}" : block
|
||||
/-- Literal code -/
|
||||
/--
|
||||
A code block that contains literal code.
|
||||
|
||||
Code blocks have the following syntax:
|
||||
````
|
||||
```(NAME ARGS*)?
|
||||
CONTENT
|
||||
```
|
||||
````
|
||||
|
||||
`CONTENT` is a literal string. If the `CONTENT` contains a sequence of three or more backticks, then
|
||||
the opening and closing ` ``` ` (called _fences_) should have more backticks than the longest
|
||||
sequence in `CONTENT`. Additionally, the opening and closing fences should have the same number of
|
||||
backticks.
|
||||
|
||||
If `NAME` and `ARGS` are not provided, then the code block represents literal text. If provided, the
|
||||
`NAME` is an identifier that selects an interpretation of the block. Unlike Markdown, this name is
|
||||
not necessarily the language in which the code is written, though many custom code blocks are, in
|
||||
practice, named after the language that they contain. `NAME` is more akin to a function name. Each
|
||||
of the `ARGS` may have the following forms:
|
||||
* A value, which is a string literal, natural number, or identifier
|
||||
* A named argument, of the form `(NAME := VALUE)`
|
||||
* A flag, of the form `+NAME` or `-NAME`
|
||||
|
||||
The `CONTENT` is interpreted according to the indentation of the fences. If the fences are indented
|
||||
`n` spaces, then `n` spaces are removed from the start of each line of `CONTENT`.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=codeblock) "```" (ident doc_arg*)? "|" str "```" : block
|
||||
/-- Quotation -/
|
||||
/--
|
||||
A quotation, which contains a sequence of blocks that are at least as indented as the `>`.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=blockquote) ">" block* : block
|
||||
/-- A link reference definition -/
|
||||
/--
|
||||
A named URL that can be used in links and images.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=link_ref) "[" str "]:" str : block
|
||||
/-- A footnote definition -/
|
||||
/--
|
||||
A footnote definition.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=footnote_ref) "[^" str "]:" inline* : block
|
||||
/-- Custom directive -/
|
||||
/--
|
||||
A _directive_, which is an extension to the Verso language in block position.
|
||||
|
||||
Directives have the following syntax:
|
||||
```
|
||||
:::NAME ARGS*
|
||||
CONTENT*
|
||||
:::
|
||||
```
|
||||
|
||||
The `NAME` is an identifier that determines which directive is being used, akin to a function name.
|
||||
Each of the `ARGS` may have the following forms:
|
||||
* A value, which is a string literal, natural number, or identifier
|
||||
* A named argument, of the form `(NAME := VALUE)`
|
||||
* A flag, of the form `+NAME` or `-NAME`
|
||||
|
||||
The `CONTENT` is a sequence of block content. Directives may be nested by using more colons in
|
||||
the outer directive. For example:
|
||||
```
|
||||
::::outer +flag (arg := 5)
|
||||
A paragraph.
|
||||
:::inner "label"
|
||||
* 1
|
||||
* 2
|
||||
:::
|
||||
::::
|
||||
```
|
||||
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=directive) ":::" rawIdent doc_arg* "{" block:max* "}" : block
|
||||
/-- A header -/
|
||||
/--
|
||||
A header
|
||||
|
||||
Headers must be correctly nested to form a tree structure. The first header in a document must
|
||||
start with `#`, and subsequent headers must have at most one more `#` than the preceding header.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=header) "header(" num ")" "{" inline+ "}" : block
|
||||
|
||||
open Lean.Parser Term in
|
||||
meta def metadataContents : Parser :=
|
||||
meta def metadataContents : Lean.Parser.Parser :=
|
||||
structInstFields (sepByIndent structInstField ", " (allowTrailingSep := true))
|
||||
|
||||
/-- Metadata for this section, defined by the current genre -/
|
||||
/--
|
||||
Metadata for the preceding header.
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=metadata_block) "%%%" metadataContents "%%%" : block
|
||||
|
||||
/-- A block-level command -/
|
||||
/--
|
||||
A block-level command, which invokes an extension during documentation processing.
|
||||
|
||||
The `NAME` is an identifier that determines which command is being used, akin to a function name.
|
||||
Each of the `ARGS` may have the following forms:
|
||||
* A value, which is a string literal, natural number, or identifier
|
||||
* A named argument, of the form `(NAME := VALUE)`
|
||||
* A flag, of the form `+NAME` or `-NAME`
|
||||
-/
|
||||
@[builtin_doc]
|
||||
scoped syntax (name:=command) "command{" rawIdent doc_arg* "}" : block
|
||||
|
|
|
|||
|
|
@ -23,13 +23,20 @@ public section
|
|||
namespace Lean.Elab.Command
|
||||
|
||||
@[builtin_command_elab moduleDoc] def elabModuleDoc : CommandElab := fun stx => do
|
||||
let some range ← Elab.getDeclarationRange? stx
|
||||
| return -- must be from partial syntax, ignore
|
||||
|
||||
match stx[1] with
|
||||
| Syntax.atom _ val =>
|
||||
let doc := val.extract 0 (val.endPos - ⟨2⟩)
|
||||
let some range ← Elab.getDeclarationRange? stx
|
||||
| return -- must be from partial syntax, ignore
|
||||
modifyEnv fun env => addMainModuleDoc env ⟨doc, range⟩
|
||||
| _ => throwErrorAt stx "unexpected module doc string{indentD stx[1]}"
|
||||
if getVersoModuleDocs (← getEnv) |>.isEmpty then
|
||||
let doc := val.extract 0 (val.endPos - ⟨2⟩)
|
||||
modifyEnv fun env => addMainModuleDoc env ⟨doc, range⟩
|
||||
else
|
||||
throwError m!"Can't add Markdown-format module docs because there is already Verso-format content present."
|
||||
| Syntax.node _ ``Lean.Parser.Command.versoCommentBody args =>
|
||||
runTermElabM fun _ => do
|
||||
addVersoModDocString range ⟨args.getD 0 .missing⟩
|
||||
| _ => throwErrorAt stx "unexpected module doc string{indentD <| stx}"
|
||||
|
||||
private def addScope (isNewNamespace : Bool) (header : String) (newNamespace : Name)
|
||||
(isNoncomputable isPublic : Bool := false) (attrs : List (TSyntax ``Parser.Term.attrInstance) := []) :
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ public import Lean.Elab.Term.TermElabM
|
|||
public import Lean.Elab.Command.Scope
|
||||
import Lean.DocString.Syntax
|
||||
import Lean.Meta.Hint
|
||||
import Lean.DocString.Markdown
|
||||
import Lean.BuiltinDocAttr
|
||||
|
||||
set_option linter.missingDocs true
|
||||
|
||||
|
|
@ -22,7 +24,6 @@ open Std
|
|||
open scoped Lean.Doc.Syntax
|
||||
|
||||
|
||||
|
||||
public section
|
||||
|
||||
private structure ElabLink where
|
||||
|
|
@ -112,8 +113,6 @@ deriving BEq, Repr
|
|||
|
||||
/-- Context used as a reader in `DocM`. -/
|
||||
structure Context where
|
||||
/-- The declaration for which documentation is being elaborated. -/
|
||||
declName : Name
|
||||
/-- Whether suggestions should be provided interactively. -/
|
||||
suggestionMode : SuggestionMode
|
||||
|
||||
|
|
@ -140,32 +139,95 @@ instance : MonadLift TermElabM DocM where
|
|||
act
|
||||
return v
|
||||
|
||||
private structure ModuleDocstringState extends Lean.Doc.State where
|
||||
scopedExts : Array (ScopedEnvExtension EnvExtensionEntry EnvExtensionEntry EnvExtensionState)
|
||||
|
||||
|
||||
private builtin_initialize modDocstringStateExt : EnvExtension (Option ModuleDocstringState) ←
|
||||
registerEnvExtension (pure none)
|
||||
|
||||
private def getModState
|
||||
[Monad m] [MonadEnv m] [MonadLiftT IO m] [MonadLCtx m]
|
||||
[MonadResolveName m] [MonadOptions m] : m ModuleDocstringState := do
|
||||
if let some st := modDocstringStateExt.getState (← getEnv) then
|
||||
return st
|
||||
else
|
||||
let lctx ← getLCtx
|
||||
let openDecls ← getOpenDecls
|
||||
let options ← getOptions
|
||||
let scopes := [{header := "", isPublic := true}]
|
||||
let st : ModuleDocstringState := { scopes, openDecls, lctx, options, scopedExts := #[] }
|
||||
modifyEnv fun env =>
|
||||
modDocstringStateExt.setState env st
|
||||
return st
|
||||
|
||||
private def setModState [Monad m] [MonadEnv m] (state : ModuleDocstringState) : m Unit := do
|
||||
modifyEnv fun env =>
|
||||
modDocstringStateExt.setState env state
|
||||
|
||||
/--
|
||||
Runs a documentation elaborator in the module docstring context.
|
||||
-/
|
||||
def DocM.execForModule (act : DocM α) (suggestionMode : SuggestionMode := .interactive) :
|
||||
TermElabM α := withoutModifyingEnv do
|
||||
let sc ← scopedEnvExtensionsRef.get
|
||||
let st ← getModState
|
||||
try
|
||||
scopedEnvExtensionsRef.set st.scopedExts
|
||||
let ((v, _), _) ←
|
||||
act.run { suggestionMode } |>.run {} |>.run st.toState
|
||||
pure v
|
||||
finally
|
||||
scopedEnvExtensionsRef.set sc
|
||||
|
||||
open Lean.Parser.Term in
|
||||
/--
|
||||
Runs a documentation elaborator, discarding changes made to the environment.
|
||||
Runs a documentation elaborator in a declaration's context, discarding changes made to the
|
||||
environment.
|
||||
-/
|
||||
def DocM.exec (declName : Name) (binders : Syntax) (act : DocM α)
|
||||
(suggestionMode : SuggestionMode := .interactive) :
|
||||
TermElabM α := withoutModifyingEnv do
|
||||
let some ci := (← getEnv).constants.find? declName
|
||||
| throwError "Unknown constant {declName} when building docstring"
|
||||
let (lctx, localInstances) ← buildContext ci.type binders
|
||||
let sc ← scopedEnvExtensionsRef.get
|
||||
let st ← Term.saveState
|
||||
Core.resetMessageLog -- We'll replay the messages after the elab loop
|
||||
try
|
||||
let openDecls ← getOpenDecls
|
||||
let options ← getOptions
|
||||
let scopes := [{header := "", isPublic := true}]
|
||||
let ((v, _), _) ← withTheReader Meta.Context (fun ρ => { ρ with localInstances }) <|
|
||||
act.run { declName, suggestionMode } |>.run {} |>.run { scopes, openDecls, lctx, options }
|
||||
pure v
|
||||
let (lctx, localInstances) ← buildContext ci.type binders
|
||||
let sc ← scopedEnvExtensionsRef.get
|
||||
try
|
||||
let openDecls ← getOpenDecls
|
||||
let options ← getOptions
|
||||
let scopes := [{header := "", isPublic := true}]
|
||||
let ((v, _), _) ← withTheReader Meta.Context (fun ρ => { ρ with localInstances }) <|
|
||||
act.run { suggestionMode } |>.run {} |>.run { scopes, openDecls, lctx, options }
|
||||
pure v
|
||||
finally
|
||||
scopedEnvExtensionsRef.set sc
|
||||
finally
|
||||
scopedEnvExtensionsRef.set sc
|
||||
let msgs ← Core.getMessageLog
|
||||
st.restore
|
||||
Core.setMessageLog ((← Core.getMessageLog) ++ msgs)
|
||||
where
|
||||
buildContext (type : Expr) (binders : Syntax) : TermElabM (LocalContext × LocalInstances) := do
|
||||
-- Create a local context with all binders
|
||||
-- Create a local context with all binders. The type will be updated as we introduce parameters.
|
||||
let mut type := type
|
||||
let mut localInstances := (← readThe Meta.Context).localInstances
|
||||
let mut lctx := ← getLCtx
|
||||
|
||||
-- We start with a local context that's reset to only include section variables
|
||||
let mut localInstances ← Meta.getLocalInstances
|
||||
let mut lctx ← getLCtx
|
||||
let sectionFVars := (← read).sectionFVars.valuesArray.filterMap fun
|
||||
| .fvar fv => some fv
|
||||
| _ => none
|
||||
repeat
|
||||
if lctx.size = 0 then break
|
||||
if let some decl := lctx.lastDecl then
|
||||
if sectionFVars.any (· == decl.fvarId) then break
|
||||
else
|
||||
lctx := lctx.pop
|
||||
localInstances := localInstances.filter (·.fvar != .fvar decl.fvarId)
|
||||
else break
|
||||
|
||||
let names ← binders.getArgs.flatMapM binderNames
|
||||
let mut i := 0
|
||||
let mut x := none
|
||||
|
|
@ -724,6 +786,8 @@ builtin_initialize registerBuiltinAttribute {
|
|||
let ret := .app (.const ``Inline [0]) (.const ``ElabInline [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {}
|
||||
docRoleExt.add (roleName, wrapper)
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
}
|
||||
|
||||
/--
|
||||
|
|
@ -749,8 +813,12 @@ builtin_initialize registerBuiltinAttribute {
|
|||
(mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `inline) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind [])))
|
||||
let ret := .app (.const ``Inline [0]) (.const ``ElabInline [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {}
|
||||
addDeclarationRangesFromSyntax wrapper stx
|
||||
declareBuiltin roleName <|
|
||||
mkApp3 (.const ``addBuiltinDocRole []) (toExpr roleName) (toExpr wrapper) (.const wrapper [])
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
declareBuiltinDocStringAndRanges wrapper
|
||||
}
|
||||
|
||||
builtin_initialize registerBuiltinAttribute {
|
||||
|
|
@ -766,6 +834,8 @@ builtin_initialize registerBuiltinAttribute {
|
|||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some (.const ``StrLit [])) ret |>.run {} {} |>.run {} {}
|
||||
docCodeBlockExt.add (blockName, wrapper)
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
}
|
||||
|
||||
/--
|
||||
|
|
@ -788,9 +858,13 @@ builtin_initialize registerBuiltinAttribute {
|
|||
pure decl
|
||||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some (.const ``StrLit [])) ret |>.run {} {} |>.run {} {}
|
||||
addDeclarationRangesFromSyntax wrapper stx
|
||||
declareBuiltin blockName <|
|
||||
mkApp3 (.const ``addBuiltinDocCodeBlock [])
|
||||
(toExpr blockName) (toExpr wrapper) (.const wrapper [])
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
declareBuiltinDocStringAndRanges wrapper
|
||||
}
|
||||
|
||||
/-- A suggestion about an applicable code block -/
|
||||
|
|
@ -872,6 +946,9 @@ builtin_initialize registerBuiltinAttribute {
|
|||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {}
|
||||
docDirectiveExt.add (directiveName, wrapper)
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
|
||||
}
|
||||
|
||||
/--
|
||||
|
|
@ -897,9 +974,13 @@ builtin_initialize registerBuiltinAttribute {
|
|||
(mkApp3 (.const ``List.cons [0]) (.const ``SyntaxNodeKind []) (toExpr `block) (.app (.const ``List.nil [0]) (.const ``SyntaxNodeKind [])))
|
||||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl (some argTy) ret |>.run {} {} |>.run {} {}
|
||||
addDeclarationRangesFromSyntax wrapper stx
|
||||
declareBuiltin directiveName <|
|
||||
mkApp3 (.const ``addBuiltinDocDirective [])
|
||||
(toExpr directiveName) (toExpr wrapper) (.const wrapper [])
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
declareBuiltinDocStringAndRanges wrapper
|
||||
}
|
||||
|
||||
builtin_initialize registerBuiltinAttribute {
|
||||
|
|
@ -916,6 +997,8 @@ builtin_initialize registerBuiltinAttribute {
|
|||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl none ret |>.run {} {} |>.run {} {}
|
||||
docCommandExt.add (commandName, wrapper)
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
}
|
||||
|
||||
/--
|
||||
|
|
@ -939,9 +1022,13 @@ builtin_initialize registerBuiltinAttribute {
|
|||
|
||||
let ret := mkApp2 (.const ``Block [0, 0]) (.const ``ElabInline []) (.const ``ElabBlock [])
|
||||
let ((wrapper, _), _) ← genWrapper decl none ret |>.run {} {} |>.run {} {}
|
||||
addDeclarationRangesFromSyntax wrapper stx
|
||||
declareBuiltin commandName <|
|
||||
mkApp3 (.const ``addBuiltinDocCommand [])
|
||||
(toExpr commandName) (toExpr wrapper) (.const wrapper [])
|
||||
if (← findInternalDocString? (← getEnv) decl).isSome then
|
||||
addInheritedDocString wrapper decl
|
||||
declareBuiltinDocStringAndRanges wrapper
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -959,76 +1046,88 @@ private unsafe def codeBlockSuggestionsUnsafe : TermElabM (Array (StrLit → Doc
|
|||
@[implemented_by codeBlockSuggestionsUnsafe]
|
||||
private opaque codeBlockSuggestions : TermElabM (Array (StrLit → DocM (Array CodeBlockSuggestion)))
|
||||
|
||||
private unsafe def roleExpandersForUnsafe (roleName : Ident) : TermElabM (Array (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline))) := do
|
||||
private def builtinElabName (n : Name) : Option Name :=
|
||||
if (`Lean.Doc).isPrefixOf n then some n
|
||||
else if n matches (.str .anonymous _) then some <| `Lean.Doc ++ n
|
||||
else none
|
||||
|
||||
private unsafe def roleExpandersForUnsafe (roleName : Ident) :
|
||||
TermElabM (Array (Name × (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline)))) := do
|
||||
let x? ←
|
||||
try some <$> realizeGlobalConstNoOverloadWithInfo roleName
|
||||
try some <$> realizeGlobalConstNoOverload roleName
|
||||
catch | _ => pure none
|
||||
if let some x := x? then
|
||||
let names := (docRoleExt.getState (← getEnv)).get? x |>.getD #[]
|
||||
let builtins := (← builtinDocRoles.get).get? x |>.getD #[]
|
||||
return (← names.mapM (evalConst _)) ++ builtins.map (·.2)
|
||||
return (← names.mapM (fun x => do return (x, ← evalConst _ x))) ++ builtins
|
||||
else
|
||||
let x := roleName.getId
|
||||
let hasBuiltin :=
|
||||
(← builtinDocRoles.get).get? x <|> (← builtinDocRoles.get).get? (`Lean.Doc ++ x)
|
||||
return hasBuiltin.toArray.flatten.map (·.2)
|
||||
return hasBuiltin.toArray.flatten
|
||||
|
||||
|
||||
@[implemented_by roleExpandersForUnsafe]
|
||||
private opaque roleExpandersFor (roleName : Ident) :
|
||||
TermElabM (Array (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline)))
|
||||
TermElabM (Array (Name × (TSyntaxArray `inline → StateT (Array (TSyntax `doc_arg)) DocM (Inline ElabInline))))
|
||||
|
||||
private unsafe def codeBlockExpandersForUnsafe (codeBlockName : Ident) : TermElabM (Array (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do
|
||||
private unsafe def codeBlockExpandersForUnsafe (codeBlockName : Ident) :
|
||||
TermElabM (Array (Name × (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))) := do
|
||||
let x? ←
|
||||
try some <$> realizeGlobalConstNoOverloadWithInfo codeBlockName
|
||||
try some <$> realizeGlobalConstNoOverload codeBlockName
|
||||
catch | _ => pure none
|
||||
if let some x := x? then
|
||||
let names := (docCodeBlockExt.getState (← getEnv)).get? x |>.getD #[]
|
||||
let names' := (← builtinDocCodeBlocks.get).get? x |>.getD #[]
|
||||
return (← names.mapM (evalConst _)) ++ names'.map (·.2)
|
||||
return (← names.mapM (fun x => do return (x, ← evalConst _ x))) ++ names'
|
||||
else
|
||||
let x := codeBlockName.getId
|
||||
let hasBuiltin :=
|
||||
(← builtinDocCodeBlocks.get).get? x <|> (← builtinDocCodeBlocks.get).get? (`Lean.Doc ++ x)
|
||||
return hasBuiltin.toArray.flatten.map (·.2)
|
||||
return hasBuiltin.toArray.flatten
|
||||
|
||||
|
||||
@[implemented_by codeBlockExpandersForUnsafe]
|
||||
private opaque codeBlockExpandersFor (codeBlockName : Ident) : TermElabM (Array (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))
|
||||
private opaque codeBlockExpandersFor (codeBlockName : Ident) :
|
||||
TermElabM (Array (Name × (StrLit → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))))
|
||||
|
||||
private unsafe def directiveExpandersForUnsafe (directiveName : Ident) : TermElabM (Array (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do
|
||||
private unsafe def directiveExpandersForUnsafe (directiveName : Ident) :
|
||||
TermElabM (Array (Name × (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))) := do
|
||||
let x? ←
|
||||
try some <$> realizeGlobalConstNoOverloadWithInfo directiveName
|
||||
try some <$> realizeGlobalConstNoOverload directiveName
|
||||
catch | _ => pure none
|
||||
if let some x := x? then
|
||||
let names := (docDirectiveExt.getState (← getEnv)).get? x |>.getD #[]
|
||||
let names' := (← builtinDocDirectives.get).get? x |>.getD #[]
|
||||
return (← names.mapM (evalConst _)) ++ names'.map (·.2)
|
||||
return (← names.mapM (fun x => do return (x, ← evalConst _ x))) ++ names'
|
||||
else
|
||||
let x := directiveName.getId
|
||||
let hasBuiltin :=
|
||||
(← builtinDocDirectives.get).get? x <|> (← builtinDocDirectives.get).get? (`Lean.Doc ++ x)
|
||||
return hasBuiltin.toArray.flatten.map (·.2)
|
||||
return hasBuiltin.toArray.flatten
|
||||
|
||||
@[implemented_by directiveExpandersForUnsafe]
|
||||
private opaque directiveExpandersFor (directiveName : Ident) : TermElabM (Array (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))
|
||||
private opaque directiveExpandersFor (directiveName : Ident) :
|
||||
TermElabM (Array (Name × (TSyntaxArray `block → StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))))
|
||||
|
||||
private unsafe def commandExpandersForUnsafe (commandName : Ident) : TermElabM (Array (StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do
|
||||
private unsafe def commandExpandersForUnsafe (commandName : Ident) :
|
||||
TermElabM (Array (Name × StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock))) := do
|
||||
let x? ←
|
||||
try some <$> realizeGlobalConstNoOverloadWithInfo commandName
|
||||
try some <$> realizeGlobalConstNoOverload commandName
|
||||
catch | _ => pure none
|
||||
if let some x := x? then
|
||||
let names := (docCommandExt.getState (← getEnv)).get? x |>.getD #[]
|
||||
let names' := (← builtinDocCommands.get).get? x |>.getD #[]
|
||||
return (← names.mapM (evalConst _)) ++ names'.map (·.2)
|
||||
return (← names.mapM (fun x => do return (x, ← evalConst _ x))) ++ names'
|
||||
else
|
||||
let x := commandName.getId
|
||||
let hasBuiltin :=
|
||||
(← builtinDocCommands.get).get? x <|> (← builtinDocCommands.get).get? (`Lean.Doc ++ x)
|
||||
return hasBuiltin.toArray.flatten.map (·.2)
|
||||
return hasBuiltin.toArray.flatten
|
||||
|
||||
@[implemented_by commandExpandersForUnsafe]
|
||||
private opaque commandExpandersFor (commandName : Ident) : TermElabM (Array (StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))
|
||||
private opaque commandExpandersFor (commandName : Ident) :
|
||||
TermElabM (Array (Name × StateT (Array (TSyntax `doc_arg)) DocM (Block ElabInline ElabBlock)))
|
||||
|
||||
|
||||
private def mkArgVal (arg : TSyntax `arg_val) : DocM Term :=
|
||||
|
|
@ -1117,12 +1216,17 @@ private def mkSuggestion (ref : Syntax) (hintTitle : MessageData) (newStrings :
|
|||
toMessageData <| Diff.linesToString <| d.filter (·.1 != Action.skip)
|
||||
pure m!"\n\nHint: {hintTitle}\n{indentD <| m!"\n".joinSep edits.toList}"
|
||||
|
||||
def nameOrBuiltinName [Monad m] [MonadEnv m] (x : Name) : m Name := do
|
||||
let env ← getEnv
|
||||
if env.contains x then return x
|
||||
else return `Lean.Doc ++ x
|
||||
|
||||
/--
|
||||
Elaborates the syntax of an inline document element to an actual inline document element.
|
||||
-/
|
||||
public partial def elabInline (stx : TSyntax `inline) : DocM (Inline ElabInline) :=
|
||||
withRef stx do
|
||||
withRef stx <|
|
||||
withInfoContext (mkInfo := pure <| .ofDocInfo {elaborator := decl_name%, stx := stx}) do
|
||||
match stx with
|
||||
| `(inline|$s:str) =>
|
||||
return .text s.getString
|
||||
|
|
@ -1154,14 +1258,15 @@ public partial def elabInline (stx : TSyntax `inline) : DocM (Inline ElabInline)
|
|||
unless suggestions.isEmpty do
|
||||
let text ← getFileMap
|
||||
let str := text.source.extract b e
|
||||
let ss : Array (String × Option String × Option String) ← suggestions.mapM fun {role, args, moreInfo} => do
|
||||
pure {
|
||||
fst :=
|
||||
"{" ++ (← suggestionName role).toString ++
|
||||
(args.map (" " ++ ·)).getD "" ++ "}" ++ str,
|
||||
snd.fst := none
|
||||
snd.snd := moreInfo.map withSpace
|
||||
}
|
||||
let ss : Array (String × Option String × Option String) ←
|
||||
suggestions.mapM fun {role, args, moreInfo} => do
|
||||
pure {
|
||||
fst :=
|
||||
"{" ++ (← suggestionName role).toString ++
|
||||
(args.map (" " ++ ·)).getD "" ++ "}" ++ str,
|
||||
snd.fst := none
|
||||
snd.snd := moreInfo.map withSpace
|
||||
}
|
||||
let ss := ss.qsort (fun x y => x.1 < y.1)
|
||||
let hint ← mkSuggestion stx m!"Insert a role to document it:" ss
|
||||
logWarning m!"Code element could be more specific.{hint}"
|
||||
|
|
@ -1172,9 +1277,15 @@ public partial def elabInline (stx : TSyntax `inline) : DocM (Inline ElabInline)
|
|||
return .math .display s.getString
|
||||
| `(inline|role{$name $args*}[$inl*]) =>
|
||||
let expanders ← roleExpandersFor name
|
||||
for ex in expanders do
|
||||
for (exName, ex) in expanders do
|
||||
try
|
||||
let res ← ex inl args <&> (·.1)
|
||||
pushInfoLeaf <| .ofDocElabInfo {
|
||||
elaborator := exName,
|
||||
stx := name,
|
||||
name := exName,
|
||||
kind := .role
|
||||
}
|
||||
return res
|
||||
catch
|
||||
| e@(.internal id _) =>
|
||||
|
|
@ -1193,7 +1304,8 @@ where
|
|||
Elaborates the syntax of an block-level document element to an actual block-level document element.
|
||||
-/
|
||||
public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline ElabBlock) :=
|
||||
withRef stx do
|
||||
withRef stx <|
|
||||
withInfoContext (mkInfo := pure <| .ofDocInfo {elaborator := decl_name%, stx := stx}) do
|
||||
match stx with
|
||||
| `(block|para[$inls*]) =>
|
||||
.para <$> inls.mapM elabInline
|
||||
|
|
@ -1231,9 +1343,15 @@ public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline Ela
|
|||
return .empty
|
||||
| `(block| ::: $name $args* { $content*}) =>
|
||||
let expanders ← directiveExpandersFor name
|
||||
for ex in expanders do
|
||||
for (exName, ex) in expanders do
|
||||
try
|
||||
let res ← ex content args <&> (·.1)
|
||||
pushInfoLeaf <| .ofDocElabInfo {
|
||||
elaborator := exName,
|
||||
stx := name,
|
||||
name := exName,
|
||||
kind := .directive
|
||||
}
|
||||
return res
|
||||
catch
|
||||
| e@(.internal id _) =>
|
||||
|
|
@ -1253,24 +1371,30 @@ public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline Ela
|
|||
unless suggestions.isEmpty do
|
||||
let text ← getFileMap
|
||||
let str := text.source.extract b e
|
||||
let ss : Array (String × Option String × Option String) ← suggestions.mapM fun {name, args, moreInfo} => do
|
||||
pure {
|
||||
fst :=
|
||||
str ++ (← suggestionName name).toString ++
|
||||
(args.map (" " ++ ·)).getD "",
|
||||
snd.fst := moreInfo.map withSpace
|
||||
snd.snd := none
|
||||
}
|
||||
let ss : Array (String × Option String × Option String) ←
|
||||
suggestions.mapM fun {name, args, moreInfo} => do
|
||||
pure {
|
||||
fst :=
|
||||
str ++ (← suggestionName name).toString ++
|
||||
(args.map (" " ++ ·)).getD "",
|
||||
snd.fst := moreInfo.map withSpace
|
||||
snd.snd := none
|
||||
}
|
||||
let ss := ss.qsort (fun x y => x.1 < y.1)
|
||||
let hint ← mkSuggestion opener m!"Insert a specific kind of code block:" ss
|
||||
logWarning m!"Code block could be more specific.{hint}"
|
||||
|
||||
return .code s.getString
|
||||
| `(block| ```$name $args* | $s ```) =>
|
||||
let expanders ← codeBlockExpandersFor name
|
||||
for ex in expanders do
|
||||
for (exName, ex) in expanders do
|
||||
try
|
||||
let res ← ex s args <&> (·.1)
|
||||
pushInfoLeaf <| .ofDocElabInfo {
|
||||
elaborator := exName,
|
||||
stx := name,
|
||||
name := exName,
|
||||
kind := .codeBlock
|
||||
}
|
||||
return res
|
||||
catch
|
||||
| e@(.internal id _) =>
|
||||
|
|
@ -1281,9 +1405,15 @@ public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline Ela
|
|||
throwErrorAt name "No code block expander for `{name}`"
|
||||
| `(block| command{$name $args*}) =>
|
||||
let expanders ← commandExpandersFor name
|
||||
for ex in expanders do
|
||||
for (exName, ex) in expanders do
|
||||
try
|
||||
let res ← ex args <&> (·.1)
|
||||
pushInfoLeaf <| .ofDocElabInfo {
|
||||
elaborator := exName,
|
||||
stx := name,
|
||||
name := exName,
|
||||
kind := .command
|
||||
}
|
||||
return res
|
||||
catch
|
||||
| e@(.internal id _) =>
|
||||
|
|
@ -1292,6 +1422,12 @@ public partial def elabBlock (stx : TSyntax `block) : DocM (Block ElabInline Ela
|
|||
else throw e
|
||||
| e => throw e
|
||||
throwErrorAt name "No document command elaborator for `{name}`"
|
||||
| `(block|%%%$_*%%%) =>
|
||||
let h ←
|
||||
if stx.raw.getRange?.isSome then m!"Remove it".hint #[""] (ref? := stx)
|
||||
else pure m!""
|
||||
logError m!"Part metadata is not supported in docstrings.{h}"
|
||||
return .empty
|
||||
| other => throwErrorAt other "Unsupported syntax: {other}"
|
||||
where
|
||||
withSpace (s : String) : String :=
|
||||
|
|
@ -1314,10 +1450,13 @@ private partial def elabBlocks' (level : Nat) :
|
|||
else if n = level then
|
||||
set xs
|
||||
let (content, subParts) ← elabBlocks' (level + 1)
|
||||
let title ← liftM <| name.mapM elabInline
|
||||
let title ←
|
||||
liftM <| withInfoContext (mkInfo := pure <| .ofDocInfo {elaborator := `no_elab, stx := x}) <|
|
||||
name.mapM elabInline
|
||||
let mdTitle := ToMarkdown.toMarkdown (Inline.concat title) |>.run'
|
||||
sub := sub.push {
|
||||
title,
|
||||
titleString := "" -- TODO get string from filemap?
|
||||
titleString := mdTitle
|
||||
metadata := none
|
||||
content, subParts
|
||||
}
|
||||
|
|
@ -1331,83 +1470,119 @@ private partial def elabBlocks' (level : Nat) :
|
|||
catch
|
||||
| e =>
|
||||
logErrorAt e.getRef e.toMessageData
|
||||
else break
|
||||
else
|
||||
break
|
||||
return (pre, sub)
|
||||
|
||||
private def elabModSnippet'
|
||||
(range : DeclarationRange) (level : Nat) (blocks : TSyntaxArray `block) :
|
||||
DocM VersoModuleDocs.Snippet := do
|
||||
let mut snippet : VersoModuleDocs.Snippet := {
|
||||
declarationRange := range
|
||||
}
|
||||
let mut maxLevel := level
|
||||
for b in blocks do
|
||||
if let `(block|header($n){$name*}) := b then
|
||||
let n := n.getNat
|
||||
if n > maxLevel then
|
||||
logErrorAt b m!"Incorrect header nesting: expected at most `{"".pushn '#' n}`"
|
||||
else
|
||||
let title ←
|
||||
liftM <| withInfoContext (mkInfo := pure <| .ofDocInfo {elaborator := `no_elab, stx := b}) <|
|
||||
name.mapM elabInline
|
||||
let some headerRange ← getDeclarationRange? b
|
||||
| throwErrorAt b "Can't find header source position"
|
||||
let mdTitle := ToMarkdown.toMarkdown (Inline.concat title) |>.run'
|
||||
snippet := snippet.addPart n headerRange {
|
||||
title,
|
||||
titleString := mdTitle
|
||||
metadata := none, content := #[], subParts := #[]
|
||||
}
|
||||
else
|
||||
snippet := snippet.addBlock (← elabBlock b)
|
||||
return snippet
|
||||
|
||||
private partial def fixupInline (inl : Inline ElabInline) : DocM (Inline ElabInline) := do
|
||||
match inl with
|
||||
| .concat xs => .concat <$> xs.mapM fixupInline
|
||||
| .emph xs => .emph <$> xs.mapM fixupInline
|
||||
| .bold xs => .bold <$> xs.mapM fixupInline
|
||||
| .link content url => (.link · url) <$> content.mapM fixupInline
|
||||
| .footnote name content => .footnote name <$> content.mapM fixupInline
|
||||
| .text s => pure (.text s)
|
||||
| .image alt url => pure (.image alt url)
|
||||
| .code s => pure (.code s)
|
||||
| .math mode s => pure (.math mode s)
|
||||
| .linebreak s => pure (.linebreak s)
|
||||
| .other i@{ name, val } xs =>
|
||||
match name with
|
||||
| ``delayLink =>
|
||||
let some {name} := val.get? ElabLink
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content := url, seen, .. } := (← getThe InternalState).urls[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } }
|
||||
return .link (← xs.mapM fixupInline) url
|
||||
else
|
||||
logErrorAt name "Reference not found"
|
||||
return .concat (← xs.mapM fixupInline)
|
||||
| ``delayImage =>
|
||||
let some {alt, name} := val.get? ElabImage
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content := url, seen, ..} := (← getThe InternalState).urls[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } }
|
||||
return .image alt url
|
||||
else
|
||||
logErrorAt name "Reference not found"
|
||||
return .empty
|
||||
| ``delayFootnote =>
|
||||
let some {name} := val.get? ElabFootnote
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content, seen, ..} := (← getThe InternalState).footnotes[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st =>
|
||||
{ st with footnotes := st.footnotes.insert nameStr { r with seen := true } }
|
||||
return .footnote nameStr #[← fixupInline content]
|
||||
else
|
||||
logErrorAt name "Footnote not found"
|
||||
return .empty
|
||||
| _ => .other i <$> xs.mapM fixupInline
|
||||
|
||||
private partial def fixupBlock (block : Block ElabInline ElabBlock) : DocM (Block ElabInline ElabBlock) := do
|
||||
match block with
|
||||
| .para xs => .para <$> xs.mapM fixupInline
|
||||
| .concat xs => .concat <$> xs.mapM fixupBlock
|
||||
| .blockquote xs => .blockquote <$> xs.mapM fixupBlock
|
||||
| .dl xs => .dl <$> xs.mapM fun { term, desc } => do
|
||||
let term ← term.mapM fixupInline
|
||||
let desc ← desc.mapM fixupBlock
|
||||
pure { term, desc }
|
||||
| .ul xs => .ul <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixupBlock⟩
|
||||
| .ol n xs => .ol n <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixupBlock⟩
|
||||
| .code s => pure (.code s)
|
||||
| .other i xs => .other i <$> xs.mapM fixupBlock
|
||||
|
||||
private partial def fixupPart (part : Part ElabInline ElabBlock Empty) : DocM (Part ElabInline ElabBlock Empty) := do
|
||||
return { part with
|
||||
title := ← part.title.mapM fixupInline
|
||||
content := ← part.content.mapM fixupBlock,
|
||||
subParts := ← part.subParts.mapM fixupPart
|
||||
}
|
||||
|
||||
|
||||
private partial def fixupBlocks : (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) → DocM (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty))
|
||||
| (bs, ps) => do
|
||||
let bs ← bs.mapM fixB
|
||||
let ps ← ps.mapM fixP
|
||||
let bs ← bs.mapM fixupBlock
|
||||
let ps ← ps.mapM fixupPart
|
||||
return (bs, ps)
|
||||
where
|
||||
fixI (inl : Inline ElabInline) : DocM (Inline ElabInline) := do
|
||||
match inl with
|
||||
| .concat xs => .concat <$> xs.mapM fixI
|
||||
| .emph xs => .emph <$> xs.mapM fixI
|
||||
| .bold xs => .bold <$> xs.mapM fixI
|
||||
| .link content url => (.link · url) <$> content.mapM fixI
|
||||
| .footnote name content => .footnote name <$> content.mapM fixI
|
||||
| .text s => pure (.text s)
|
||||
| .image alt url => pure (.image alt url)
|
||||
| .code s => pure (.code s)
|
||||
| .math mode s => pure (.math mode s)
|
||||
| .linebreak s => pure (.linebreak s)
|
||||
| .other i@{ name, val } xs =>
|
||||
match name with
|
||||
| ``delayLink =>
|
||||
let some {name} := val.get? ElabLink
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content := url, seen, .. } := (← getThe InternalState).urls[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } }
|
||||
return .link (← xs.mapM fixI) url
|
||||
else
|
||||
logErrorAt name "Reference not found"
|
||||
return .concat (← xs.mapM fixI)
|
||||
| ``delayImage =>
|
||||
let some {alt, name} := val.get? ElabImage
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content := url, seen, ..} := (← getThe InternalState).urls[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st => { st with urls := st.urls.insert nameStr { r with seen := true } }
|
||||
return .image alt url
|
||||
else
|
||||
logErrorAt name "Reference not found"
|
||||
return .empty
|
||||
| ``delayFootnote =>
|
||||
let some {name} := val.get? ElabFootnote
|
||||
| throwError "Wrong value for {name}: {val.typeName}"
|
||||
let nameStr := name.getString
|
||||
if let some r@{content, seen, ..} := (← getThe InternalState).footnotes[nameStr]? then
|
||||
unless seen do modifyThe InternalState fun st =>
|
||||
{ st with footnotes := st.footnotes.insert nameStr { r with seen := true } }
|
||||
return .footnote nameStr #[← fixI content]
|
||||
else
|
||||
logErrorAt name "Footnote not found"
|
||||
return .empty
|
||||
| _ => .other i <$> xs.mapM fixI
|
||||
|
||||
fixB (block : Block ElabInline ElabBlock) : DocM (Block ElabInline ElabBlock) := do
|
||||
match block with
|
||||
| .para xs => .para <$> xs.mapM fixI
|
||||
| .concat xs => .concat <$> xs.mapM fixB
|
||||
| .blockquote xs => .blockquote <$> xs.mapM fixB
|
||||
| .dl xs => .dl <$> xs.mapM fun { term, desc } => do
|
||||
let term ← term.mapM fixI
|
||||
let desc ← desc.mapM fixB
|
||||
pure { term, desc }
|
||||
| .ul xs => .ul <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixB⟩
|
||||
| .ol n xs => .ol n <$> xs.mapM fun ⟨bs⟩ => do return ⟨← bs.mapM fixB⟩
|
||||
| .code s => pure (.code s)
|
||||
| .other i xs => .other i <$> xs.mapM fixB
|
||||
|
||||
fixP (part : Part ElabInline ElabBlock Empty) : DocM (Part ElabInline ElabBlock Empty) := do
|
||||
return { part with
|
||||
title := ← part.title.mapM fixI
|
||||
content := ← part.content.mapM fixB,
|
||||
subParts := ← part.subParts.mapM fixP
|
||||
}
|
||||
|
||||
private partial def fixupSnippet (snippet : VersoModuleDocs.Snippet) : DocM VersoModuleDocs.Snippet := do
|
||||
return {snippet with
|
||||
text := ← snippet.text.mapM fixupBlock,
|
||||
sections := ← snippet.sections.mapM fun (level, range, content) => do
|
||||
return (level, range, ← fixupPart content)
|
||||
}
|
||||
/--
|
||||
After fixing up the references, check to see which were not used and emit a suitable warning.
|
||||
-/
|
||||
|
|
@ -1419,10 +1594,19 @@ private def warnUnusedRefs : DocM Unit := do
|
|||
unless seen do
|
||||
logWarningAt location "Unused footnote"
|
||||
|
||||
/-- Elaborates a sequence of blocks into a document -/
|
||||
/-- Elaborates a sequence of blocks into a document. -/
|
||||
public def elabBlocks (blocks : TSyntaxArray `block) :
|
||||
DocM (Array (Block ElabInline ElabBlock) × Array (Part ElabInline ElabBlock Empty)) := do
|
||||
let (v, _) ← elabBlocks' 0 |>.run blocks
|
||||
let res ← fixupBlocks v
|
||||
warnUnusedRefs
|
||||
return res
|
||||
|
||||
/-- Elaborates a sequence of blocks into a module doc snippet. -/
|
||||
public def elabModSnippet
|
||||
(range : DeclarationRange) (blocks : TSyntaxArray `block) (nestingLevel : Nat) :
|
||||
DocM (VersoModuleDocs.Snippet) := do
|
||||
let s ← elabModSnippet' range nestingLevel blocks
|
||||
let s ← fixupSnippet s
|
||||
warnUnusedRefs
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -221,6 +221,12 @@ def DelabTermInfo.format (ctx : ContextInfo) (info : DelabTermInfo) : IO Format
|
|||
def ChoiceInfo.format (ctx : ContextInfo) (info : ChoiceInfo) : Format :=
|
||||
f!"[Choice] @ {formatElabInfo ctx info.toElabInfo}"
|
||||
|
||||
def DocInfo.format (ctx : ContextInfo) (info : DocInfo) : Format :=
|
||||
f!"[Doc] {info.stx.getKind} @ {formatElabInfo ctx info.toElabInfo}"
|
||||
|
||||
def DocElabInfo.format (ctx : ContextInfo) (info : DocElabInfo) : Format :=
|
||||
f!"[DocElab] {info.name} ({repr info.kind}) @ {formatElabInfo ctx info.toElabInfo}"
|
||||
|
||||
def Info.format (ctx : ContextInfo) : Info → IO Format
|
||||
| ofTacticInfo i => i.format ctx
|
||||
| ofTermInfo i => i.format ctx
|
||||
|
|
@ -237,6 +243,8 @@ def Info.format (ctx : ContextInfo) : Info → IO Format
|
|||
| ofFieldRedeclInfo i => pure <| i.format ctx
|
||||
| ofDelabTermInfo i => i.format ctx
|
||||
| ofChoiceInfo i => pure <| i.format ctx
|
||||
| ofDocInfo i => pure <| i.format ctx
|
||||
| ofDocElabInfo i => pure <| i.format ctx
|
||||
|
||||
def Info.toElabInfo? : Info → Option ElabInfo
|
||||
| ofTacticInfo i => some i.toElabInfo
|
||||
|
|
@ -254,6 +262,8 @@ def Info.toElabInfo? : Info → Option ElabInfo
|
|||
| ofFieldRedeclInfo _ => none
|
||||
| ofDelabTermInfo i => some i.toElabInfo
|
||||
| ofChoiceInfo i => some i.toElabInfo
|
||||
| ofDocInfo i => some i.toElabInfo
|
||||
| ofDocElabInfo i => some i.toElabInfo
|
||||
|
||||
/--
|
||||
Helper function for propagating the tactic metavariable context to its children nodes.
|
||||
|
|
|
|||
|
|
@ -207,6 +207,23 @@ the language server provide interactivity even when all overloaded elaborators f
|
|||
-/
|
||||
structure ChoiceInfo extends ElabInfo where
|
||||
|
||||
inductive DocElabKind where
|
||||
| role | codeBlock | directive | command
|
||||
deriving Repr
|
||||
|
||||
/--
|
||||
Indicates that an extensible document elaborator was used here.
|
||||
-/
|
||||
structure DocElabInfo extends ElabInfo where
|
||||
name : Name
|
||||
kind : DocElabKind
|
||||
|
||||
/--
|
||||
Indicates that a piece of syntax was elaborated as documentation.
|
||||
-/
|
||||
structure DocInfo extends ElabInfo where
|
||||
|
||||
|
||||
/-- Header information for a node in `InfoTree`. -/
|
||||
inductive Info where
|
||||
| ofTacticInfo (i : TacticInfo)
|
||||
|
|
@ -224,6 +241,8 @@ inductive Info where
|
|||
| ofFieldRedeclInfo (i : FieldRedeclInfo)
|
||||
| ofDelabTermInfo (i : DelabTermInfo)
|
||||
| ofChoiceInfo (i : ChoiceInfo)
|
||||
| ofDocInfo (i : DocInfo)
|
||||
| ofDocElabInfo (i : DocElabInfo)
|
||||
deriving Inhabited
|
||||
|
||||
/-- The InfoTree is a structure that is generated during elaboration and used
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ module
|
|||
prelude
|
||||
public import Lean.Parser.Term
|
||||
public import Lean.Parser.Do
|
||||
import Lean.DocString.Parser
|
||||
public import Lean.DocString.Formatter
|
||||
meta import Lean.Parser.Basic
|
||||
|
||||
public section
|
||||
|
|
@ -49,6 +51,7 @@ match against a quotation in a command kind's elaborator). -/
|
|||
@[builtin_term_parser low] def quot := leading_parser
|
||||
"`(" >> withoutPosition (incQuotDepth (many1Unbox commandParser)) >> ")"
|
||||
|
||||
|
||||
/--
|
||||
`/-! <text> -/` defines a *module docstring* that can be displayed by documentation generation
|
||||
tools. The string is associated with the corresponding position in the file. It can be used
|
||||
|
|
@ -56,7 +59,8 @@ multiple times in the same file.
|
|||
-/
|
||||
@[builtin_command_parser]
|
||||
def moduleDoc := leading_parser ppDedent <|
|
||||
"/-!" >> commentBody >> ppLine
|
||||
"/-!" >> Doc.Parser.ifVerso versoCommentBody commentBody >> ppLine
|
||||
|
||||
|
||||
def namedPrio := leading_parser
|
||||
atomic (" (" >> nonReservedSymbol "priority") >> " := " >> withoutPosition priorityParser >> ")"
|
||||
|
|
@ -889,6 +893,7 @@ builtin_initialize
|
|||
register_parser_alias optDeclSig
|
||||
register_parser_alias openDecl
|
||||
register_parser_alias docComment
|
||||
register_parser_alias plainDocComment
|
||||
register_parser_alias visibility
|
||||
|
||||
/--
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@ module
|
|||
prelude
|
||||
public import Lean.Parser.Attr
|
||||
public import Lean.Parser.Level
|
||||
public import Lean.Parser.Term.Basic
|
||||
public import Lean.Parser.Term.Basic
|
||||
meta import Lean.Parser.Term.Basic
|
||||
public import Lean.Parser.Term.Doc
|
||||
meta import Lean.Parser.Basic
|
||||
import Lean.DocString.Parser
|
||||
public import Lean.DocString.Formatter
|
||||
|
||||
public section
|
||||
|
||||
|
|
@ -17,6 +22,38 @@ namespace Lean
|
|||
namespace Parser
|
||||
|
||||
namespace Command
|
||||
|
||||
open Lean.Parser in
|
||||
def versoCommentBodyFn : ParserFn := fun c s =>
|
||||
let startPos := s.pos
|
||||
let s := finishCommentBlock (pushMissingOnError := true) 1 c s
|
||||
if !s.hasError then
|
||||
let commentEndPos := s.pos
|
||||
let endPos := c.prev (c.prev commentEndPos)
|
||||
let endPos := if endPos ≤ c.inputString.endPos then endPos else c.inputString.endPos
|
||||
let c' := c.setEndPos endPos (by unfold endPos; split <;> simp [*])
|
||||
let s := Doc.Parser.document {} c' (s.setPos startPos)
|
||||
if s.hasError then
|
||||
s.pushSyntax .missing
|
||||
else
|
||||
(rawFn (Doc.Parser.ignoreFn <| chFn '-' >> chFn '/') (trailingWs := true)) c s
|
||||
else s
|
||||
|
||||
public def versoCommentBody : Parser where
|
||||
fn := nodeFn `Lean.Parser.Command.versoCommentBody versoCommentBodyFn
|
||||
|
||||
@[combinator_parenthesizer versoCommentBody, expose]
|
||||
public def versoCommentBody.parenthesizer := PrettyPrinter.Parenthesizer.visitToken
|
||||
|
||||
open PrettyPrinter Formatter in
|
||||
open Syntax.MonadTraverser in
|
||||
@[combinator_formatter versoCommentBody, expose]
|
||||
public def versoCommentBody.formatter : PrettyPrinter.Formatter := do
|
||||
visitArgs $ do
|
||||
visitAtom `«-/»
|
||||
goLeft
|
||||
Lean.Doc.Parser.document.formatter
|
||||
|
||||
def commentBody : Parser :=
|
||||
{ fn := rawFn (finishCommentBlock (pushMissingOnError := true) 1) (trailingWs := true) }
|
||||
|
||||
|
|
@ -25,86 +62,31 @@ def commentBody.parenthesizer := PrettyPrinter.Parenthesizer.visitToken
|
|||
@[combinator_formatter commentBody, expose]
|
||||
def commentBody.formatter := PrettyPrinter.Formatter.visitAtom Name.anonymous
|
||||
|
||||
/-- A `docComment` parses a "documentation comment" like `/-- foo -/`. This is not treated like
|
||||
/--
|
||||
A `docComment` parses a "documentation comment" like `/-- foo -/`. This is not treated like
|
||||
a regular comment (that is, as whitespace); it is parsed and forms part of the syntax tree structure.
|
||||
|
||||
A `docComment` node contains a `/--` atom and then the remainder of the comment, `foo -/` in this
|
||||
example. Use `TSyntax.getDocString` to extract the body text from a doc string syntax node. -/
|
||||
At parse time, `docComment` checks the value of the `doc.verso` option. If it is true, the contents
|
||||
are parsed as Verso markup. If not, the contents are treated as plain text or Markdown. Use
|
||||
`plainDocComment` to always treat the contents as plain text.
|
||||
|
||||
A plain text doc comment node contains a `/--` atom and then the remainder of the comment, `foo -/`
|
||||
in this example. Use `TSyntax.getDocString` to extract the body text from a doc string syntax node.
|
||||
A Verso comment node contains the `/--` atom, the document's syntax tree, and a closing `-/` atom.
|
||||
-/
|
||||
-- @[builtin_doc] -- FIXME: suppress the hover
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def docComment := leading_parser
|
||||
ppDedent $ "/--" >> ppSpace >> commentBody >> ppLine
|
||||
ppDedent $ "/--" >> ppSpace >> Doc.Parser.ifVerso versoCommentBody commentBody >> ppLine
|
||||
|
||||
@[inherit_doc docComment, run_builtin_parser_attribute_hooks]
|
||||
def plainDocComment : Parser := Doc.Parser.withoutVersoSyntax docComment
|
||||
|
||||
end Command
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinParserAttribute `builtin_tactic_parser ``Category.tactic .both
|
||||
registerBuiltinDynamicParserAttribute `tactic_parser `tactic
|
||||
|
||||
@[inline] def tacticParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `tactic rbp
|
||||
|
||||
@[inline] def convParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `conv rbp
|
||||
|
||||
namespace Tactic
|
||||
|
||||
/-- `sepByIndentSemicolon(p)` parses a sequence of `p` optionally followed by `;`,
|
||||
similar to `manyIndent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
|
||||
the `;` is mandatory. This is used by tactic parsing, so that
|
||||
```
|
||||
example := by
|
||||
skip
|
||||
skip
|
||||
```
|
||||
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
|
||||
@[run_builtin_parser_attribute_hooks, builtin_doc]
|
||||
def sepByIndentSemicolon (p : Parser) : Parser :=
|
||||
sepByIndent p "; " (allowTrailingSep := true)
|
||||
|
||||
/-- `sepBy1IndentSemicolon(p)` parses a (nonempty) sequence of `p` optionally followed by `;`,
|
||||
similar to `many1Indent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
|
||||
the `;` is mandatory. This is used by tactic parsing, so that
|
||||
```
|
||||
example := by
|
||||
skip
|
||||
skip
|
||||
```
|
||||
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
|
||||
@[run_builtin_parser_attribute_hooks, builtin_doc]
|
||||
def sepBy1IndentSemicolon (p : Parser) : Parser :=
|
||||
sepBy1Indent p "; " (allowTrailingSep := true)
|
||||
|
||||
builtin_initialize
|
||||
register_parser_alias sepByIndentSemicolon
|
||||
register_parser_alias sepBy1IndentSemicolon
|
||||
|
||||
@[builtin_doc] def tacticSeq1Indented : Parser := leading_parser
|
||||
sepBy1IndentSemicolon tacticParser
|
||||
/-- The syntax `{ tacs }` is an alternative syntax for `· tacs`.
|
||||
It runs the tactics in sequence, and fails if the goal is not solved. -/
|
||||
@[builtin_doc] def tacticSeqBracketed : Parser := leading_parser
|
||||
"{ " >> sepByIndentSemicolon tacticParser >> ppDedent ppLine >> "}"
|
||||
|
||||
/-- A sequence of tactics in brackets, or a delimiter-free indented sequence of tactics.
|
||||
Delimiter-free indentation is determined by the *first* tactic of the sequence. -/
|
||||
@[builtin_doc] def tacticSeq := leading_parser
|
||||
tacticSeqBracketed <|> tacticSeq1Indented
|
||||
|
||||
/-- Same as [`tacticSeq`] but requires delimiter-free tactic sequence to have strict indentation.
|
||||
The strict indentation requirement only apply to *nested* `by`s, as top-level `by`s do not have a
|
||||
position set. -/
|
||||
@[builtin_doc] def tacticSeqIndentGt := withAntiquot (mkAntiquot "tacticSeq" ``tacticSeq) <| node ``tacticSeq <|
|
||||
tacticSeqBracketed <|> (checkColGt "indented tactic sequence" >> tacticSeq1Indented)
|
||||
|
||||
/- Raw sequence for quotation and grouping -/
|
||||
def seq1 :=
|
||||
node `Lean.Parser.Tactic.seq1 $ sepBy1 tacticParser ";\n" (allowTrailingSep := true)
|
||||
|
||||
end Tactic
|
||||
|
||||
def darrow : Parser := " => "
|
||||
def semicolonOrLinebreak := ";" <|> checkLinebreakBefore >> pushNone
|
||||
|
||||
|
||||
namespace Term
|
||||
|
||||
/-! # Built-in parsers -/
|
||||
|
|
@ -148,81 +130,8 @@ def optSemicolon (p : Parser) : Parser :=
|
|||
Every proposition is propositionally equal to either `True` or `False`. -/
|
||||
@[builtin_term_parser] def prop := leading_parser
|
||||
"Prop"
|
||||
/--
|
||||
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
|
||||
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.
|
||||
|
||||
The way this works is that holes create fresh metavariables.
|
||||
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
|
||||
This is often known as *unification*.
|
||||
|
||||
Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
|
||||
* In `match` patterns, holes are catch-all patterns.
|
||||
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.
|
||||
|
||||
Related concept: implicit parameters are automatically filled in with holes during the elaboration process.
|
||||
|
||||
See also `?m` syntax (synthetic holes).
|
||||
-/
|
||||
@[builtin_term_parser] def hole := leading_parser
|
||||
"_"
|
||||
/--
|
||||
A *synthetic hole* (or *synthetic placeholder*), which stands for an unknown term that should be synthesized using tactics.
|
||||
- `?_` creates a fresh metavariable with an auto-generated name.
|
||||
- `?m` either refers to a pre-existing metavariable named `m` or creates a fresh metavariable with that name.
|
||||
|
||||
In particular, the synthetic hole syntax creates "synthetic opaque metavariables",
|
||||
the same kind of metavariable used to represent goals in the tactic state.
|
||||
|
||||
Synthetic holes are similar to holes in that `_` also creates metavariables,
|
||||
but synthetic opaque metavariables have some different properties:
|
||||
- In tactics such as `refine`, only synthetic holes yield new goals.
|
||||
- During elaboration, unification will not solve for synthetic opaque metavariables, they are "opaque".
|
||||
This is to prevent counterintuitive behavior such as disappearing goals.
|
||||
- When synthetic holes appear under binders, they capture local variables using a more complicated mechanism known as delayed assignment.
|
||||
|
||||
## Delayed assigned metavariables
|
||||
|
||||
This section gives an overview of some technical details of synthetic holes, which you should feel free to skip.
|
||||
Understanding delayed assignments is mainly useful for those who are working on tactics and other metaprogramming.
|
||||
It is included here until there is a suitable place for it in the reference manual.
|
||||
|
||||
When a synthetic hole appears under a binding construct, such as for example `fun (x : α) (y : β) => ?s`,
|
||||
the system creates a *delayed assignment*. This consists of
|
||||
1. A metavariable `?m` of type `(x : α) → (y : β) → γ x y` whose local context is the local context outside the `fun`,
|
||||
where `γ x y` is the type of `?s`. Recall that `x` and `y` appear in the local context of `?s`.
|
||||
2. A delayed assignment record associating `?m` to `?s` and the variables `#[x, y]` in the local context of `?s`
|
||||
|
||||
Then, this function elaborates as `fun (x : α) (y : β) => ?m x y`, where one should understand `x` and `y` here
|
||||
as being De Bruijn indexes, since Lean uses the locally nameless encoding of lambda calculus.
|
||||
|
||||
Once `?s` is fully solved for, in the sense that after metavariable instantiation it is a metavariable-free term `e`,
|
||||
then we can make the assignment `?m := fun (x' : α) (y' : β) => e[x := x', y := y']`.
|
||||
(Implementation note: Lean only instantiates full applications `?m x' y'` of delayed assigned metavariables, to skip forming this function.)
|
||||
This delayed assignment mechanism is essential to the operation of basic tactics like `intro`,
|
||||
and a good mental model is that it is a way to "apply" the metavariable `?s` by substituting values in for some of its local variables.
|
||||
While it would be easier to immediately assign `?s := ?m x y`,
|
||||
delayed assignment preserves `?s` as an unsolved-for metavariable with a local context that still contains `x` and `y`,
|
||||
which is exactly what tactics like `intro` need.
|
||||
|
||||
By default, delayed assigned metavariables pretty print with what they are delayed assigned to.
|
||||
The delayed assigned metavariables themselves can be pretty printed using `set_option pp.mvars.delayed true`.
|
||||
|
||||
For more information, see the "Gruesome details" module docstrings in `Lean.MetavarContext`.
|
||||
-/
|
||||
@[builtin_term_parser] def syntheticHole := leading_parser
|
||||
"?" >> (ident <|> "_")
|
||||
/--
|
||||
The `⋯` term denotes a term that was omitted by the pretty printer.
|
||||
The presence of `⋯` in pretty printer output is controlled by the `pp.deepTerms` and `pp.proofs` options,
|
||||
and these options can be further adjusted using `pp.deepTerms.threshold` and `pp.proofs.threshold`.
|
||||
|
||||
It is only meant to be used for pretty printing.
|
||||
However, in case it is copied and pasted from the Infoview, `⋯` logs a warning and elaborates like `_`.
|
||||
-/
|
||||
@[builtin_term_parser] def omission := leading_parser
|
||||
"⋯"
|
||||
def binderIdent : Parser := ident <|> hole
|
||||
/--
|
||||
The `sorry` term is a temporary placeholder for a missing proof or value.
|
||||
|
||||
|
|
@ -287,8 +196,6 @@ are turned into a new anonymous constructor application. For example,
|
|||
-/
|
||||
@[builtin_term_parser] def anonymousCtor := leading_parser
|
||||
"⟨" >> withoutPosition (withoutForbidden (sepBy termParser ", " (allowTrailingSep := true))) >> "⟩"
|
||||
def optIdent : Parser :=
|
||||
optional (atomic (ident >> " : "))
|
||||
def fromTerm := leading_parser
|
||||
"from " >> termParser
|
||||
def showRhs := fromTerm <|> byTactic'
|
||||
|
|
@ -299,8 +206,6 @@ an optional `x :`, then a term `ty`, then `from val` or `by tac`. -/
|
|||
@[builtin_term_parser] def «suffices» := leading_parser:leadPrec
|
||||
withPosition ("suffices " >> sufficesDecl) >> optSemicolon termParser
|
||||
@[builtin_term_parser] def «show» := leading_parser:leadPrec "show " >> termParser >> ppSpace >> showRhs
|
||||
def typeSpec := leading_parser " : " >> termParser
|
||||
def optType : Parser := optional typeSpec
|
||||
/--
|
||||
`@x` disables automatic insertion of implicit parameters of the constant `x`.
|
||||
`@e` for any term `e` also disables the insertion of implicit lambdas at this position.
|
||||
|
|
@ -313,77 +218,6 @@ In contrast to regular patterns, `e` may be an arbitrary term of the appropriate
|
|||
-/
|
||||
@[builtin_term_parser] def inaccessible := leading_parser
|
||||
".(" >> withoutPosition termParser >> ")"
|
||||
def binderType (requireType := false) : Parser :=
|
||||
if requireType then node nullKind (" : " >> termParser) else optional (" : " >> termParser)
|
||||
def binderTactic := leading_parser
|
||||
atomic (symbol " := " >> " by ") >> Tactic.tacticSeq
|
||||
def binderDefault := leading_parser
|
||||
" := " >> termParser
|
||||
|
||||
open Lean.PrettyPrinter Parenthesizer Syntax.MonadTraverser in
|
||||
@[combinator_parenthesizer Lean.Parser.Term.binderDefault, expose] def binderDefault.parenthesizer : Parenthesizer := do
|
||||
let prec := match (← getCur) with
|
||||
-- must parenthesize to distinguish from `binderTactic`
|
||||
| `(binderDefault| := by $_) => maxPrec
|
||||
| _ => 0
|
||||
visitArgs do
|
||||
term.parenthesizer prec
|
||||
visitToken
|
||||
|
||||
/--
|
||||
Explicit binder, like `(x y : A)` or `(x y)`.
|
||||
Default values can be specified using `(x : A := v)` syntax, and tactics using `(x : A := by tac)`.
|
||||
-/
|
||||
@[builtin_doc] def explicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
"(" >> withoutPosition (many1 binderIdent >> binderType requireType >> optional (binderTactic <|> binderDefault)) >> ")"
|
||||
/--
|
||||
Implicit binder, like `{x y : A}` or `{x y}`.
|
||||
In regular applications, whenever all parameters before it have been specified,
|
||||
then a `_` placeholder is automatically inserted for this parameter.
|
||||
Implicit parameters should be able to be determined from the other arguments and the return type
|
||||
by unification.
|
||||
|
||||
In `@` explicit mode, implicit binders behave like explicit binders.
|
||||
-/
|
||||
@[builtin_doc] def implicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
"{" >> withoutPosition (many1 binderIdent >> binderType requireType) >> "}"
|
||||
def strictImplicitLeftBracket := atomic (group (symbol "{" >> "{")) <|> "⦃"
|
||||
def strictImplicitRightBracket := atomic (group (symbol "}" >> "}")) <|> "⦄"
|
||||
/--
|
||||
Strict-implicit binder, like `⦃x y : A⦄` or `⦃x y⦄`.
|
||||
In contrast to `{ ... }` implicit binders, strict-implicit binders do not automatically insert
|
||||
a `_` placeholder until at least one subsequent explicit parameter is specified.
|
||||
Do *not* use strict-implicit binders unless there is a subsequent explicit parameter.
|
||||
Assuming this rule is followed, for fully applied expressions implicit and strict-implicit binders have the same behavior.
|
||||
|
||||
Example: If `h : ∀ ⦃x : A⦄, x ∈ s → p x` and `hs : y ∈ s`,
|
||||
then `h` by itself elaborates to itself without inserting `_` for the `x : A` parameter,
|
||||
and `h hs` has type `p y`.
|
||||
In contrast, if `h' : ∀ {x : A}, x ∈ s → p x`, then `h` by itself elaborates to have type `?m ∈ s → p ?m`
|
||||
with `?m` a fresh metavariable.
|
||||
-/
|
||||
@[builtin_doc] def strictImplicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
strictImplicitLeftBracket >> many1 binderIdent >>
|
||||
binderType requireType >> strictImplicitRightBracket
|
||||
/--
|
||||
Instance-implicit binder, like `[C]` or `[inst : C]`.
|
||||
In regular applications without `@` explicit mode, it is automatically inserted
|
||||
and solved for by typeclass inference for the specified class `C`.
|
||||
In `@` explicit mode, if `_` is used for an instance-implicit parameter, then it is still solved for by typeclass inference;
|
||||
use `(_)` to inhibit this and have it be solved for by unification instead, like an implicit argument.
|
||||
-/
|
||||
@[builtin_doc] def instBinder := leading_parser ppGroup <|
|
||||
"[" >> withoutPosition (optIdent >> termParser) >> "]"
|
||||
/-- A `bracketedBinder` matches any kind of binder group that uses some kind of brackets:
|
||||
* An explicit binder like `(x y : A)`
|
||||
* An implicit binder like `{x y : A}`
|
||||
* A strict implicit binder, `⦃y z : A⦄` or its ASCII alternative `{{y z : A}}`
|
||||
* An instance binder `[A]` or `[x : A]` (multiple variables are not allowed here)
|
||||
-/
|
||||
@[builtin_doc] def bracketedBinder (requireType := false) :=
|
||||
withAntiquot (mkAntiquot "bracketedBinder" decl_name% (isPseudoKind := true)) <|
|
||||
explicitBinder requireType <|> strictImplicitBinder requireType <|>
|
||||
implicitBinder requireType <|> instBinder
|
||||
|
||||
/-
|
||||
It is feasible to support dependent arrows such as `{α} → α → α` without sacrificing the quality of the error messages for the longer case.
|
||||
|
|
@ -486,34 +320,6 @@ e.g. because it has no constructors.
|
|||
|
||||
@[builtin_term_parser] def «nofun» := leading_parser "nofun"
|
||||
|
||||
/-
|
||||
Syntax category for structure instance notation fields.
|
||||
Does not initialize `registerBuiltinDynamicParserAttribute` since this category is not meant to be user-extensible.
|
||||
-/
|
||||
builtin_initialize
|
||||
registerBuiltinParserAttribute `builtin_structInstFieldDecl_parser ``Category.structInstFieldDecl
|
||||
@[inline] def structInstFieldDeclParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `structInstFieldDecl rbp
|
||||
def optEllipsis := leading_parser
|
||||
optional " .."
|
||||
def structInstArrayRef := leading_parser
|
||||
"[" >> withoutPosition termParser >> "]"
|
||||
def structInstLVal := leading_parser
|
||||
(ident <|> fieldIdx <|> structInstArrayRef) >>
|
||||
many (group ("." >> (ident <|> fieldIdx)) <|> structInstArrayRef)
|
||||
def structInstFieldBinder :=
|
||||
withAntiquot (mkAntiquot "structInstFieldBinder" decl_name% (isPseudoKind := true)) <|
|
||||
binderIdent <|> bracketedBinder
|
||||
def optTypeForStructInst : Parser := optional (atomic (typeSpec >> notFollowedBy "}" "}"))
|
||||
/- `x` is an abbreviation for `x := x` -/
|
||||
def structInstField := ppGroup <| leading_parser
|
||||
structInstLVal >> optional (many (checkColGt >> ppSpace >> structInstFieldBinder) >> optTypeForStructInst >> ppDedent structInstFieldDeclParser)
|
||||
/-
|
||||
Tags the structure instance field syntax with a `Lean.Parser.Term.structInstFields` syntax node.
|
||||
This node is used to enable structure instance field completion in the whitespace
|
||||
of a structure instance notation.
|
||||
-/
|
||||
def structInstFields (p : Parser) : Parser := node `Lean.Parser.Term.structInstFields p
|
||||
/--
|
||||
Structure instance. `{ x := e, ... }` assigns `e` to field `x`, which may be
|
||||
inherited. If `e` is itself a variable called `x`, it can be elided:
|
||||
|
|
|
|||
286
src/Lean/Parser/Term/Basic.lean
Normal file
286
src/Lean/Parser/Term/Basic.lean
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura, Sebastian Ullrich, Mario Carneiro
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.Parser.Attr
|
||||
public import Lean.Parser.Level
|
||||
public import Lean.Parser.Term.Doc
|
||||
meta import Lean.Parser.Basic
|
||||
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean
|
||||
namespace Parser
|
||||
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinParserAttribute `builtin_tactic_parser ``Category.tactic .both
|
||||
registerBuiltinDynamicParserAttribute `tactic_parser `tactic
|
||||
|
||||
@[inline] def tacticParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `tactic rbp
|
||||
|
||||
@[inline] def convParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `conv rbp
|
||||
|
||||
namespace Tactic
|
||||
|
||||
/-- `sepByIndentSemicolon(p)` parses a sequence of `p` optionally followed by `;`,
|
||||
similar to `manyIndent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
|
||||
the `;` is mandatory. This is used by tactic parsing, so that
|
||||
```
|
||||
example := by
|
||||
skip
|
||||
skip
|
||||
```
|
||||
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
|
||||
@[run_builtin_parser_attribute_hooks, builtin_doc]
|
||||
def sepByIndentSemicolon (p : Parser) : Parser :=
|
||||
sepByIndent p "; " (allowTrailingSep := true)
|
||||
|
||||
/-- `sepBy1IndentSemicolon(p)` parses a (nonempty) sequence of `p` optionally followed by `;`,
|
||||
similar to `many1Indent(p ";"?)`, except that if two occurrences of `p` occur on the same line,
|
||||
the `;` is mandatory. This is used by tactic parsing, so that
|
||||
```
|
||||
example := by
|
||||
skip
|
||||
skip
|
||||
```
|
||||
is legal, but `by skip skip` is not - it must be written as `by skip; skip`. -/
|
||||
@[run_builtin_parser_attribute_hooks, builtin_doc]
|
||||
def sepBy1IndentSemicolon (p : Parser) : Parser :=
|
||||
sepBy1Indent p "; " (allowTrailingSep := true)
|
||||
|
||||
builtin_initialize
|
||||
register_parser_alias sepByIndentSemicolon
|
||||
register_parser_alias sepBy1IndentSemicolon
|
||||
|
||||
@[builtin_doc] def tacticSeq1Indented : Parser := leading_parser
|
||||
sepBy1IndentSemicolon tacticParser
|
||||
/-- The syntax `{ tacs }` is an alternative syntax for `· tacs`.
|
||||
It runs the tactics in sequence, and fails if the goal is not solved. -/
|
||||
@[builtin_doc] def tacticSeqBracketed : Parser := leading_parser
|
||||
"{ " >> sepByIndentSemicolon tacticParser >> ppDedent ppLine >> "}"
|
||||
|
||||
/-- A sequence of tactics in brackets, or a delimiter-free indented sequence of tactics.
|
||||
Delimiter-free indentation is determined by the *first* tactic of the sequence. -/
|
||||
@[builtin_doc, run_builtin_parser_attribute_hooks] def tacticSeq := leading_parser
|
||||
tacticSeqBracketed <|> tacticSeq1Indented
|
||||
|
||||
/-- Same as [`tacticSeq`] but requires delimiter-free tactic sequence to have strict indentation.
|
||||
The strict indentation requirement only apply to *nested* `by`s, as top-level `by`s do not have a
|
||||
position set. -/
|
||||
@[builtin_doc, run_builtin_parser_attribute_hooks]
|
||||
def tacticSeqIndentGt := withAntiquot (mkAntiquot "tacticSeq" ``tacticSeq) <| node ``tacticSeq <|
|
||||
tacticSeqBracketed <|> (checkColGt "indented tactic sequence" >> tacticSeq1Indented)
|
||||
|
||||
/- Raw sequence for quotation and grouping -/
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def seq1 :=
|
||||
node `Lean.Parser.Tactic.seq1 $ sepBy1 tacticParser ";\n" (allowTrailingSep := true)
|
||||
|
||||
end Tactic
|
||||
|
||||
namespace Term
|
||||
/--
|
||||
A *hole* (or *placeholder term*), which stands for an unknown term that is expected to be inferred based on context.
|
||||
For example, in `@id _ Nat.zero`, the `_` must be the type of `Nat.zero`, which is `Nat`.
|
||||
|
||||
The way this works is that holes create fresh metavariables.
|
||||
The elaborator is allowed to assign terms to metavariables while it is checking definitional equalities.
|
||||
This is often known as *unification*.
|
||||
|
||||
Normally, all holes must be solved for. However, there are a few contexts where this is not necessary:
|
||||
* In `match` patterns, holes are catch-all patterns.
|
||||
* In some tactics, such as `refine'` and `apply`, unsolved-for placeholders become new goals.
|
||||
|
||||
Related concept: implicit parameters are automatically filled in with holes during the elaboration process.
|
||||
|
||||
See also `?m` syntax (synthetic holes).
|
||||
-/
|
||||
@[builtin_term_parser] def hole := leading_parser
|
||||
"_"
|
||||
/--
|
||||
A *synthetic hole* (or *synthetic placeholder*), which stands for an unknown term that should be synthesized using tactics.
|
||||
- `?_` creates a fresh metavariable with an auto-generated name.
|
||||
- `?m` either refers to a pre-existing metavariable named `m` or creates a fresh metavariable with that name.
|
||||
|
||||
In particular, the synthetic hole syntax creates "synthetic opaque metavariables",
|
||||
the same kind of metavariable used to represent goals in the tactic state.
|
||||
|
||||
Synthetic holes are similar to holes in that `_` also creates metavariables,
|
||||
but synthetic opaque metavariables have some different properties:
|
||||
- In tactics such as `refine`, only synthetic holes yield new goals.
|
||||
- During elaboration, unification will not solve for synthetic opaque metavariables, they are "opaque".
|
||||
This is to prevent counterintuitive behavior such as disappearing goals.
|
||||
- When synthetic holes appear under binders, they capture local variables using a more complicated mechanism known as delayed assignment.
|
||||
|
||||
## Delayed assigned metavariables
|
||||
|
||||
This section gives an overview of some technical details of synthetic holes, which you should feel free to skip.
|
||||
Understanding delayed assignments is mainly useful for those who are working on tactics and other metaprogramming.
|
||||
It is included here until there is a suitable place for it in the reference manual.
|
||||
|
||||
When a synthetic hole appears under a binding construct, such as for example `fun (x : α) (y : β) => ?s`,
|
||||
the system creates a *delayed assignment*. This consists of
|
||||
1. A metavariable `?m` of type `(x : α) → (y : β) → γ x y` whose local context is the local context outside the `fun`,
|
||||
where `γ x y` is the type of `?s`. Recall that `x` and `y` appear in the local context of `?s`.
|
||||
2. A delayed assignment record associating `?m` to `?s` and the variables `#[x, y]` in the local context of `?s`
|
||||
|
||||
Then, this function elaborates as `fun (x : α) (y : β) => ?m x y`, where one should understand `x` and `y` here
|
||||
as being De Bruijn indexes, since Lean uses the locally nameless encoding of lambda calculus.
|
||||
|
||||
Once `?s` is fully solved for, in the sense that after metavariable instantiation it is a metavariable-free term `e`,
|
||||
then we can make the assignment `?m := fun (x' : α) (y' : β) => e[x := x', y := y']`.
|
||||
(Implementation note: Lean only instantiates full applications `?m x' y'` of delayed assigned metavariables, to skip forming this function.)
|
||||
This delayed assignment mechanism is essential to the operation of basic tactics like `intro`,
|
||||
and a good mental model is that it is a way to "apply" the metavariable `?s` by substituting values in for some of its local variables.
|
||||
While it would be easier to immediately assign `?s := ?m x y`,
|
||||
delayed assignment preserves `?s` as an unsolved-for metavariable with a local context that still contains `x` and `y`,
|
||||
which is exactly what tactics like `intro` need.
|
||||
|
||||
By default, delayed assigned metavariables pretty print with what they are delayed assigned to.
|
||||
The delayed assigned metavariables themselves can be pretty printed using `set_option pp.mvars.delayed true`.
|
||||
|
||||
For more information, see the "Gruesome details" module docstrings in `Lean.MetavarContext`.
|
||||
-/
|
||||
@[builtin_term_parser] def syntheticHole := leading_parser
|
||||
"?" >> (ident <|> "_")
|
||||
|
||||
/--
|
||||
The `⋯` term denotes a term that was omitted by the pretty printer.
|
||||
The presence of `⋯` in pretty printer output is controlled by the `pp.deepTerms` and `pp.proofs` options,
|
||||
and these options can be further adjusted using `pp.deepTerms.threshold` and `pp.proofs.threshold`.
|
||||
|
||||
It is only meant to be used for pretty printing.
|
||||
However, in case it is copied and pasted from the Infoview, `⋯` logs a warning and elaborates like `_`.
|
||||
-/
|
||||
@[builtin_term_parser] def omission := leading_parser
|
||||
"⋯"
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def binderIdent : Parser := ident <|> hole
|
||||
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def binderType (requireType := false) : Parser :=
|
||||
if requireType then node nullKind (" : " >> termParser) else optional (" : " >> termParser)
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def binderTactic := leading_parser
|
||||
atomic (symbol " := " >> " by ") >> Tactic.tacticSeq
|
||||
def binderDefault := leading_parser
|
||||
" := " >> termParser
|
||||
|
||||
open Lean.PrettyPrinter Parenthesizer Syntax.MonadTraverser in
|
||||
@[combinator_parenthesizer Lean.Parser.Term.binderDefault, expose] def binderDefault.parenthesizer : Parenthesizer := do
|
||||
let prec := match (← getCur) with
|
||||
-- must parenthesize to distinguish from `binderTactic`
|
||||
| `(binderDefault| := by $_) => maxPrec
|
||||
| _ => 0
|
||||
visitArgs do
|
||||
term.parenthesizer prec
|
||||
visitToken
|
||||
|
||||
/--
|
||||
Explicit binder, like `(x y : A)` or `(x y)`.
|
||||
Default values can be specified using `(x : A := v)` syntax, and tactics using `(x : A := by tac)`.
|
||||
-/
|
||||
@[builtin_doc] def explicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
"(" >> withoutPosition (many1 binderIdent >> binderType requireType >> optional (binderTactic <|> binderDefault)) >> ")"
|
||||
/--
|
||||
Implicit binder, like `{x y : A}` or `{x y}`.
|
||||
In regular applications, whenever all parameters before it have been specified,
|
||||
then a `_` placeholder is automatically inserted for this parameter.
|
||||
Implicit parameters should be able to be determined from the other arguments and the return type
|
||||
by unification.
|
||||
|
||||
In `@` explicit mode, implicit binders behave like explicit binders.
|
||||
-/
|
||||
@[builtin_doc] def implicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
"{" >> withoutPosition (many1 binderIdent >> binderType requireType) >> "}"
|
||||
def strictImplicitLeftBracket := atomic (group (symbol "{" >> "{")) <|> "⦃"
|
||||
def strictImplicitRightBracket := atomic (group (symbol "}" >> "}")) <|> "⦄"
|
||||
/--
|
||||
Strict-implicit binder, like `⦃x y : A⦄` or `⦃x y⦄`.
|
||||
In contrast to `{ ... }` implicit binders, strict-implicit binders do not automatically insert
|
||||
a `_` placeholder until at least one subsequent explicit parameter is specified.
|
||||
Do *not* use strict-implicit binders unless there is a subsequent explicit parameter.
|
||||
Assuming this rule is followed, for fully applied expressions implicit and strict-implicit binders have the same behavior.
|
||||
|
||||
Example: If `h : ∀ ⦃x : A⦄, x ∈ s → p x` and `hs : y ∈ s`,
|
||||
then `h` by itself elaborates to itself without inserting `_` for the `x : A` parameter,
|
||||
and `h hs` has type `p y`.
|
||||
In contrast, if `h' : ∀ {x : A}, x ∈ s → p x`, then `h` by itself elaborates to have type `?m ∈ s → p ?m`
|
||||
with `?m` a fresh metavariable.
|
||||
-/
|
||||
@[builtin_doc] def strictImplicitBinder (requireType := false) := leading_parser ppGroup <|
|
||||
strictImplicitLeftBracket >> many1 binderIdent >>
|
||||
binderType requireType >> strictImplicitRightBracket
|
||||
|
||||
def optIdent : Parser :=
|
||||
optional (atomic (ident >> " : "))
|
||||
|
||||
/--
|
||||
Instance-implicit binder, like `[C]` or `[inst : C]`.
|
||||
In regular applications without `@` explicit mode, it is automatically inserted
|
||||
and solved for by typeclass inference for the specified class `C`.
|
||||
In `@` explicit mode, if `_` is used for an instance-implicit parameter, then it is still solved for by typeclass inference;
|
||||
use `(_)` to inhibit this and have it be solved for by unification instead, like an implicit argument.
|
||||
-/
|
||||
@[builtin_doc] def instBinder := leading_parser ppGroup <|
|
||||
"[" >> withoutPosition (optIdent >> termParser) >> "]"
|
||||
/-- A `bracketedBinder` matches any kind of binder group that uses some kind of brackets:
|
||||
* An explicit binder like `(x y : A)`
|
||||
* An implicit binder like `{x y : A}`
|
||||
* A strict implicit binder, `⦃y z : A⦄` or its ASCII alternative `{{y z : A}}`
|
||||
* An instance binder `[A]` or `[x : A]` (multiple variables are not allowed here)
|
||||
-/
|
||||
@[builtin_doc, run_builtin_parser_attribute_hooks] def bracketedBinder (requireType := false) :=
|
||||
withAntiquot (mkAntiquot "bracketedBinder" decl_name% (isPseudoKind := true)) <|
|
||||
explicitBinder requireType <|> strictImplicitBinder requireType <|>
|
||||
implicitBinder requireType <|> instBinder
|
||||
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def typeSpec := leading_parser " : " >> termParser
|
||||
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def optType : Parser := optional typeSpec
|
||||
|
||||
/-
|
||||
Syntax category for structure instance notation fields.
|
||||
Does not initialize `registerBuiltinDynamicParserAttribute` since this category is not meant to be user-extensible.
|
||||
-/
|
||||
builtin_initialize
|
||||
registerBuiltinParserAttribute `builtin_structInstFieldDecl_parser ``Category.structInstFieldDecl
|
||||
@[inline] def structInstFieldDeclParser (rbp : Nat := 0) : Parser :=
|
||||
categoryParser `structInstFieldDecl rbp
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def optEllipsis := leading_parser
|
||||
optional " .."
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def structInstArrayRef := leading_parser
|
||||
"[" >> withoutPosition termParser >> "]"
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def structInstLVal := leading_parser
|
||||
(ident <|> fieldIdx <|> structInstArrayRef) >>
|
||||
many (group ("." >> (ident <|> fieldIdx)) <|> structInstArrayRef)
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def structInstFieldBinder :=
|
||||
withAntiquot (mkAntiquot "structInstFieldBinder" decl_name% (isPseudoKind := true)) <|
|
||||
binderIdent <|> bracketedBinder
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def optTypeForStructInst : Parser := optional (atomic (typeSpec >> notFollowedBy "}" "}"))
|
||||
/- `x` is an abbreviation for `x := x` -/
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def structInstField := ppGroup <| leading_parser
|
||||
structInstLVal >> optional (many (checkColGt >> ppSpace >> structInstFieldBinder) >> optTypeForStructInst >> ppDedent structInstFieldDeclParser)
|
||||
/-
|
||||
Tags the structure instance field syntax with a `Lean.Parser.Term.structInstFields` syntax node.
|
||||
This node is used to enable structure instance field completion in the whitespace
|
||||
of a structure instance notation.
|
||||
-/
|
||||
@[run_builtin_parser_attribute_hooks]
|
||||
def structInstFields (p : Parser) : Parser := node `Lean.Parser.Term.structInstFields p
|
||||
|
|
@ -7,6 +7,7 @@ module
|
|||
|
||||
prelude
|
||||
public import Lean.Server.Requests
|
||||
import Lean.DocString.Syntax
|
||||
|
||||
public section
|
||||
|
||||
|
|
@ -23,9 +24,13 @@ def noHighlightKinds : Array SyntaxNodeKind := #[
|
|||
``Lean.Parser.Term.type,
|
||||
``Lean.Parser.Term.prop,
|
||||
-- not really keywords
|
||||
`antiquotName,
|
||||
`antiquotName]
|
||||
|
||||
def docKinds : Array SyntaxNodeKind := #[
|
||||
``Lean.Parser.Command.plainDocComment,
|
||||
``Lean.Parser.Command.docComment,
|
||||
``Lean.Parser.Command.moduleDoc]
|
||||
``Lean.Parser.Command.moduleDoc
|
||||
]
|
||||
|
||||
-- TODO: make extensible, or don't
|
||||
/-- Keywords for which a specific semantic token is provided. -/
|
||||
|
|
@ -93,6 +98,131 @@ def computeDeltaLspSemanticTokens (tokens : Array AbsoluteLspSemanticToken) : Se
|
|||
lastPos := pos
|
||||
return { data }
|
||||
|
||||
open Lean.Doc.Syntax in
|
||||
def isVersoKind (k : SyntaxNodeKind) : Bool :=
|
||||
(`Lean.Doc.Syntax).isPrefixOf k
|
||||
|
||||
|
||||
open Lean.Doc.Syntax in
|
||||
private partial def collectVersoTokens
|
||||
(stx : Syntax) (getTokens : (stx : Syntax) → Array LeanSemanticToken) :
|
||||
Array LeanSemanticToken :=
|
||||
go stx |>.run #[] |>.2
|
||||
where
|
||||
tok (tk : Syntax) (k : SemanticTokenType) : StateM (Array LeanSemanticToken) Unit :=
|
||||
modify (·.push ⟨tk, k⟩)
|
||||
|
||||
go (stx : Syntax) : StateM (Array LeanSemanticToken) Unit := do
|
||||
match stx with
|
||||
| `(arg_val| $x:ident )
|
||||
| `(arg_val| $x:str )
|
||||
| `(arg_val| $x:num ) =>
|
||||
tok x .parameter
|
||||
| `(named| (%$tk1 $x:ident :=%$tk2 $v:arg_val )%$tk3) =>
|
||||
tok tk1 .keyword
|
||||
tok x .property
|
||||
tok tk2 .keyword
|
||||
go v
|
||||
tok tk3 .keyword
|
||||
| `(named_no_paren| $x:ident :=%$tk $v:arg_val ) =>
|
||||
tok x .property
|
||||
tok tk .keyword
|
||||
go v
|
||||
| `(flag_on| +%$tk$x) | `(flag_off| -%$tk$x) =>
|
||||
tok tk .keyword
|
||||
tok x .property
|
||||
| `(link_target| [%$tk1 $s ]%$tk2) =>
|
||||
tok tk1 .keyword
|
||||
tok s .string
|
||||
tok tk2 .keyword
|
||||
| `(link_target| (%$tk1 $s )%$tk2) =>
|
||||
tok tk1 .keyword
|
||||
tok s .property
|
||||
tok tk2 .keyword
|
||||
| `(inline|$_:str) | `(inline|line! $_) => pure () -- No tokens for plain text or line breaks
|
||||
| `(inline| *[%$tk1 $inls* ]%$tk2) | `(inline|_[%$tk1 $inls* ]%$tk2) =>
|
||||
tok tk1 .keyword
|
||||
inls.forM go
|
||||
tok tk2 .keyword
|
||||
| `(inline| link[%$tk1 $inls* ]%$tk2 $ref) =>
|
||||
tok tk1 .keyword
|
||||
inls.forM go
|
||||
tok tk2 .keyword
|
||||
go ref
|
||||
| `(inline| image(%$tk1 $s )%$tk2 $ref) =>
|
||||
tok tk1 .keyword
|
||||
tok s .string
|
||||
tok tk2 .keyword
|
||||
go ref
|
||||
| `(inline| footnote(%$tk1 $s )%$tk2) =>
|
||||
tok tk1 .keyword
|
||||
tok s .property
|
||||
tok tk2 .keyword
|
||||
| `(inline| code(%$tk1 $s )%$tk2) =>
|
||||
tok tk1 .keyword
|
||||
tok s .string
|
||||
tok tk2 .keyword
|
||||
| `(inline| role{%$tk1 $x $args* }%$tk2 [%$tk3 $inls* ]%$tk4) =>
|
||||
tok tk1 .keyword
|
||||
tok x .function
|
||||
args.forM go
|
||||
tok tk2 .keyword
|
||||
tok tk3 .keyword
|
||||
inls.forM go
|
||||
tok tk4 .keyword
|
||||
| `(inline| \math%$tk1 code(%$tk2 $s )%$tk3)
|
||||
| `(inline| \displaymath%$tk1 code(%$tk2 $s )%$tk3) =>
|
||||
tok tk1 .keyword
|
||||
tok s .string
|
||||
tok tk2 .keyword
|
||||
tok tk3 .keyword
|
||||
| `(list_item| *%$tk $inls*) =>
|
||||
tok tk .keyword
|
||||
inls.forM go
|
||||
| `(desc| :%$tk $inls* => $blks*) =>
|
||||
tok tk .keyword
|
||||
inls.forM go
|
||||
blks.forM go
|
||||
| `(block|para[$inl*]) => inl.forM go
|
||||
| `(block| ```%$tk1 $x $args* | $s ```%$tk2)=>
|
||||
tok tk1 .keyword
|
||||
tok x .function
|
||||
args.forM go
|
||||
tok s .string
|
||||
tok tk2 .keyword
|
||||
| `(block| :::%$tk1 $x $args* { $blks* }%$tk2)=>
|
||||
tok tk1 .keyword
|
||||
tok x .function
|
||||
args.forM go
|
||||
blks.forM go
|
||||
tok tk2 .keyword
|
||||
| `(block| command{%$tk1 $x $args*}%$tk2)=>
|
||||
tok tk1 .keyword
|
||||
tok x .function
|
||||
args.forM go
|
||||
tok tk2 .keyword
|
||||
| `(block| %%%%$tk1 $vals* %%%%$tk2)=>
|
||||
tok tk1 .keyword
|
||||
modify (· ++ getTokens (mkNullNode vals))
|
||||
tok tk2 .keyword
|
||||
| `(block| [%$tk1 $s ]:%$tk2 $url) =>
|
||||
tok tk1 .keyword
|
||||
tok s .property
|
||||
tok tk2 .keyword
|
||||
tok url .string
|
||||
| `(block| [^%$tk1 $s ]:%$tk2 $inls*) =>
|
||||
tok tk1 .keyword
|
||||
tok s .property
|
||||
tok tk2 .keyword
|
||||
inls.forM go
|
||||
| `(block| header(%$tk $_ ){ $inls* })=>
|
||||
tok tk .keyword
|
||||
inls.forM go
|
||||
| `(block|ul{$items*}) | `(block|ol($_){$items*}) | `(block|dl{$items*}) =>
|
||||
items.forM go
|
||||
| _ =>
|
||||
pure ()
|
||||
|
||||
/--
|
||||
Collects all semantic tokens that can be deduced purely from `Syntax`
|
||||
without elaboration information.
|
||||
|
|
@ -107,6 +237,12 @@ partial def collectSyntaxBasedSemanticTokens : (stx : Syntax) → Array LeanSema
|
|||
| stx => Id.run do
|
||||
if noHighlightKinds.contains stx.getKind then
|
||||
return #[]
|
||||
if docKinds.contains stx.getKind then
|
||||
-- Docs are only highlighted in Verso format, in which case `stx[1]` is a node.
|
||||
if stx[1].isAtom then
|
||||
return #[]
|
||||
else
|
||||
return collectVersoTokens stx[1] collectSyntaxBasedSemanticTokens
|
||||
let mut tokens :=
|
||||
if stx.isOfKind choiceKind then
|
||||
collectSyntaxBasedSemanticTokens stx[0]
|
||||
|
|
@ -120,10 +256,12 @@ partial def collectSyntaxBasedSemanticTokens : (stx : Syntax) → Array LeanSema
|
|||
return tokens
|
||||
return tokens.push ⟨stx, keywordSemanticTokenMap.getD val .keyword⟩
|
||||
|
||||
|
||||
open Lean.Doc.Syntax in
|
||||
/-- Collects all semantic tokens from the given `Elab.InfoTree`. -/
|
||||
def collectInfoBasedSemanticTokens (i : Elab.InfoTree) : Array LeanSemanticToken :=
|
||||
List.toArray <| i.deepestNodes fun _ i _ => do
|
||||
let .ofTermInfo ti := i
|
||||
Array.flatten <| List.toArray <| i.deepestNodes fun _ info _ => do
|
||||
let .ofTermInfo ti := info
|
||||
| none
|
||||
let .original .. := ti.stx.getHeadInfo
|
||||
| none
|
||||
|
|
@ -133,11 +271,11 @@ def collectInfoBasedSemanticTokens (i : Elab.InfoTree) : Array LeanSemanticToken
|
|||
-- Recall that `isAuxDecl` is an auxiliary declaration used to elaborate a recursive definition.
|
||||
if localDecl.isAuxDecl then
|
||||
if ti.isBinder then
|
||||
return ⟨ti.stx, SemanticTokenType.function⟩
|
||||
return #[⟨ti.stx, SemanticTokenType.function⟩]
|
||||
else if ! localDecl.isImplementationDetail then
|
||||
return ⟨ti.stx, SemanticTokenType.variable⟩
|
||||
return #[⟨ti.stx, SemanticTokenType.variable⟩]
|
||||
if ti.stx.getKind == Parser.Term.identProjKind then
|
||||
return ⟨ti.stx, SemanticTokenType.property⟩
|
||||
return #[⟨ti.stx, SemanticTokenType.property⟩]
|
||||
none
|
||||
|
||||
def computeSemanticTokens (doc : EditableDocument) (beginPos : String.Pos)
|
||||
|
|
|
|||
|
|
@ -324,6 +324,8 @@ def locationLinksOfInfo (doc : DocumentMeta) (kind : GoToKind) (ictx : InfoWithC
|
|||
locationLinksFromCommandInfo cci
|
||||
| .ofErrorNameInfo eni =>
|
||||
locationLinksFromErrorNameInfo eni
|
||||
| .ofDocElabInfo dei =>
|
||||
locationLinksFromDecl dei.name
|
||||
| _ =>
|
||||
pure #[]
|
||||
if kind == .declaration || ll.isEmpty then
|
||||
|
|
|
|||
|
|
@ -186,6 +186,8 @@ def Info.stx : Info → Syntax
|
|||
| ofFieldRedeclInfo i => i.stx
|
||||
| ofDelabTermInfo i => i.stx
|
||||
| ofChoiceInfo i => i.stx
|
||||
| ofDocInfo i => i.stx
|
||||
| ofDocElabInfo i => i.stx
|
||||
|
||||
def Info.lctx : Info → LocalContext
|
||||
| .ofTermInfo i => i.lctx
|
||||
|
|
@ -343,6 +345,10 @@ def Info.docString? (i : Info) : MetaM (Option String) := do
|
|||
| .ofErrorNameInfo eni => do
|
||||
let some errorExplanation := getErrorExplanationRaw? (← getEnv) eni.errorName | return none
|
||||
return errorExplanation.summaryWithSeverity
|
||||
| .ofDocInfo di =>
|
||||
return (← findDocString? env di.stx.getKind)
|
||||
| .ofDocElabInfo dei =>
|
||||
return (← findDocString? env dei.name)
|
||||
| _ => pure ()
|
||||
if let some ei := i.toElabInfo? then
|
||||
return ← findDocString? env ei.stx.getKind <||> findDocString? env ei.elaborator
|
||||
|
|
|
|||
|
|
@ -252,6 +252,8 @@ def identOf (ci : ContextInfo) (i : Info) : Option (RefIdent × Bool) := do
|
|||
some (RefIdent.const (← getModuleContainingDecl? ci.env fi.projName).toString fi.projName.toString, false)
|
||||
| Info.ofOptionInfo oi =>
|
||||
some (RefIdent.const (← getModuleContainingDecl? ci.env oi.declName).toString oi.declName.toString, false)
|
||||
| Info.ofDocElabInfo dei =>
|
||||
some (RefIdent.const (← getModuleContainingDecl? ci.env dei.name).toString dei.name.toString, false)
|
||||
| _ => none
|
||||
|
||||
/-- Finds all references in `trees`. -/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "util/options.h"
|
||||
|
||||
// Dear CI, please update stage0
|
||||
|
||||
namespace lean {
|
||||
options get_default_options() {
|
||||
options opts;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
|
||||
import Lean
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/-!
|
||||
This test checks that the basic features of Verso docstrings work.
|
||||
-/
|
||||
|
||||
open Lean Doc Elab Term
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -23,6 +27,7 @@ def d (s : TSyntaxArray `block) : DocM (Block ElabInline ElabBlock) := do
|
|||
-/
|
||||
def oneLine := "one line"
|
||||
|
||||
set_option doc.verso false
|
||||
/--
|
||||
x
|
||||
yz
|
||||
|
|
@ -74,6 +79,8 @@ def x (y : Nat) : Nat := y * 5
|
|||
#eval show TermElabM Unit from do (← findDocString? (← getEnv) ``x).forM (IO.println ·.quote)
|
||||
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
/--
|
||||
{name}`inst`
|
||||
-/
|
||||
|
|
@ -197,12 +204,14 @@ def a := "a"
|
|||
def b := "b"
|
||||
end A
|
||||
|
||||
/- Commented out for bootstrapping
|
||||
/-- error: Unknown constant `a` -/
|
||||
#guard_msgs in
|
||||
/--
|
||||
{name}`a`
|
||||
role {name}`a` here
|
||||
-/
|
||||
def testDef := 15
|
||||
-/
|
||||
|
||||
#guard_msgs in
|
||||
/--
|
||||
|
|
@ -220,6 +229,7 @@ def testDef' := 15
|
|||
-/
|
||||
def testDef'' := 15
|
||||
|
||||
/- Commented out for bootstrapping
|
||||
/-- error: Unknown constant `b` -/
|
||||
#guard_msgs in
|
||||
/--
|
||||
|
|
@ -228,6 +238,7 @@ def testDef'' := 15
|
|||
{name}`b`
|
||||
-/
|
||||
def testDef''' := 15
|
||||
-/
|
||||
|
||||
#guard_msgs in
|
||||
/--
|
||||
|
|
@ -289,7 +300,7 @@ Examples:
|
|||
-/
|
||||
def somethingElseAgain := ()
|
||||
|
||||
|
||||
/- Commented out for bootstrapping
|
||||
/--
|
||||
error: Unknown option `pp.alll`
|
||||
---
|
||||
|
|
@ -308,13 +319,14 @@ Examples:
|
|||
* {option}`set_option pp.all "true"` to set it
|
||||
-/
|
||||
def somethingElseAgain' := ()
|
||||
|
||||
-/
|
||||
|
||||
/--
|
||||
{kw (cat := term)}`Type` {kw (of := termIfLet)}`if`
|
||||
-/
|
||||
def somethingElseAgain'' := ()
|
||||
|
||||
/- Commented out for bootstrapping
|
||||
/--
|
||||
info:
|
||||
|
||||
|
|
@ -326,7 +338,7 @@ Hint: Specify the syntax kind:
|
|||
{kw?}`Type`
|
||||
-/
|
||||
def somethingElseAgain''' := ()
|
||||
|
||||
-/
|
||||
|
||||
/--
|
||||
{syntaxCat}`term`
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue