diff --git a/client/src/components/Inventory.tsx b/client/src/components/Inventory.tsx
index e90ac1e..99fa2b5 100644
--- a/client/src/components/Inventory.tsx
+++ b/client/src/components/Inventory.tsx
@@ -19,7 +19,7 @@ function Inventory({ tactics, lemmas } :
Tactics
{ tactics.map(tac =>
-
{setDocName(tac.name); setDocType("tactic")}}
+ {setDocName(tac.name); setDocType("Tactic")}}
name={tac.name} locked={tac.locked} disabled={tac.disabled} />) }
{/* TODO: Click on Tactic: show info
TODO: click on paste icon -> paste into command line */}
@@ -28,7 +28,7 @@ function Inventory({ tactics, lemmas } :
Lemmas
{ lemmas.map(lem =>
- {setDocName(lem.name); setDocType("lemma")}}
+ {setDocName(lem.name); setDocType("Lemma")}}
name={lem.name} locked={lem.locked} disabled={lem.disabled} />) }
diff --git a/server/leanserver/GameServer/Commands.lean b/server/leanserver/GameServer/Commands.lean
index 0b6b924..97c131e 100644
--- a/server/leanserver/GameServer/Commands.lean
+++ b/server/leanserver/GameServer/Commands.lean
@@ -180,92 +180,119 @@ macro "Hint" decls:mydecl* ":" goal:term "=>" msg:str : command => do
macro "HiddenHint" decls:mydecl* ":" goal:term "=>" msg:str : command => do
`(set_option linter.unusedVariables false in HiddenHint' $decls* : $goal => $msg)
-/-! ## Tactics -/
+/-! ## Inventory -/
+
+/-- Throw an error if inventory doc does not exist -/
+def checkInventoryDoc (type : InventoryType) (name : Name) : CommandElabM Unit := do
+ let some _ := (inventoryDocExt.getState (← getEnv)).find?
+ (fun x => x.name == name && x.type == type)
+ | throwError "Missing {type} Documentation: {name}"
+
+/-! ### Tactics -/
/-- Declare a documentation entry for some tactic.
Expect an identifier and then a string literal. -/
elab "TacticDoc" name:ident content:str : command =>
- modifyEnv (tacticDocExt.addEntry · {
+ modifyEnv (inventoryDocExt.addEntry · {
+ category := default
+ type := .Tactic
name := name.getId,
+ userName := name.getId,
content := content.getString })
-instance : Quote TacticDocEntry `term :=
-⟨λ entry => Syntax.mkCApp ``TacticDocEntry.mk #[quote entry.name, quote entry.content]⟩
-
-/-- Throw an error if tactic doc does not exist -/
-def checkTacticDoc (name : Name) : CommandElabM Unit := do
- let some _ := (tacticDocExt.getState (← getEnv)).find? (·.name = name)
- | throwError "Missing Tactic Documentation: {name}"
-
/-- Declare tactics that are introduced by this level. -/
elab "NewTactics" args:ident* : command => do
let names := args.map (·.getId)
- for name in names do checkTacticDoc name
- modifyCurLevel fun level => pure {level with newTactics := names}
+ for name in names do checkInventoryDoc .Tactic name
+ modifyCurLevel fun level => pure {level with tactics := {level.tactics with new := names}}
/-- Declare tactics that are temporarily disabled in this level -/
elab "DisabledTactics" args:ident* : command => do
let names := args.map (·.getId)
- for name in names do checkTacticDoc name
- modifyCurLevel fun level => pure {level with disabledTactics := names}
+ for name in names do checkInventoryDoc .Tactic name
+ modifyCurLevel fun level => pure {level with tactics := {level.tactics with disabled := names}}
/-- Temporarily disable all tactics except the ones declared here -/
elab "OnlyTactics" args:ident* : command => do
let names := args.map (·.getId)
- for name in names do checkTacticDoc name
- modifyCurLevel fun level => pure {level with onlyTactics := names}
+ for name in names do checkInventoryDoc .Tactic name
+ modifyCurLevel fun level => pure {level with tactics := {level.tactics with only := names}}
-/-! ## Lemmas -/
+/-! ### Definitions -/
+
+/-- Declare a documentation entry for some definition.
+Expect an identifier and then a string literal. -/
+elab "DefinitionDoc" name:ident content:str : command =>
+ modifyEnv (inventoryDocExt.addEntry · {
+ category := default
+ type := .Definition
+ name := name.getId,
+ userName := name.getId,
+ content := content.getString })
+
+/-- Declare definitions that are introduced by this level. -/
+elab "NewDefinitions" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Definition name
+ modifyCurLevel fun level => pure {level with definitions := {level.definitions with new := names}}
+
+/-- Declare definitions that are temporarily disabled in this level -/
+elab "DisabledDefinitions" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Definition name
+ modifyCurLevel fun level => pure {level with definitions := {level.definitions with disabled := names}}
+
+/-- Temporarily disable all definitions except the ones declared here -/
+elab "OnlyDefinitions" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Definition name
+ modifyCurLevel fun level => pure {level with definitions := {level.definitions with only := names}}
+
+
+/-! ### Lemmas -/
/-- Declare a documentation entry for some lemma.
Expect two identifiers and then a string literal. The first identifier is meant
as the real name of the lemma while the second is the displayed name. Currently
the real name isn't used. -/
elab "LemmaDoc" name:ident "as" userName:ident "in" category:str content:str : command =>
- modifyEnv (lemmaDocExt.addEntry · {
+ modifyEnv (inventoryDocExt.addEntry · {
name := name.getId,
+ type := .Lemma
userName := userName.getId,
category := category.getString,
content := content.getString })
-/-- Declare a set of lemma documentation entries.
-Expect an identifier used as the set name then `:=` and a
-space separated list of identifiers. -/
-elab "LemmaSet" name:ident ":" title:str ":=" args:ident* : command => do
- let docs := lemmaDocExt.getState (← getEnv)
- let mut entries : Array Name := #[]
- for arg in args do
- let name := arg.getId
- match docs.find? (·.userName = name) with
- | some doc => entries := entries.push name
- | none => throwError "Lemma doc {name} wasn't found."
- modifyEnv (lemmaSetExt.addEntry · {
- name := name.getId,
- title := title.getString,
- lemmas := entries })
+/-- Declare lemmas that are introduced by this level. -/
+elab "NewLemmas" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Lemma name
+ modifyCurLevel fun level => pure {level with lemmas := {level.lemmas with new := names}}
-instance : Quote LemmaDocEntry `term :=
-⟨λ entry => Syntax.mkCApp ``LemmaDocEntry.mk #[quote entry.name, quote entry.userName, quote entry.category, quote entry.content]⟩
+/-- Declare lemmas that are temporarily disabled in this level -/
+elab "DisabledLemmas" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Lemma name
+ modifyCurLevel fun level => pure {level with lemmas := {level.lemmas with disabled := names}}
-/-- Declare the list of lemmas that will be displayed in the current level.
-Expects a space separated list of identifiers that refer to either a lemma doc
-entry or a lemma doc set. -/
-elab "NewLemmas" args:ident* : command => do
- let env ← getEnv
- let docs := lemmaDocExt.getState env
- let sets := lemmaSetExt.getState env
- let mut lemmas : Array Name := #[]
- for arg in args do
- let name := arg.getId
- match docs.find? (·.userName = name) with
- | some entry => lemmas := lemmas.push name
- | none => match sets.find? (·.name = name) with
- | some entry => lemmas := lemmas ++ entry.lemmas
- | none => throwError "Lemma doc or lemma set {name} wasn't found."
- modifyCurLevel fun level => pure {level with newLemmas := lemmas}
+/-- Temporarily disable all lemmas except the ones declared here -/
+elab "OnlyLemmas" args:ident* : command => do
+ let names := args.map (·.getId)
+ for name in names do checkInventoryDoc .Lemma name
+ modifyCurLevel fun level => pure {level with lemmas := {level.lemmas with only := names}}
/-! ## Make Game -/
+def GameLevel.getInventory (level : GameLevel) : InventoryType → InventoryInfo
+| .Tactic => level.tactics
+| .Definition => level.definitions
+| .Lemma => level.lemmas
+
+def GameLevel.setComputedInventory (level : GameLevel) : InventoryType → Array Availability → GameLevel
+| .Tactic, v => {level with tactics := {level.tactics with computed := v}}
+| .Definition, v => {level with definitions := {level.definitions with computed := v}}
+| .Lemma, v => {level with lemmas := {level.lemmas with computed := v}}
+
/-- Make the final Game. This command will precompute various things about the game, such as which
tactics are available in each level etc. -/
elab "MakeGame" : command => do
@@ -275,72 +302,47 @@ elab "MakeGame" : command => do
if game.worlds.hasLoops then
throwError "World graph has loops!"
- -- Compute which tactics/lemmas are available in which level:
- let mut newTacticsInWorld : HashMap Name (HashSet Name) := {}
- let mut allTactics : HashSet Name := {}
- let mut newLemmasInWorld : HashMap Name (HashSet Name) := {}
- let mut allLemmas : HashSet Name := {}
- for (worldId, world) in game.worlds.nodes.toArray do
- let mut newTactics : HashSet Name:= {}
- let mut newLemmas : HashSet Name:= {}
- for (_, level) in world.levels.toArray do
- newTactics := newTactics.insertMany level.newTactics
- allTactics := allTactics.insertMany level.newTactics
- newLemmas := newLemmas.insertMany level.newLemmas
- allLemmas := allLemmas.insertMany level.newLemmas
- newTacticsInWorld := newTacticsInWorld.insert worldId newTactics
- newLemmasInWorld := newLemmasInWorld.insert worldId newLemmas
-
- -- Basic tactic/lemma availability: all locked, none disabled.
- let Availability₀ : HashMap Name Availability :=
- HashMap.ofList $
- allTactics.toList.map fun name =>
- (name, {name, locked := true, disabled := false})
- let lemmaAvailability₀ : HashMap Name Availability :=
- HashMap.ofList $
- allLemmas.toList.map fun name =>
- (name, {name, locked := true, disabled := false})
-
- -- Availability after a given world
- let mut tacticsInWorld : HashMap Name (HashMap Name Availability) := {}
- let mut lemmasInWorld : HashMap Name (HashMap Name Availability) := {}
- for (worldId, _) in game.worlds.nodes.toArray do
- let mut tactics := Availability₀
- let mut lemmas := lemmaAvailability₀
- let predecessors := game.worlds.predecessors worldId
- for predWorldId in predecessors do
- for tac in newTacticsInWorld.find! predWorldId do
- tactics := tactics.insert tac {name := tac, locked := false, disabled := false}
- for lem in newLemmasInWorld.find! predWorldId do
- lemmas := lemmas.insert lem {name := lem, locked := false, disabled := false}
- tacticsInWorld := tacticsInWorld.insert worldId tactics
- lemmasInWorld := lemmasInWorld.insert worldId lemmas
-
- for (worldId, world) in game.worlds.nodes.toArray do
- let mut tactics := tacticsInWorld.find! worldId
- let mut lemmas := lemmasInWorld.find! worldId
-
- let levels := world.levels.toArray.insertionSort fun a b => a.1 < b.1
-
- for (levelId, level) in levels do
- for tac in level.newTactics do
- tactics := tactics.insert tac {name := tac, locked := false, disabled := false}
- for tac in level.disabledTactics do
- tactics := tactics.insert tac {name := tac, locked := false, disabled := true}
- for lem in level.newLemmas do
- lemmas := lemmas.insert lem {name := lem, locked := false, disabled := false}
- for lem in level.disabledLemmas do
- lemmas := lemmas.insert lem {name := lem, locked := false, disabled := true}
-
- let tacticArray := tactics.toArray
- |>.insertionSort (fun a b => a.1.toString < b.1.toString)
- |>.map (·.2)
-
- let lemmaArray := lemmas.toArray
- |>.insertionSort (fun a b => a.1.toString < b.1.toString)
- |>.map (·.2)
-
- modifyLevel ⟨← getCurGameId, worldId, levelId⟩ fun level => do
- return {level with
- tactics := tacticArray
- lemmas := lemmaArray}
+ -- Compute which inventory items are available in which level:
+ for inventoryType in open InventoryType in #[Tactic, Definition, Lemma] do
+ let mut newItemsInWorld : HashMap Name (HashSet Name) := {}
+ let mut allItems : HashSet Name := {}
+ for (worldId, world) in game.worlds.nodes.toArray do
+ let mut newItems : HashSet Name := {}
+ for (_, level) in world.levels.toArray do
+ newItems := newItems.insertMany (level.getInventory inventoryType).new
+ allItems := allItems.insertMany (level.getInventory inventoryType).new
+ newItemsInWorld := newItemsInWorld.insert worldId newItems
+
+ -- Basic inventory item availability: all locked, none disabled.
+ let Availability₀ : HashMap Name Availability :=
+ HashMap.ofList $
+ allItems.toList.map fun name =>
+ (name, {name, locked := true, disabled := false})
+
+ -- Availability after a given world
+ let mut itemsInWorld : HashMap Name (HashMap Name Availability) := {}
+ for (worldId, _) in game.worlds.nodes.toArray do
+ let mut items := Availability₀
+ let predecessors := game.worlds.predecessors worldId
+ for predWorldId in predecessors do
+ for item in newItemsInWorld.find! predWorldId do
+ items := items.insert item {name := item, locked := false, disabled := false}
+ itemsInWorld := itemsInWorld.insert worldId items
+
+ for (worldId, world) in game.worlds.nodes.toArray do
+ let mut items := itemsInWorld.find! worldId
+
+ let levels := world.levels.toArray.insertionSort fun a b => a.1 < b.1
+
+ for (levelId, level) in levels do
+ for item in (level.getInventory inventoryType).new do
+ items := items.insert item {name := item, locked := false, disabled := false}
+ for item in (level.getInventory inventoryType).disabled do
+ items := items.insert item {name := item, locked := false, disabled := true}
+
+ let itemsArray := items.toArray
+ |>.insertionSort (fun a b => a.1.toString < b.1.toString)
+ |>.map (·.2)
+
+ modifyLevel ⟨← getCurGameId, worldId, levelId⟩ fun level => do
+ return level.setComputedInventory inventoryType itemsArray
diff --git a/server/leanserver/GameServer/EnvExtensions.lean b/server/leanserver/GameServer/EnvExtensions.lean
index 55fe713..1469780 100644
--- a/server/leanserver/GameServer/EnvExtensions.lean
+++ b/server/leanserver/GameServer/EnvExtensions.lean
@@ -26,77 +26,43 @@ structure GoalHintEntry where
hidden : Bool := false
deriving Repr
-/-! ## Tactic documentation -/
-structure TacticDocEntry where
- name : Name
- content : String
- deriving ToJson, Repr
+/-! ## Tactic/Definition/Lemma documentation -/
-/-- Environment extension for tactic documentation. -/
-initialize tacticDocExt : SimplePersistentEnvExtension TacticDocEntry (Array TacticDocEntry) ←
- registerSimplePersistentEnvExtension {
- name := `tactic_doc
- addEntryFn := Array.push
- addImportedFn := Array.concatMap id
- }
+inductive InventoryType := | Tactic | Lemma | Definition
+deriving ToJson, FromJson, Repr, BEq, Hashable
-def getTacticDoc? {m : Type → Type} [Monad m] [MonadEnv m] (tac : Name) :
- m (Option TacticDocEntry) := do
- return (tacticDocExt.getState (← getEnv)).find? (·.name = tac)
-
-open Elab Command in
-/-- Print a registered tactic doc for debugging purposes. -/
-elab "#print_tactic_doc" : command => do
- for entry in tacticDocExt.getState (← getEnv) do
- dbg_trace "{entry.name} : {entry.content}"
-
-/-! ## Lemma documentation -/
+instance : ToString InventoryType := ⟨fun t => match t with
+| .Tactic => "Tactic"
+| .Lemma => "Lemma"
+| .Definition => "Definition"
+⟩
-structure LemmaDocEntry where
+structure InventoryDocEntry where
name : Name
+ type : InventoryType
userName : Name
category : String
content : String
deriving ToJson, Repr
-/-- Environment extension for lemma documentation. -/
-initialize lemmaDocExt : SimplePersistentEnvExtension LemmaDocEntry (Array LemmaDocEntry) ←
+/-- Environment extension for inventory documentation. -/
+initialize inventoryDocExt : SimplePersistentEnvExtension InventoryDocEntry (Array InventoryDocEntry) ←
registerSimplePersistentEnvExtension {
- name := `lemma_doc
+ name := `inventory_doc
addEntryFn := Array.push
addImportedFn := Array.concatMap id
}
-def getLemmaDoc? {m : Type → Type} [Monad m] [MonadEnv m] (tac : Name) :
- m (Option LemmaDocEntry) := do
- return (lemmaDocExt.getState (← getEnv)).find? (·.name = tac)
+def getInventoryDoc? {m : Type → Type} [Monad m] [MonadEnv m] (n : Name) (type : InventoryType) :
+ m (Option InventoryDocEntry) := do
+ return (inventoryDocExt.getState (← getEnv)).find? (fun x => x.name == n && x.type == type)
open Elab Command in
-/-- Print a lemma doc for debugging purposes. -/
-elab "#print_lemma_doc" : command => do
- for entry in lemmaDocExt.getState (← getEnv) do
- dbg_trace "{entry.userName} ({entry.name}) in {entry.category}: {entry.content}"
-
-structure LemmaSetEntry where
- name : Name
- title : String
- lemmas : Array Name
- deriving ToJson, Repr
-
-/-- Environment extension for lemma sets. -/
-initialize lemmaSetExt : SimplePersistentEnvExtension LemmaSetEntry (Array LemmaSetEntry) ←
- registerSimplePersistentEnvExtension {
- name := `lemma_set
- addEntryFn := Array.push
- addImportedFn := Array.concatMap id
- }
-
-open Elab Command in
-/-- Print all registered lemma sets for debugging purposes. -/
-elab "#print_lemma_set" : command => do
- for entry in lemmaSetExt.getState (← getEnv) do
- dbg_trace "{entry.name} : {entry.lemmas}"
+/-- Print a registered tactic doc for debugging purposes. -/
+elab "#print_doc" : command => do
+ for entry in inventoryDocExt.getState (← getEnv) do
+ dbg_trace "[{entry.type}] {entry.name} : {entry.content}"
/-! ## Environment extensions for game specification-/
@@ -156,7 +122,18 @@ structure Availability where
name : Name
locked : Bool
disabled : Bool
-deriving ToJson, FromJson, Repr
+deriving ToJson, FromJson, Repr, Inhabited
+
+structure InventoryInfo where
+ -- new inventory items introduced by this level:
+ new : Array Name
+ -- inventory items exceptionally forbidden in this level:
+ disabled : Array Name
+ -- only these inventory items are allowed in this level (ignored if empty):
+ only : Array Name
+ -- inventory items in this level (computed by `MakeGame`):
+ computed : Array Availability
+deriving ToJson, FromJson, Repr, Inhabited
def getCurLevelId [MonadError m] : m LevelId := do
return { game := ← getCurGameId, world := ← getCurWorldId, level := ← getCurLevelIdx}
@@ -164,37 +141,20 @@ def getCurLevelId [MonadError m] : m LevelId := do
/-- Instance to make GameLevel Repr work -/
instance : Repr Elab.Command.Scope := ⟨fun s _ => repr s.currNamespace⟩
-/--
-Fields:
-- TODO
-- introduction: Theory block shown all the time.
-- description: The mathematical statemtent in mathematician-readable form.
-- goal: The statement in Lean.
-- conclusion: Displayed when level is completed.
--/
structure GameLevel where
index: Nat
title: String := default
+ /-- introduction: Theory block shown all the time -/
introduction: String := default
+ /-- The mathematical statemtent in mathematician-readable form -/
description: String := default
+ /-- conclusion displayed when level is completed. -/
conclusion: String := default
- -- new tactics introduces by this level:
- newTactics: Array Name := default
- -- tactics exceptionally forbidden in this level:
- disabledTactics: Array Name := default
- -- only these tactics are allowed in this level (ignore if empty):
- onlyTactics: Array Name := default
- -- tactics in this level (computed by `MakeGame`):
- tactics: Array Availability := default
- -- new lemmas introduces by this level:
- newLemmas: Array Name := default
- -- lemmas exceptionally forbidden in this level:
- disabledLemmas: Array Name := default
- -- only these lemmas are allowed in this level (ignore if empty):
- onlyLemmas: Array Name := default
- -- lemmas in this level (computed by `MakeGame`):
- lemmas: Array Availability := default
+ tactics: InventoryInfo := default
+ definitions: InventoryInfo := default
+ lemmas: InventoryInfo := default
hints: Array GoalHintEntry := default
+ /-- The statement in Lean. -/
goal : TSyntax `Lean.Parser.Command.declSig := default
scope : Elab.Command.Scope := default
descrText: String := default
diff --git a/server/leanserver/GameServer/Game.lean b/server/leanserver/GameServer/Game.lean
index a546350..b16cc9b 100644
--- a/server/leanserver/GameServer/Game.lean
+++ b/server/leanserver/GameServer/Game.lean
@@ -66,27 +66,11 @@ structure Doc where
text: String
deriving ToJson
-inductive TacOrLem := | tactic | «lemma»
-deriving ToJson, FromJson
-
structure LoadDocParams where
name : Name
- type : TacOrLem
+ type : InventoryType
deriving ToJson, FromJson
-def mkDoc (name : Name) (type : TacOrLem) : GameServerM (Option Doc) := do
- match type with
- | .tactic => do
- let doc ← getTacticDoc? name
- return doc.map fun doc =>
- { name := doc.name.toString
- text := doc.content }
- | .lemma => do
- let doc ← getLemmaDoc? name
- return doc.map fun doc =>
- { name := doc.name.toString
- text := doc.content }
-
def handleDidOpenLevel (params : Json) : GameServerM Unit := do
let p ← parseParams _ params
let m := p.textDocument
@@ -105,8 +89,8 @@ def handleDidOpenLevel (params : Json) : GameServerM Unit := do
param := {
uri := m.uri
levelModule := lvl.module
- tactics := lvl.tactics
- lemmas := lvl.lemmas
+ tactics := lvl.tactics.computed
+ lemmas := lvl.lemmas.computed
: DidOpenLevelParams
}
}
@@ -133,8 +117,8 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
let levelInfo : LevelInfo :=
{ index := lvl.index,
title := lvl.title,
- tactics := lvl.tactics,
- lemmas := lvl.lemmas,
+ tactics := lvl.tactics.computed,
+ lemmas := lvl.lemmas.computed,
descrText := lvl.descrText,
descrFormat := lvl.descrFormat --toExpr <| format (lvl.goal.raw) --toString <| Syntax.formatStx (lvl.goal.raw) --Syntax.formatStx (lvl.goal.raw) , -- TODO
introduction := lvl.introduction }
@@ -144,10 +128,13 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
let p ← parseParams LoadDocParams (toJson params)
let s ← get
let c ← read
- let some doc ← mkDoc p.name p.type
+ let some doc ← getInventoryDoc? p.name p.type
| do
c.hOut.writeLspResponseError ⟨id, .invalidParams, s!"Documentation not found: {p.name}", none⟩
return true
+ let doc : Doc :=
+ { name := doc.name.toString
+ text := doc.content }
c.hOut.writeLspResponse ⟨id, ToJson.toJson doc⟩
return true
| _ => return false
diff --git a/server/testgame/TestGame/LemmaDocs.lean b/server/testgame/TestGame/LemmaDocs.lean
index f1badf4..dfb8aa6 100644
--- a/server/testgame/TestGame/LemmaDocs.lean
+++ b/server/testgame/TestGame/LemmaDocs.lean
@@ -55,9 +55,6 @@ LemmaDoc add_zero as add_zero in "Addition"
LemmaDoc add_succ as add_succ in "Addition"
"This lemma says `∀ a b : ℕ, a + succ b = succ (a + b)`."
-LemmaSet addition : "Addition lemmas" :=
-zero_add add_zero
-
LemmaDoc not_forall as not_forall in "Logic"
"`∀ (A : Prop), ¬(∀ x, A) ↔ ∃x, (¬A)`."
@@ -106,14 +103,6 @@ LemmaDoc Nat.prime_def_lt'' as Nat.prime_def_lt'' in "Nat"
""
-
-
-LemmaSet natural : "Natürliche Zahlen" :=
-Even Odd not_odd not_even
-
-LemmaSet logic : "Logik" :=
-not_not not_forall not_exists
-
LemmaDoc Finset.sum_add_distrib as Finset.sum_add_distrib in "Sum"
""