Skip to content

Commit c198e80

Browse files
committed
feat: implement pi-base cross-reference attribute
1 parent ed0d70f commit c198e80

2 files changed

Lines changed: 153 additions & 2 deletions

File tree

Mathlib/Tactic/CrossRefAttribute.lean

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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 -/
3637
inductive 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 `/`. -/
4547
def 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. -/
5170
def 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. -/
5777
def 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
92113
def 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 := {
169190
def 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+
172236
end Mathlib.CrossRef
173237

174238
open 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+
186255
namespace Lean.PrettyPrinter
187256

188257
namespace 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+
198271
end Formatter
199272

200273
namespace 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+
208284
end Lean.PrettyPrinter.Parenthesizer
209285

210286
namespace 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+
268366
end 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
336434
elab (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+
339449
end Mathlib.CrossRef

MathlibTest/CrossRefAttribute.lean

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,47 @@ info:
9797
/-- info: Q42 -/
9898
#guard_msgs in #parse Mathlib.CrossRef.wikidataIdFn => "Q42"
9999

100+
namespace Pibase
101+
102+
@[pibase P000001]
103+
theorem p : True := .intro
104+
105+
@[pibase S000023]
106+
theorem s : True := .intro
107+
108+
@[pibase T000001]
109+
theorem t : True := .intro
110+
111+
/--
112+
info: some ([π-Base P000001](https://topology.pi-base.org/properties/P000001))
113+
-/
114+
#guard_msgs in
115+
run_cmd
116+
Lean.logInfo m!"{← Lean.findDocString? (← Lean.getEnv) `Pibase.p}"
117+
118+
/--
119+
info:
120+
[π-Base P000001](https://topology.pi-base.org/properties/P000001) corresponds to declaration 'p'.
121+
[π-Base S000023](https://topology.pi-base.org/spaces/S000023) corresponds to declaration 's'.
122+
[π-Base T000001](https://topology.pi-base.org/theorems/T000001) corresponds to declaration 't'.
123+
-/
124+
#guard_msgs in
125+
#pibase_tags
126+
127+
/-- error: <input>:1:3: π-Base ids must have exactly six digits after P/S/T. -/
128+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "P42"
129+
130+
/-- error: <input>:1:7: π-Base ids must consist of P, S, or T followed by six digits. -/
131+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "P00001X"
132+
133+
/-- error: <input>:1:7: π-Base ids must start with P, S, or T. -/
134+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "Q000001"
135+
136+
/-- info: S000023 -/
137+
#guard_msgs in #parse Mathlib.CrossRef.pibaseIdFn => "S000023"
138+
139+
end Pibase
140+
100141
section errors
101142

102143
open Lean Parser Mathlib.CrossRef

0 commit comments

Comments
 (0)