Skip to content

Commit 78bd73f

Browse files
committed
feat: implement pi-base cross-reference attribute
1 parent ae86149 commit 78bd73f

2 files changed

Lines changed: 150 additions & 8 deletions

File tree

Mathlib/Tactic/CrossRefAttribute.lean

Lines changed: 109 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ to entries in external mathematical databases:
1818
* `@[kerodon TAG]` — [Kerodon](https://kerodon.net/tag/)
1919
* `@[wikidata QID]` — [Wikidata](https://www.wikidata.org)
2020
* `@[lmfdb ID]` — [LMFDB](https://www.lmfdb.org)
21+
* `@[pibase ID]` — [π-Base](https://topology.pi-base.org/)
2122
2223
Each attribute records the cross-reference in an environment extension and appends
2324
a link to the declaration's docstring.
@@ -37,30 +38,41 @@ namespace Mathlib.CrossRef
3738
inductive Database where
3839
| kerodon
3940
| lmfdb
41+
| pibase
4042
| stacks
4143
| wikidata
4244
deriving BEq, Hashable, Ord
4345

4446
namespace Database
4547

46-
/-- The base URL for an external database's tag pages. Always ends with `/`. -/
47-
def url : Database → String
48-
| .kerodon => "https://kerodon.net/tag/"
49-
| .lmfdb => "https://www.lmfdb.org/knowledge/show/"
50-
| .stacks => "https://stacks.math.columbia.edu/tag/"
51-
| .wikidata => "https://www.wikidata.org/wiki/"
48+
/-- The URL for an external database entry. -/
49+
def url (db : Database) (id : String) : String :=
50+
match db with
51+
| .kerodon => "https://kerodon.net/tag/" ++ id
52+
| .lmfdb => "https://www.lmfdb.org/knowledge/show/" ++ id
53+
| .pibase =>
54+
let path := match id.toList with
55+
| 'P' :: _ => "properties/"
56+
| 'S' :: _ => "spaces/"
57+
| 'T' :: _ => "theorems/"
58+
| _ => ""
59+
"https://topology.pi-base.org/" ++ path ++ id
60+
| .stacks => "https://stacks.math.columbia.edu/tag/" ++ id
61+
| .wikidata => "https://www.wikidata.org/wiki/" ++ id
5262

5363
/-- The display label used in docstring links and trace output. -/
5464
def label : Database → String
5565
| .kerodon => "Kerodon Tag"
5666
| .lmfdb => "LMFDB"
67+
| .pibase => "π-Base"
5768
| .stacks => "Stacks Tag"
5869
| .wikidata => "Wikidata"
5970

6071
/-- A lowercase short name for the given database. Useful when exporting to JSON. -/
6172
def shortName : Database → String
6273
| .kerodon => "kerodon"
6374
| .lmfdb => "lmfdb"
75+
| .pibase => "pibase"
6476
| .stacks => "stacks"
6577
| .wikidata => "wikidata"
6678

@@ -97,7 +109,7 @@ This is the database-agnostic core of every cross-reference attribute's `add` ha
97109
def addCrossRefDoc (db : Database) (decl : Name) (idStr comment : String) : CoreM Unit := do
98110
let oldDoc := (← findDocString? (← getEnv) decl).getD ""
99111
let commentInDoc := if comment.isEmpty then "" else s!" ({comment})"
100-
let link := s!"[{db.label} {idStr}]({db.url}{idStr}){commentInDoc}"
112+
let link := s!"[{db.label} {idStr}]({db.url idStr}){commentInDoc}"
101113
addDocStringCore decl <| "\n\n".intercalate ([oldDoc, link].filter (· != ""))
102114
addTagEntry decl db idStr comment
103115

@@ -207,6 +219,49 @@ def lmfdbIdNoAntiquot : Parser := {
207219
def lmfdbIdParser : Parser :=
208220
withAntiquot (mkAntiquot "lmfdbId" lmfdbIdKind) lmfdbIdNoAntiquot
209221

222+
/-! ### π-Base parser -/
223+
224+
/-- `pibaseId` is the node kind of π-Base identifiers: one of the letters `P`, `S`, or `T`,
225+
followed by exactly six digits. -/
226+
abbrev pibaseIdKind : SyntaxNodeKind := `pibaseId
227+
228+
/-- The main parser for π-Base identifiers: it accepts canonical property, space, and theorem
229+
identifiers, such as `P000001`, `S000023`, and `T000010`. -/
230+
def pibaseIdFn : ParserFn := fun c s =>
231+
let i := s.pos
232+
let s := takeWhileFn (fun c => c.isAlphanum) c s
233+
if s.hasError then
234+
s
235+
else if s.pos == i then
236+
ParserState.mkError s "π-Base id"
237+
else
238+
let id := c.extract i s.pos
239+
match id.toList with
240+
| kind :: rest =>
241+
if kind != 'P' && kind != 'S' && kind != 'T' then
242+
ParserState.mkUnexpectedError s
243+
"π-Base ids must start with P, S, or T."
244+
else if rest.length != 6 then
245+
ParserState.mkUnexpectedError s "π-Base ids must have exactly six digits after P/S/T."
246+
else if !rest.all Char.isDigit then
247+
ParserState.mkUnexpectedError s
248+
"π-Base ids must consist of P, S, or T followed by six digits."
249+
else
250+
mkNodeToken pibaseIdKind i true c s
251+
| _ =>
252+
ParserState.mkUnexpectedError s
253+
"π-Base ids must consist of P, S, or T followed by six digits."
254+
255+
@[inherit_doc pibaseIdFn]
256+
def pibaseIdNoAntiquot : Parser := {
257+
fn := pibaseIdFn
258+
info := mkAtomicInfo "pibaseId"
259+
}
260+
261+
@[inherit_doc pibaseIdFn]
262+
def pibaseIdParser : Parser :=
263+
withAntiquot (mkAntiquot "pibaseId" pibaseIdKind) pibaseIdNoAntiquot
264+
210265
end Mathlib.CrossRef
211266

212267
open Mathlib.CrossRef
@@ -226,6 +281,11 @@ def Lean.TSyntax.getLmfdbId (stx : TSyntax lmfdbIdKind) : CoreM String := do
226281
let some val := Syntax.isLit? lmfdbIdKind stx | throwError "Malformed LMFDB id"
227282
return val
228283

284+
/-- Extract the underlying identifier as a string from a `pibaseId` node. -/
285+
def Lean.TSyntax.getPibaseId (stx : TSyntax pibaseIdKind) : CoreM String := do
286+
let some val := Syntax.isLit? pibaseIdKind stx | throwError "Malformed π-Base id"
287+
return val
288+
229289
namespace Lean.PrettyPrinter
230290

231291
namespace Formatter
@@ -242,6 +302,10 @@ namespace Formatter
242302
@[combinator_formatter lmfdbIdNoAntiquot] def lmfdbIdNoAntiquot.formatter :=
243303
visitAtom lmfdbIdKind
244304

305+
/-- The formatter for π-Base identifier syntax. -/
306+
@[combinator_formatter pibaseIdNoAntiquot] def pibaseIdNoAntiquot.formatter :=
307+
visitAtom pibaseIdKind
308+
245309
end Formatter
246310

247311
namespace Parenthesizer
@@ -255,6 +319,9 @@ namespace Parenthesizer
255319
/-- The parenthesizer for LMFDB identifier syntax. -/
256320
@[combinator_parenthesizer lmfdbIdNoAntiquot] def lmfdbIdAntiquot.parenthesizer := visitToken
257321

322+
/-- The parenthesizer for π-Base identifier syntax. -/
323+
@[combinator_parenthesizer pibaseIdNoAntiquot] def pibaseIdAntiquot.parenthesizer := visitToken
324+
258325
end Lean.PrettyPrinter.Parenthesizer
259326

260327
namespace Mathlib.CrossRef
@@ -335,6 +402,28 @@ initialize Lean.registerBuiltinAttribute {
335402
applicationTime := .beforeElaboration
336403
}
337404

405+
/-! ### π-Base attribute -/
406+
407+
/-- The `pibase` attribute.
408+
Use it as `@[pibase P000001 "Optional comment"]` to associate a Mathlib declaration with
409+
the corresponding [π-Base](https://topology.pi-base.org/) property, space, or theorem.
410+
411+
The identifier must start with `P`, `S`, or `T`, followed by exactly six digits.
412+
-/
413+
syntax (name := pibaseTag) "pibase" pibaseIdParser (ppSpace str)? : attr
414+
415+
initialize Lean.registerBuiltinAttribute {
416+
name := `pibaseTag
417+
descr := "Apply a π-Base identifier to a declaration."
418+
add := fun decl stx _attrKind => do
419+
let (id, comment) ← match stx with
420+
| `(attr| pibase $id $[$comment]?) => pure (id, comment)
421+
| _ => throwUnsupportedSyntax
422+
addCrossRefDoc .pibase decl (← id.getPibaseId) ((comment.map (·.getString)).getD "")
423+
-- docstrings are immutable once an asynchronous elaboration task has been started
424+
applicationTime := .beforeElaboration
425+
}
426+
338427
end Mathlib.CrossRef
339428

340429
/-- Returns the array of `Tag`s in the environment, sorted alphabetically by tag. -/
@@ -361,7 +450,7 @@ def traceCrossRefs (db : Database) (verbose : Bool := false) :
361450
let (parL, parR) := if d.comment.isEmpty then ("", "") else (" (", ")")
362451
let cmt := parL ++ d.comment ++ parR
363452
msgs := msgs.push
364-
m!"[{db.label} {d.tag}]({db.url ++ d.tag}) \
453+
m!"[{db.label} {d.tag}]({db.url d.tag}) \
365454
corresponds to declaration '{.ofConstName d.declName}'.{cmt}"
366455
if verbose then
367456
let dType := ((env.find? d.declName).getD default).type
@@ -418,4 +507,16 @@ or declaration type (for definitions, structures, instances, etc.) after each su
418507
elab (name := lmfdbTags) "#lmfdb_tags" tk:("!")? : command =>
419508
traceCrossRefs .lmfdb (tk.isSome)
420509

510+
/-- The `#pibase_tags` command retrieves all declarations that have the `pibase` attribute.
511+
512+
For each found declaration, it prints a line
513+
```
514+
'declaration_name' corresponds to tag 'declaration_tag'.
515+
```
516+
The variant `#pibase_tags!` also adds the theorem statement (for theorems)
517+
or declaration type (for definitions, structures, instances, etc.) after each summary line.
518+
-/
519+
elab (name := pibaseTags) "#pibase_tags" tk:("!")? : command =>
520+
traceCrossRefs .pibase (tk.isSome)
521+
421522
end Mathlib.CrossRef

MathlibTest/CrossRefAttribute.lean

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,47 @@ info:
132132

133133
end LMFDB
134134

135+
namespace Pibase
136+
137+
@[pibase P000001]
138+
theorem p : True := .intro
139+
140+
@[pibase S000023]
141+
theorem s : True := .intro
142+
143+
@[pibase T000001]
144+
theorem t : True := .intro
145+
146+
/--
147+
info: some ([π-Base P000001](https://topology.pi-base.org/properties/P000001))
148+
-/
149+
#guard_msgs in
150+
run_cmd
151+
Lean.logInfo m!"{← Lean.findDocString? (← Lean.getEnv) `Pibase.p}"
152+
153+
/--
154+
info:
155+
[π-Base P000001](https://topology.pi-base.org/properties/P000001) corresponds to declaration 'p'.
156+
[π-Base S000023](https://topology.pi-base.org/spaces/S000023) corresponds to declaration 's'.
157+
[π-Base T000001](https://topology.pi-base.org/theorems/T000001) corresponds to declaration 't'.
158+
-/
159+
#guard_msgs in
160+
#pibase_tags
161+
162+
/-- error: <input>:1:3: π-Base ids must have exactly six digits after P/S/T. -/
163+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "P42"
164+
165+
/-- error: <input>:1:7: π-Base ids must consist of P, S, or T followed by six digits. -/
166+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "P00001X"
167+
168+
/-- error: <input>:1:7: π-Base ids must start with P, S, or T. -/
169+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "Q000001"
170+
171+
/-- info: S000023 -/
172+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "S000023"
173+
174+
end Pibase
175+
135176
section errors
136177

137178
open Lean Parser Mathlib.CrossRef

0 commit comments

Comments
 (0)