Skip to content

Commit 6d6fafc

Browse files
committed
feat(CrossRefAttribute): add @[wikidata] attribute (leanprover-community#39284)
This PR adds a Wikidata cross-reference attribute alongside the existing Stacks and Kerodon ones, accepting identifiers of the form `QN+`, plus a `#wikidata_tags`/`#wikidata_tags!` trace command. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 026db32 commit 6d6fafc

2 files changed

Lines changed: 120 additions & 2 deletions

File tree

Mathlib/Tactic/CrossRefAttribute.lean

Lines changed: 92 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+
* `@[wikidata QID]` — [Wikidata](https://www.wikidata.org)
1920
2021
Each attribute records the cross-reference in an environment extension and appends
2122
a link to the declaration's docstring.
@@ -35,17 +36,20 @@ namespace Mathlib.CrossRef
3536
inductive Database where
3637
| kerodon
3738
| stacks
39+
| wikidata
3840
deriving BEq, Hashable
3941

4042
/-- The base URL for an external database's tag pages. Always ends with `/`. -/
4143
def databaseURL : Database → String
4244
| .kerodon => "https://kerodon.net/tag/"
4345
| .stacks => "https://stacks.math.columbia.edu/tag/"
46+
| .wikidata => "https://www.wikidata.org/wiki/"
4447

4548
/-- The display label used in docstring links and trace output. -/
4649
def databaseLabel : Database → String
4750
| .kerodon => "Kerodon Tag"
4851
| .stacks => "Stacks Tag"
52+
| .wikidata => "Wikidata"
4953

5054
/-- A cross-reference from a Mathlib declaration to an entry in an external database. -/
5155
structure Tag where
@@ -84,6 +88,8 @@ def addCrossRefDoc (db : Database) (decl : Name) (idStr comment : String) : Core
8488

8589
open Parser
8690

91+
/-! ### Stacks (and Kerodon) parser -/
92+
8793
/-- `stacksTag` is the node kind of Stacks Project Tags: a sequence of digits and
8894
uppercase letters. -/
8995
abbrev stacksTagKind : SyntaxNodeKind := `stacksTag
@@ -117,6 +123,42 @@ def stacksTagNoAntiquot : Parser := {
117123
def stacksTagParser : Parser :=
118124
withAntiquot (mkAntiquot "stacksTag" stacksTagKind) stacksTagNoAntiquot
119125

126+
/-! ### Wikidata parser -/
127+
128+
/-- `wikidataId` is the node kind of Wikidata identifiers: the letter `Q` followed by digits. -/
129+
abbrev wikidataIdKind : SyntaxNodeKind := `wikidataId
130+
131+
/-- The main parser for Wikidata identifiers: it accepts `Q` followed by one or more digits. -/
132+
def wikidataIdFn : ParserFn := fun c s =>
133+
let i := s.pos
134+
let s := takeWhileFn (fun c => c.isAlphanum) c s
135+
if s.hasError then
136+
s
137+
else if s.pos == i then
138+
ParserState.mkError s "wikidata id"
139+
else
140+
let id := c.extract i s.pos
141+
match id.toList with
142+
| 'Q' :: rest@(_ :: _) =>
143+
if rest.all Char.isDigit then
144+
mkNodeToken wikidataIdKind i true c s
145+
else
146+
ParserState.mkUnexpectedError s
147+
"Wikidata ids must consist of the letter Q followed by digits."
148+
| _ =>
149+
ParserState.mkUnexpectedError s
150+
"Wikidata ids must start with the letter Q followed by one or more digits."
151+
152+
@[inherit_doc wikidataIdFn]
153+
def wikidataIdNoAntiquot : Parser := {
154+
fn := wikidataIdFn
155+
info := mkAtomicInfo "wikidataId"
156+
}
157+
158+
@[inherit_doc wikidataIdFn]
159+
def wikidataIdParser : Parser :=
160+
withAntiquot (mkAntiquot "wikidataId" wikidataIdKind) wikidataIdNoAntiquot
161+
120162
end Mathlib.CrossRef
121163

122164
open Mathlib.CrossRef
@@ -126,6 +168,11 @@ def Lean.TSyntax.getStacksTag (stx : TSyntax stacksTagKind) : CoreM String := do
126168
let some val := Syntax.isLit? stacksTagKind stx | throwError "Malformed Stacks tag"
127169
return val
128170

171+
/-- Extract the underlying identifier as a string from a `wikidataId` node. -/
172+
def Lean.TSyntax.getWikidataId (stx : TSyntax wikidataIdKind) : CoreM String := do
173+
let some val := Syntax.isLit? wikidataIdKind stx | throwError "Malformed Wikidata id"
174+
return val
175+
129176
namespace Lean.PrettyPrinter
130177

131178
namespace Formatter
@@ -134,17 +181,26 @@ namespace Formatter
134181
@[combinator_formatter stacksTagNoAntiquot] def stacksTagNoAntiquot.formatter :=
135182
visitAtom stacksTagKind
136183

184+
/-- The formatter for Wikidata identifier syntax. -/
185+
@[combinator_formatter wikidataIdNoAntiquot] def wikidataIdNoAntiquot.formatter :=
186+
visitAtom wikidataIdKind
187+
137188
end Formatter
138189

139190
namespace Parenthesizer
140191

141192
/-- The parenthesizer for Stacks Project Tags syntax. -/
142193
@[combinator_parenthesizer stacksTagNoAntiquot] def stacksTagAntiquot.parenthesizer := visitToken
143194

195+
/-- The parenthesizer for Wikidata identifier syntax. -/
196+
@[combinator_parenthesizer wikidataIdNoAntiquot] def wikidataIdAntiquot.parenthesizer := visitToken
197+
144198
end Lean.PrettyPrinter.Parenthesizer
145199

146200
namespace Mathlib.CrossRef
147201

202+
/-! ### Stacks / Kerodon attribute -/
203+
148204
/-- The syntax category for the database name. -/
149205
declare_syntax_cat stacksTagDB
150206

@@ -177,14 +233,36 @@ initialize Lean.registerBuiltinAttribute {
177233
applicationTime := .beforeElaboration
178234
}
179235

236+
/-! ### Wikidata attribute -/
237+
238+
/-- The `wikidata` attribute.
239+
Use it as `@[wikidata Q12345 "Optional comment"]` to associate a Mathlib declaration with
240+
the corresponding [Wikidata](https://www.wikidata.org) item.
241+
242+
The identifier must be the letter `Q` followed by one or more digits.
243+
-/
244+
syntax (name := wikidataTag) "wikidata" wikidataIdParser (ppSpace str)? : attr
245+
246+
initialize Lean.registerBuiltinAttribute {
247+
name := `wikidataTag
248+
descr := "Apply a Wikidata identifier to a declaration."
249+
add := fun decl stx _attrKind => do
250+
let (id, comment) := ← match stx with
251+
| `(attr| wikidata $id $[$comment]?) => return (id, comment)
252+
| _ => throwUnsupportedSyntax
253+
addCrossRefDoc .wikidata decl (← id.getWikidataId) ((comment.map (·.getString)).getD "")
254+
-- docstrings are immutable once an asynchronous elaboration task has been started
255+
applicationTime := .beforeElaboration
256+
}
257+
180258
end Mathlib.CrossRef
181259

182-
/-- Return the array of `Tag`s in the environment, sorted alphabetically by tag. -/
260+
/-- Returns the array of `Tag`s in the environment, sorted alphabetically by tag. -/
183261
private def Lean.Environment.getSortedCrossRefs (env : Environment) : Array Tag :=
184262
let tags := PersistentEnvExtension.getState tagExt env
185263
tags.2.flatten.appendList tags.1 |>.qsort (·.tag < ·.tag)
186264

187-
/-- Return the declaration names of results carrying the cross-reference `tag`. -/
265+
/-- Returns the declaration names of results carrying the cross-reference `tag`. -/
188266
private def Lean.Environment.getCrossRefDeclNames (env : Environment) (tag : String) :
189267
Array Name :=
190268
env.getSortedCrossRefs.filterMap fun d => if d.tag == tag then some d.declName else none
@@ -236,4 +314,16 @@ or declaration type (for definitions, structures, instances, etc.) after each su
236314
elab (name := kerodonTags) "#kerodon_tags" tk:("!")? : command =>
237315
traceCrossRefs .kerodon (tk.isSome)
238316

317+
/-- The `#wikidata_tags` command retrieves all declarations that have the `wikidata` attribute.
318+
319+
For each found declaration, it prints a line
320+
```
321+
'declaration_name' corresponds to tag 'declaration_tag'.
322+
```
323+
The variant `#wikidata_tags!` also adds the theorem statement (for theorems)
324+
or declaration type (for definitions, structures, instances, etc.) after each summary line.
325+
-/
326+
elab (name := wikidataTags) "#wikidata_tags" tk:("!")? : command =>
327+
traceCrossRefs .wikidata (tk.isSome)
328+
239329
end Mathlib.CrossRef

MathlibTest/CrossRefAttribute.lean

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,34 @@ True
6969
#guard_msgs in
7070
#kerodon_tags!
7171

72+
namespace W
73+
74+
@[wikidata Q12345 "A Wikidata comment"]
75+
theorem wikiTagged : True := .intro
76+
77+
end W
78+
79+
/-- info: some ([Wikidata Q12345](https://www.wikidata.org/wiki/Q12345) (A Wikidata comment)) -/
80+
#guard_msgs in
81+
run_cmd
82+
Lean.logInfo m!"{← Lean.findDocString? (← Lean.getEnv) `W.wikiTagged}"
83+
84+
/--
85+
info:
86+
[Wikidata Q12345](https://www.wikidata.org/wiki/Q12345) corresponds to declaration 'W.wikiTagged'. (A Wikidata comment)
87+
-/
88+
#guard_msgs in
89+
#wikidata_tags
90+
91+
/-- error: <input>:1:5: Wikidata ids must consist of the letter Q followed by digits. -/
92+
#guard_msgs in #parse Mathlib.CrossRef.wikidataIdFn => "Q12X3"
93+
94+
/-- error: <input>:1:6: Wikidata ids must start with the letter Q followed by one or more digits. -/
95+
#guard_msgs in #parse Mathlib.CrossRef.wikidataIdFn => "P12345"
96+
97+
/-- info: Q42 -/
98+
#guard_msgs in #parse Mathlib.CrossRef.wikidataIdFn => "Q42"
99+
72100
section errors
73101

74102
open Lean Parser Mathlib.CrossRef

0 commit comments

Comments
 (0)