@@ -16,6 +16,7 @@ to entries in external mathematical databases:
1616
1717* `@[stacks TAG]` — [ Stacks Project ] (https://stacks.math.columbia.edu/tags)
1818* `@[kerodon TAG]` — [ Kerodon ] (https://kerodon.net/tag/)
19+ * `@[pibase ID]` — [ π-Base ] (https://topology.pi-base.org/)
1920* `@[wikidata QID]` — [ Wikidata ] (https://www.wikidata.org)
2021
2122 Each attribute records the cross-reference in an environment extension and appends
@@ -35,6 +36,7 @@ namespace Mathlib.CrossRef
3536/-- The supported external databases -/
3637inductive Database where
3738 | kerodon
39+ | pibase
3840 | stacks
3941 | wikidata
4042 deriving BEq, Hashable, Ord
@@ -44,18 +46,37 @@ namespace Database
4446/-- The base URL for an external database's tag pages. Always ends with `/`. -/
4547def url : Database → String
4648 | .kerodon => "https://kerodon.net/tag/"
49+ | .pibase => "https://topology.pi-base.org/"
4750 | .stacks => "https://stacks.math.columbia.edu/tag/"
4851 | .wikidata => "https://www.wikidata.org/wiki/"
4952
53+ /-- The URL path for the given database identifier. -/
54+ def urlPath (db : Database) (id : String) : String :=
55+ match db with
56+ | .pibase =>
57+ let path := match id.toList with
58+ | 'P' :: _ => "properties/"
59+ | 'S' :: _ => "spaces/"
60+ | 'T' :: _ => "theorems/"
61+ | _ => ""
62+ path ++ id
63+ | _ => id
64+
65+ /-- The URL for an external database entry. -/
66+ def entryUrl (db : Database) (id : String) : String :=
67+ db.url ++ db.urlPath id
68+
5069/-- The display label used in docstring links and trace output. -/
5170def label : Database → String
5271 | .kerodon => "Kerodon Tag"
72+ | .pibase => "π-Base"
5373 | .stacks => "Stacks Tag"
5474 | .wikidata => "Wikidata"
5575
5676/-- A lowercase short name for the given database. Useful when exporting to JSON. -/
5777def shortName : Database → String
5878 | .kerodon => "kerodon"
79+ | .pibase => "pibase"
5980 | .stacks => "stacks"
6081 | .wikidata => "wikidata"
6182
@@ -92,7 +113,7 @@ This is the database-agnostic core of every cross-reference attribute's `add` ha
92113def addCrossRefDoc (db : Database) (decl : Name) (idStr comment : String) : CoreM Unit := do
93114 let oldDoc := (← findDocString? (← getEnv) decl).getD ""
94115 let commentInDoc := if comment.isEmpty then "" else s! " ({ comment} )"
95- let link := s! "[{ db.label} { idStr} ]({ db.url }{ idStr} ){ commentInDoc} "
116+ let link := s! "[{ db.label} { idStr} ]({ db.entryUrl idStr} ){ commentInDoc} "
96117 addDocStringCore decl <| "\n\n " .intercalate ([oldDoc, link].filter (· != "" ))
97118 addTagEntry decl db idStr comment
98119
@@ -169,6 +190,49 @@ def wikidataIdNoAntiquot : Parser := {
169190def wikidataIdParser : Parser :=
170191 withAntiquot (mkAntiquot "wikidataId" wikidataIdKind) wikidataIdNoAntiquot
171192
193+ /-! ### π-Base parser -/
194+
195+ /-- `pibaseId` is the node kind of π-Base identifiers: one of the letters `P`, `S`, or `T`,
196+ followed by exactly six digits. -/
197+ abbrev pibaseIdKind : SyntaxNodeKind := `pibaseId
198+
199+ /-- The main parser for π-Base identifiers: it accepts canonical property, space, and theorem
200+ identifiers, such as `P000001`, `S000023`, and `T000010`. -/
201+ def pibaseIdFn : ParserFn := fun c s =>
202+ let i := s.pos
203+ let s := takeWhileFn (fun c => c.isAlphanum) c s
204+ if s.hasError then
205+ s
206+ else if s.pos == i then
207+ ParserState.mkError s "π-Base id"
208+ else
209+ let id := c.extract i s.pos
210+ match id.toList with
211+ | kind :: rest =>
212+ if kind != 'P' && kind != 'S' && kind != 'T' then
213+ ParserState.mkUnexpectedError s
214+ "π-Base ids must start with P, S, or T."
215+ else if rest.length != 6 then
216+ ParserState.mkUnexpectedError s "π-Base ids must have exactly six digits after P/S/T."
217+ else if !rest.all Char.isDigit then
218+ ParserState.mkUnexpectedError s
219+ "π-Base ids must consist of P, S, or T followed by six digits."
220+ else
221+ mkNodeToken pibaseIdKind i true c s
222+ | _ =>
223+ ParserState.mkUnexpectedError s
224+ "π-Base ids must consist of P, S, or T followed by six digits."
225+
226+ @ [inherit_doc pibaseIdFn]
227+ def pibaseIdNoAntiquot : Parser := {
228+ fn := pibaseIdFn
229+ info := mkAtomicInfo "pibaseId"
230+ }
231+
232+ @ [inherit_doc pibaseIdFn]
233+ def pibaseIdParser : Parser :=
234+ withAntiquot (mkAntiquot "pibaseId" pibaseIdKind) pibaseIdNoAntiquot
235+
172236end Mathlib.CrossRef
173237
174238open Mathlib.CrossRef
@@ -183,6 +247,11 @@ def Lean.TSyntax.getWikidataId (stx : TSyntax wikidataIdKind) : CoreM String :=
183247 let some val := Syntax.isLit? wikidataIdKind stx | throwError "Malformed Wikidata id"
184248 return val
185249
250+ /-- Extract the underlying identifier as a string from a `pibaseId` node. -/
251+ def Lean.TSyntax.getPibaseId (stx : TSyntax pibaseIdKind) : CoreM String := do
252+ let some val := Syntax.isLit? pibaseIdKind stx | throwError "Malformed π-Base id"
253+ return val
254+
186255namespace Lean.PrettyPrinter
187256
188257namespace Formatter
@@ -195,6 +264,10 @@ namespace Formatter
195264@ [combinator_formatter wikidataIdNoAntiquot] def wikidataIdNoAntiquot.formatter :=
196265 visitAtom wikidataIdKind
197266
267+ /-- The formatter for π-Base identifier syntax. -/
268+ @ [combinator_formatter pibaseIdNoAntiquot] def pibaseIdNoAntiquot.formatter :=
269+ visitAtom pibaseIdKind
270+
198271end Formatter
199272
200273namespace Parenthesizer
@@ -205,6 +278,9 @@ namespace Parenthesizer
205278/-- The parenthesizer for Wikidata identifier syntax. -/
206279@ [combinator_parenthesizer wikidataIdNoAntiquot] def wikidataIdAntiquot.parenthesizer := visitToken
207280
281+ /-- The parenthesizer for π-Base identifier syntax. -/
282+ @ [combinator_parenthesizer pibaseIdNoAntiquot] def pibaseIdAntiquot.parenthesizer := visitToken
283+
208284end Lean.PrettyPrinter.Parenthesizer
209285
210286namespace Mathlib.CrossRef
@@ -265,6 +341,28 @@ initialize Lean.registerBuiltinAttribute {
265341 applicationTime := .beforeElaboration
266342}
267343
344+ /-! ### π-Base attribute -/
345+
346+ /-- The `pibase` attribute.
347+ Use it as `@[pibase P000001 "Optional comment"]` to associate a Mathlib declaration with
348+ the corresponding [ π-Base ] (https://topology.pi-base.org/) property, space, or theorem.
349+
350+ The identifier must start with `P`, `S`, or `T`, followed by exactly six digits.
351+ -/
352+ syntax (name := pibaseTag) "pibase" pibaseIdParser (ppSpace str)? : attr
353+
354+ initialize Lean.registerBuiltinAttribute {
355+ name := `pibaseTag
356+ descr := "Apply a π-Base identifier to a declaration."
357+ add := fun decl stx _attrKind => do
358+ let (id, comment) ← match stx with
359+ | `(attr| pibase $id $[$comment]?) => pure (id, comment)
360+ | _ => throwUnsupportedSyntax
361+ addCrossRefDoc .pibase decl (← id.getPibaseId) ((comment.map (·.getString)).getD "" )
362+ -- docstrings are immutable once an asynchronous elaboration task has been started
363+ applicationTime := .beforeElaboration
364+ }
365+
268366end Mathlib.CrossRef
269367
270368/-- Returns the array of `Tag`s in the environment, sorted alphabetically by tag. -/
@@ -291,7 +389,7 @@ def traceCrossRefs (db : Database) (verbose : Bool := false) :
291389 let (parL, parR) := if d.comment.isEmpty then ("" , "" ) else (" (" , ")" )
292390 let cmt := parL ++ d.comment ++ parR
293391 msgs := msgs.push
294- m! "[{ db.label} { d.tag} ]({ db.url ++ d.tag} ) \
392+ m! "[{ db.label} { d.tag} ]({ db.entryUrl d.tag} ) \
295393 corresponds to declaration '{ .ofConstName d.declName} '.{ cmt} "
296394 if verbose then
297395 let dType := ((env.find? d.declName).getD default).type
@@ -336,4 +434,16 @@ or declaration type (for definitions, structures, instances, etc.) after each su
336434elab (name := wikidataTags) "#wikidata_tags" tk:("!" )? : command =>
337435 traceCrossRefs .wikidata (tk.isSome)
338436
437+ /-- The `#pibase_tags` command retrieves all declarations that have the `pibase` attribute.
438+
439+ For each found declaration, it prints a line
440+ ```
441+ 'declaration_name' corresponds to tag 'declaration_tag'.
442+ ```
443+ The variant `#pibase_tags!` also adds the theorem statement (for theorems)
444+ or declaration type (for definitions, structures, instances, etc.) after each summary line.
445+ -/
446+ elab (name := pibaseTags) "#pibase_tags" tk:("!" )? : command =>
447+ traceCrossRefs .pibase (tk.isSome)
448+
339449end Mathlib.CrossRef
0 commit comments