Merge pull request #79 from leanprover-community/automatic_inventory_doc
add automatic inventory doc
This commit is contained in:
@@ -4,7 +4,7 @@ import './inventory.css'
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons'
|
import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons'
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import { useLoadDocQuery, ComputedInventoryItem, LevelInfo } from '../state/api';
|
import { useLoadDocQuery, InventoryTile, LevelInfo } from '../state/api';
|
||||||
import { GameIdContext } from '../App';
|
import { GameIdContext } from '../App';
|
||||||
|
|
||||||
export function Inventory({levelInfo, setInventoryDoc } :
|
export function Inventory({levelInfo, setInventoryDoc } :
|
||||||
@@ -13,6 +13,7 @@ export function Inventory({levelInfo, setInventoryDoc } :
|
|||||||
setInventoryDoc: (inventoryDoc: {name: string, type: string}) => void,
|
setInventoryDoc: (inventoryDoc: {name: string, type: string}) => void,
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
// TODO: This seems like a useless wrapper to me
|
||||||
function openDoc(name, type) {
|
function openDoc(name, type) {
|
||||||
setInventoryDoc({name, type})
|
setInventoryDoc({name, type})
|
||||||
}
|
}
|
||||||
@@ -36,7 +37,7 @@ export function Inventory({levelInfo, setInventoryDoc } :
|
|||||||
|
|
||||||
function InventoryList({items, docType, openDoc, defaultTab=null, level=undefined} :
|
function InventoryList({items, docType, openDoc, defaultTab=null, level=undefined} :
|
||||||
{
|
{
|
||||||
items: ComputedInventoryItem[],
|
items: InventoryTile[],
|
||||||
docType: string,
|
docType: string,
|
||||||
openDoc(name: string, type: string): void,
|
openDoc(name: string, type: string): void,
|
||||||
defaultTab? : string,
|
defaultTab? : string,
|
||||||
@@ -103,6 +104,8 @@ export function Documentation({name, type}) {
|
|||||||
|
|
||||||
return <>
|
return <>
|
||||||
<h2 className="doc">{doc.data?.displayName}</h2>
|
<h2 className="doc">{doc.data?.displayName}</h2>
|
||||||
<Markdown>{doc.data?.text}</Markdown>
|
<p><code>{doc.data?.statement}</code></p>
|
||||||
|
{/* <code>docstring: {doc.data?.docstring}</code> */}
|
||||||
|
<Markdown>{doc.data?.content}</Markdown>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ function PlayableLevel({worldId, levelId}) {
|
|||||||
}
|
}
|
||||||
}, [editor, commandLineMode])
|
}, [editor, commandLineMode])
|
||||||
|
|
||||||
|
// if this is set to a pair `(name, type)` then the according doc will be open.
|
||||||
const [inventoryDoc, setInventoryDoc] = useState<{name: string, type: string}>(null)
|
const [inventoryDoc, setInventoryDoc] = useState<{name: string, type: string}>(null)
|
||||||
|
|
||||||
const levelTitle = <>{levelId && `Level ${levelId}`}{level?.data?.title && `: ${level?.data?.title}`}</>
|
const levelTitle = <>{levelId && `Level ${levelId}`}{level?.data?.title && `: ${level?.data?.title}`}</>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface GameInfo {
|
|||||||
conclusion: null|string,
|
conclusion: null|string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComputedInventoryItem {
|
export interface InventoryTile {
|
||||||
name: string,
|
name: string,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
category: string,
|
category: string,
|
||||||
@@ -24,9 +24,9 @@ export interface LevelInfo {
|
|||||||
introduction: null|string,
|
introduction: null|string,
|
||||||
conclusion: null|string,
|
conclusion: null|string,
|
||||||
index: number,
|
index: number,
|
||||||
tactics: ComputedInventoryItem[],
|
tactics: InventoryTile[],
|
||||||
lemmas: ComputedInventoryItem[],
|
lemmas: InventoryTile[],
|
||||||
definitions: ComputedInventoryItem[],
|
definitions: InventoryTile[],
|
||||||
descrText: null|string,
|
descrText: null|string,
|
||||||
descrFormat: null|string,
|
descrFormat: null|string,
|
||||||
lemmaTab: null|string,
|
lemmaTab: null|string,
|
||||||
@@ -36,7 +36,10 @@ export interface LevelInfo {
|
|||||||
interface Doc {
|
interface Doc {
|
||||||
name: string,
|
name: string,
|
||||||
displayName: string,
|
displayName: string,
|
||||||
text: string
|
content: string,
|
||||||
|
statement: string,
|
||||||
|
type: string, // TODO: can I remove these?
|
||||||
|
category: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+224
-103
@@ -78,33 +78,40 @@ in the first level and get enabled during the game.
|
|||||||
|
|
||||||
/-! ## Doc entries -/
|
/-! ## Doc entries -/
|
||||||
|
|
||||||
/-- Throw a warning if inventory doc does not exist. If `(default := _)` is provided,
|
/-- Checks if `inventoryTemplateExt` contains an entry with `(type, name)` and yields
|
||||||
it will create a new inverntory entry with the specified default description. -/
|
a warning otherwise. If `template` is provided, it will add such an entry instead of yielding a
|
||||||
def checkInventoryDoc (type : InventoryType) (name : Syntax)
|
warning. -/
|
||||||
(default : Option (String) := none) : CommandElabM Unit := do
|
def checkInventoryDoc (type : InventoryType) (name : Ident)
|
||||||
let some _ := (inventoryDocExt.getState (← getEnv)).find?
|
(template : Option String := none) : CommandElabM Unit := do
|
||||||
(fun x => x.name == name.getId && x.type == type)
|
-- note: `name` is an `Ident` (instead of `Name`) for the log messages.
|
||||||
| match default with
|
let env ← getEnv
|
||||||
| some _ =>
|
let n := name.getId
|
||||||
logInfoAt name (m!"Missing {type} Documentation: {name}, used provided default (e.g. " ++
|
-- Find a key with matching `(type, name)`.
|
||||||
m!"statement description) instead. If you want to write your own description, add " ++
|
match (inventoryTemplateExt.getState env).findIdx?
|
||||||
m!"`{type}Doc {name}` somewhere above this statement.")
|
(fun x => x.name == n && x.type == type) with
|
||||||
| none =>
|
-- Nothing to do if the entry exists
|
||||||
logWarningAt name (m!"Missing {type} Documentation: {name}\nAdd `{type}Doc {name}` " ++
|
| some _ => pure ()
|
||||||
m!"somewhere above this statement.")
|
| none =>
|
||||||
|
match template with
|
||||||
let default₀ := match default with
|
-- Warn about missing documentation
|
||||||
| some d => d
|
| none =>
|
||||||
| none => "missing"
|
-- We just add a dummy entry
|
||||||
|
modifyEnv (inventoryTemplateExt.addEntry · {
|
||||||
-- Create a default inventory entry
|
|
||||||
let n := name.getId
|
|
||||||
modifyEnv (inventoryDocExt.addEntry · {
|
|
||||||
name := n
|
|
||||||
type := type
|
type := type
|
||||||
displayName := s!"{n}" -- TODO: for lemmas, only take the last part of the name
|
name := name.getId
|
||||||
|
category := if type == .Lemma then s!"{n.getPrefix}" else "" })
|
||||||
|
logWarningAt name (m!"Missing {type} Documentation: {name}\nAdd `{type}Doc {name}` " ++
|
||||||
|
m!"somewhere above this statement.")
|
||||||
|
-- Add the default documentation
|
||||||
|
| some s =>
|
||||||
|
modifyEnv (inventoryTemplateExt.addEntry · {
|
||||||
|
type := type
|
||||||
|
name := name.getId
|
||||||
category := if type == .Lemma then s!"{n.getPrefix}" else ""
|
category := if type == .Lemma then s!"{n.getPrefix}" else ""
|
||||||
content := default₀ })
|
content := s })
|
||||||
|
logInfoAt name (m!"Missing {type} Documentation: {name}, used provided default (e.g. " ++
|
||||||
|
m!"statement description) instead. If you want to write your own description, add " ++
|
||||||
|
m!"`{type}Doc {name}` somewhere above this statement.")
|
||||||
|
|
||||||
/-- Documentation entry of a tactic. Example:
|
/-- Documentation entry of a tactic. Example:
|
||||||
|
|
||||||
@@ -116,8 +123,7 @@ TacticDoc rw "`rw` stands for rewrite, etc. "
|
|||||||
* The description is a string supporting Markdown.
|
* The description is a string supporting Markdown.
|
||||||
-/
|
-/
|
||||||
elab "TacticDoc" name:ident content:str : command =>
|
elab "TacticDoc" name:ident content:str : command =>
|
||||||
modifyEnv (inventoryDocExt.addEntry · {
|
modifyEnv (inventoryTemplateExt.addEntry · {
|
||||||
category := default
|
|
||||||
type := .Tactic
|
type := .Tactic
|
||||||
name := name.getId
|
name := name.getId
|
||||||
displayName := name.getId.toString
|
displayName := name.getId.toString
|
||||||
@@ -134,14 +140,23 @@ LemmaDoc Nat.succ_pos as "succ_pos" in "Nat" "says `0 < n.succ`, etc."
|
|||||||
* The string following `as` is the displayed name (in the Inventory).
|
* The string following `as` is the displayed name (in the Inventory).
|
||||||
* The identifier after `in` is the category to group lemmas by (in the Inventory).
|
* The identifier after `in` is the category to group lemmas by (in the Inventory).
|
||||||
* The description is a string supporting Markdown.
|
* The description is a string supporting Markdown.
|
||||||
|
|
||||||
|
Use `[[mathlib_doc]]` in the string to insert a link to the mathlib doc page. This requries
|
||||||
|
The lemma/definition to have the same fully qualified name as in mathlib.
|
||||||
-/
|
-/
|
||||||
elab "LemmaDoc" name:ident "as" displayName:str "in" category:str content:str : command =>
|
elab "LemmaDoc" name:ident "as" displayName:str "in" category:str content:str : command =>
|
||||||
modifyEnv (inventoryDocExt.addEntry · {
|
modifyEnv (inventoryTemplateExt.addEntry · {
|
||||||
name := name.getId,
|
|
||||||
type := .Lemma
|
type := .Lemma
|
||||||
displayName := displayName.getString,
|
name := name.getId
|
||||||
category := category.getString,
|
category := category.getString
|
||||||
|
displayName := displayName.getString
|
||||||
content := content.getString })
|
content := content.getString })
|
||||||
|
-- TODO: Catch the following behaviour.
|
||||||
|
-- 1. if `LemmaDoc` appears in the same file as `Statement`, it will silently use
|
||||||
|
-- it but display the info that it wasn't found in `Statement`
|
||||||
|
-- 2. if it appears in a later file, however, it will silently not do anything and keep
|
||||||
|
-- the first one.
|
||||||
|
|
||||||
|
|
||||||
/-- Documentation entry of a definition. Example:
|
/-- Documentation entry of a definition. Example:
|
||||||
|
|
||||||
@@ -153,32 +168,67 @@ DefinitionDoc Function.Bijective as "Bijective" "defined as `Injective f ∧ Sur
|
|||||||
It is preferably the true name of the definition. However, this is not required.
|
It is preferably the true name of the definition. However, this is not required.
|
||||||
* The string following `as` is the displayed name (in the Inventory).
|
* The string following `as` is the displayed name (in the Inventory).
|
||||||
* The description is a string supporting Markdown.
|
* The description is a string supporting Markdown.
|
||||||
|
|
||||||
|
Use `[[mathlib_doc]]` in the string to insert a link to the mathlib doc page. This requries
|
||||||
|
The lemma/definition to have the same fully qualified name as in mathlib.
|
||||||
-/
|
-/
|
||||||
elab "DefinitionDoc" name:ident "as" displayName:str content:str : command =>
|
elab "DefinitionDoc" name:ident "as" displayName:str template:str : command =>
|
||||||
modifyEnv (inventoryDocExt.addEntry · {
|
modifyEnv (inventoryTemplateExt.addEntry · {
|
||||||
category := default
|
|
||||||
type := .Definition
|
type := .Definition
|
||||||
name := name.getId,
|
name := name.getId,
|
||||||
displayName := displayName.getString,
|
displayName := displayName.getString,
|
||||||
content := content.getString })
|
content := template.getString })
|
||||||
|
|
||||||
/-! ## Add inventory items -/
|
/-! ## Add inventory items -/
|
||||||
|
|
||||||
|
-- namespace Lean.PrettyPrinter
|
||||||
|
-- def ppSignature' (c : Name) : MetaM String := do
|
||||||
|
-- let decl ← getConstInfo c
|
||||||
|
-- let e := .const c (decl.levelParams.map mkLevelParam)
|
||||||
|
-- let (stx, _) ← delabCore e (delab := Delaborator.delabConstWithSignature)
|
||||||
|
-- let f ← ppTerm stx
|
||||||
|
-- return toString f
|
||||||
|
-- end Lean.PrettyPrinter
|
||||||
|
|
||||||
|
def getStatement (name : Name) : CommandElabM MessageData := do
|
||||||
|
-- let c := name.getId
|
||||||
|
|
||||||
|
let decl ← getConstInfo name
|
||||||
|
-- -- TODO: How to go between CommandElabM and MetaM
|
||||||
|
|
||||||
|
-- addCompletionInfo <| .id name c (danglingDot := false) {} none
|
||||||
|
return ← addMessageContextPartial (.ofPPFormat { pp := fun
|
||||||
|
| some ctx => ctx.runMetaM <| ppExpr decl.type
|
||||||
|
-- PrettyPrinter.ppSignature' c
|
||||||
|
-- PrettyPrinter.ppSignature c
|
||||||
|
| none => return "that's a bug." })
|
||||||
|
|
||||||
|
-- Note: We use `String` because we can't send `MessageData` as json, but
|
||||||
|
-- `MessageData` might be better for interactive highlighting.
|
||||||
|
/-- Get a string of the form `my_lemma (n : ℕ) : n + n = 2 * n`. -/
|
||||||
|
def getStatementString (name : Name) : CommandElabM String := do
|
||||||
|
try
|
||||||
|
return ← (← getStatement name).toString
|
||||||
|
catch
|
||||||
|
| _ => throwError m!"Could not find {name} in context."
|
||||||
|
-- TODO: I think it would be nicer to unresolve Namespaces as much as possible.
|
||||||
|
|
||||||
/-- Declare tactics that are introduced by this level. -/
|
/-- Declare tactics that are introduced by this level. -/
|
||||||
elab "NewTactic" args:ident* : command => do
|
elab "NewTactic" args:ident* : command => do
|
||||||
for name in ↑args do checkInventoryDoc .Tactic name
|
for name in ↑args do checkInventoryDoc .Tactic name -- TODO: Add (template := "[docstring]")
|
||||||
modifyCurLevel fun level => pure {level with
|
modifyCurLevel fun level => pure {level with
|
||||||
tactics := {level.tactics with new := args.map (·.getId)}}
|
tactics := {level.tactics with new := args.map (·.getId)}}
|
||||||
|
|
||||||
/-- Declare lemmas that are introduced by this level. -/
|
/-- Declare lemmas that are introduced by this level. -/
|
||||||
elab "NewLemma" args:ident* : command => do
|
elab "NewLemma" args:ident* : command => do
|
||||||
for name in ↑args do checkInventoryDoc .Lemma name
|
for name in ↑args do
|
||||||
|
checkInventoryDoc .Lemma name -- TODO: Add (template := "[mathlib]")
|
||||||
modifyCurLevel fun level => pure {level with
|
modifyCurLevel fun level => pure {level with
|
||||||
lemmas := {level.lemmas with new := args.map (·.getId)}}
|
lemmas := {level.lemmas with new := args.map (·.getId)}}
|
||||||
|
|
||||||
/-- Declare definitions that are introduced by this level. -/
|
/-- Declare definitions that are introduced by this level. -/
|
||||||
elab "NewDefinition" args:ident* : command => do
|
elab "NewDefinition" args:ident* : command => do
|
||||||
for name in ↑args do checkInventoryDoc .Definition name
|
for name in ↑args do checkInventoryDoc .Definition name -- TODO: Add (template := "[mathlib]")
|
||||||
modifyCurLevel fun level => pure {level with
|
modifyCurLevel fun level => pure {level with
|
||||||
definitions := {level.definitions with new := args.map (·.getId)}}
|
definitions := {level.definitions with new := args.map (·.getId)}}
|
||||||
|
|
||||||
@@ -228,22 +278,6 @@ elab "LemmaTab" category:str : command =>
|
|||||||
|
|
||||||
/-! # Exercise Statement -/
|
/-! # Exercise Statement -/
|
||||||
|
|
||||||
-- TODO: Instead of this, it would be nice to have a proper syntax parser that enables
|
|
||||||
-- us highlighting on the client side.
|
|
||||||
partial def reprintCore : Syntax → Option Format
|
|
||||||
| Syntax.missing => none
|
|
||||||
| Syntax.atom _ val => val.trim
|
|
||||||
| Syntax.ident _ rawVal _ _ => rawVal.toString
|
|
||||||
| Syntax.node _ _ args =>
|
|
||||||
match args.toList.filterMap reprintCore with
|
|
||||||
| [] => none
|
|
||||||
| [arg] => arg
|
|
||||||
| args => Format.group <| Format.nest 2 <| Format.joinSep args " "
|
|
||||||
|
|
||||||
/-- `reprint` is used to display the Lean-statement to the user-/
|
|
||||||
def reprint (stx : Syntax) : Format :=
|
|
||||||
reprintCore stx |>.getD ""
|
|
||||||
|
|
||||||
/-- A `attr := ...` option for `Statement`. Add attributes to the defined theorem. -/
|
/-- A `attr := ...` option for `Statement`. Add attributes to the defined theorem. -/
|
||||||
syntax statementAttr := "(" &"attr" ":=" Parser.Term.attrInstance,* ")"
|
syntax statementAttr := "(" &"attr" ":=" Parser.Term.attrInstance,* ")"
|
||||||
-- TODO
|
-- TODO
|
||||||
@@ -259,14 +293,9 @@ elab "Statement" statementName:ident ? descr:str ? sig:declSig val:declVal : com
|
|||||||
-- Save the messages before evaluation of the proof.
|
-- Save the messages before evaluation of the proof.
|
||||||
let initMsgs ← modifyGet fun st => (st.messages, { st with messages := {} })
|
let initMsgs ← modifyGet fun st => (st.messages, { st with messages := {} })
|
||||||
|
|
||||||
-- Check that statement has a docs entry.
|
|
||||||
match statementName with
|
|
||||||
| some name => checkInventoryDoc .Lemma name (default := descr)
|
|
||||||
| none => pure ()
|
|
||||||
|
|
||||||
-- The default name of the statement is `[Game].[World].level[no.]`, e.g. `NNG.Addition.level1`
|
-- The default name of the statement is `[Game].[World].level[no.]`, e.g. `NNG.Addition.level1`
|
||||||
-- However, this should not be used when designing the game.
|
-- However, this should not be used when designing the game.
|
||||||
let defaultDeclName : Name := (← getCurGame).name ++ (← getCurWorld).name ++
|
let defaultDeclName : Ident := mkIdent <| (← getCurGame).name ++ (← getCurWorld).name ++
|
||||||
("level" ++ toString lvlIdx : String)
|
("level" ++ toString lvlIdx : String)
|
||||||
|
|
||||||
-- Add theorem to context.
|
-- Add theorem to context.
|
||||||
@@ -277,35 +306,21 @@ elab "Statement" statementName:ident ? descr:str ? sig:declSig val:declVal : com
|
|||||||
let origType := (env.constants.map₁.find! name.getId).type
|
let origType := (env.constants.map₁.find! name.getId).type
|
||||||
-- TODO: Check if `origType` agrees with `sig` and output `logInfo` instead of `logWarning`
|
-- TODO: Check if `origType` agrees with `sig` and output `logInfo` instead of `logWarning`
|
||||||
-- in that case.
|
-- in that case.
|
||||||
logWarningAt name m!"Environment already contains {name.getId}!
|
logWarningAt name (m!"Environment already contains {name.getId}! Only the existing " ++
|
||||||
Only the existing statement will be available in later levels:
|
m!"statement will be available in later levels:\n\n{origType}")
|
||||||
|
let thmStatement ← `(theorem $defaultDeclName $sig $val)
|
||||||
{origType}"
|
|
||||||
-- let (binders, typeStx) := expandDeclSig sig
|
|
||||||
-- --let type ← Term.elabType typeStx
|
|
||||||
-- runTermElabM (fun vars =>
|
|
||||||
-- Term.elabBinders binders.getArgs (fun xs => do
|
|
||||||
-- let type ← Term.elabType typeStx
|
|
||||||
-- --Term.synthesizeSyntheticMVarsNoPostponing
|
|
||||||
-- --let type ← instantiateMVars type
|
|
||||||
-- --let type ← mkForallFVars xs type
|
|
||||||
-- ))
|
|
||||||
--let newType := Term.elabTerm sig.raw
|
|
||||||
--dbg_trace newType
|
|
||||||
--logInfo origType
|
|
||||||
-- dbg_trace sig
|
|
||||||
-- dbg_trace origType
|
|
||||||
--dbg_trace (env.constants.map₁.find! name.getId).value! -- that's the proof
|
|
||||||
--let newType := Lean.Elab.Term.elabTerm sig none
|
|
||||||
|
|
||||||
let thmStatement ← `(theorem $(mkIdent defaultDeclName) $sig $val)
|
|
||||||
elabCommand thmStatement
|
elabCommand thmStatement
|
||||||
|
-- Check that statement has a docs entry.
|
||||||
|
checkInventoryDoc .Lemma name (template := descr)
|
||||||
|
|
||||||
else
|
else
|
||||||
-- logInfo attr
|
let thmStatement ← `( theorem $name $sig $val)
|
||||||
let thmStatement ← `( theorem $(mkIdent name.getId) $sig $val)
|
|
||||||
elabCommand thmStatement
|
elabCommand thmStatement
|
||||||
|
-- Check that statement has a docs entry.
|
||||||
|
checkInventoryDoc .Lemma name (template := descr)
|
||||||
|
|
||||||
| none =>
|
| none =>
|
||||||
let thmStatement ← `(theorem $(mkIdent defaultDeclName) $sig $val)
|
let thmStatement ← `(theorem $defaultDeclName $sig $val)
|
||||||
elabCommand thmStatement
|
elabCommand thmStatement
|
||||||
|
|
||||||
let msgs := (← get).messages
|
let msgs := (← get).messages
|
||||||
@@ -339,7 +354,15 @@ Only the existing statement will be available in later levels:
|
|||||||
let scope ← getScope
|
let scope ← getScope
|
||||||
let env ← getEnv
|
let env ← getEnv
|
||||||
|
|
||||||
modifyCurLevel fun level => pure {level with
|
let st ← match statementName with
|
||||||
|
| some name => getStatementString name.getId
|
||||||
|
| none => getStatementString defaultDeclName.getId -- TODO: We dont want the internal lemma name here
|
||||||
|
|
||||||
|
let head := match statementName with
|
||||||
|
| some name => Format.join ["theorem ", name.getId.toString]
|
||||||
|
| none => "example"
|
||||||
|
|
||||||
|
modifyCurLevel fun level => pure { level with
|
||||||
module := env.header.mainModule
|
module := env.header.mainModule
|
||||||
goal := sig,
|
goal := sig,
|
||||||
scope := scope,
|
scope := scope,
|
||||||
@@ -347,13 +370,8 @@ Only the existing statement will be available in later levels:
|
|||||||
statementName := match statementName with
|
statementName := match statementName with
|
||||||
| none => default
|
| none => default
|
||||||
| some name => name.getId
|
| some name => name.getId
|
||||||
descrFormat := match statementName with
|
descrFormat := (Format.join [head, " ", st, " := by"]).pretty 10
|
||||||
| none => "example " ++ (toString <| reprint sig.raw) ++ " := by"
|
hints := hints }
|
||||||
| some name => (Format.join ["theorem ", reprint name.raw, " ", reprint sig.raw, " := by"]).pretty 10 -- "lemma " ++ (toString <| reprint name.raw) ++ " " ++ (Format.pretty (reprint sig.raw) 40) ++ " := by"
|
|
||||||
hints := hints
|
|
||||||
} -- Format.pretty <| format thmStatement.raw }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/-! # Hints -/
|
/-! # Hints -/
|
||||||
|
|
||||||
@@ -475,8 +493,21 @@ elab "Hole" t:tacticSeq : tactic => do
|
|||||||
Tactic.evalTactic t
|
Tactic.evalTactic t
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO: Notes for testing if a declaration has the simp attribute
|
||||||
|
|
||||||
|
-- -- Test: From zulip
|
||||||
|
-- section test
|
||||||
|
|
||||||
|
-- open Lean Meta Elab Command Tactic Simp
|
||||||
|
|
||||||
|
-- def Lean.Meta.SimpTheorems.hasAttribute (d : SimpTheorems) (decl : Name) :=
|
||||||
|
-- d.isLemma (.decl decl) || d.isDeclToUnfold decl
|
||||||
|
|
||||||
|
-- def isInSimpset (simpAttr decl: Name) : CoreM Bool := do
|
||||||
|
-- let .some simpDecl ←getSimpExtension? simpAttr | return false
|
||||||
|
-- return (← simpDecl.getTheorems).hasAttribute decl
|
||||||
|
|
||||||
|
-- end test
|
||||||
|
|
||||||
/-! # Make Game -/
|
/-! # Make Game -/
|
||||||
|
|
||||||
@@ -486,10 +517,68 @@ def GameLevel.getInventory (level : GameLevel) : InventoryType → InventoryInfo
|
|||||||
| .Lemma => level.lemmas
|
| .Lemma => level.lemmas
|
||||||
|
|
||||||
def GameLevel.setComputedInventory (level : GameLevel) :
|
def GameLevel.setComputedInventory (level : GameLevel) :
|
||||||
InventoryType → Array ComputedInventoryItem → GameLevel
|
InventoryType → Array InventoryTile → GameLevel
|
||||||
| .Tactic, v => {level with tactics := {level.tactics with computed := v}}
|
| .Tactic, v => {level with tactics := {level.tactics with tiles := v}}
|
||||||
| .Definition, v => {level with definitions := {level.definitions with computed := v}}
|
| .Definition, v => {level with definitions := {level.definitions with tiles := v}}
|
||||||
| .Lemma, v => {level with lemmas := {level.lemmas with computed := v}}
|
| .Lemma, v => {level with lemmas := {level.lemmas with tiles := v}}
|
||||||
|
|
||||||
|
/-- Copied from `Mathlib.Tactic.HelpCmd`.
|
||||||
|
|
||||||
|
Gets the initial string token in a parser description. For example, for a declaration like
|
||||||
|
`syntax "bla" "baz" term : tactic`, it returns `some "bla"`. Returns `none` for syntax declarations
|
||||||
|
that don't start with a string constant. -/
|
||||||
|
partial def getHeadTk (e : Expr) : Option String :=
|
||||||
|
match (Expr.withApp e λ e a => (e.constName?.getD Name.anonymous, a)) with
|
||||||
|
| (``ParserDescr.node, #[_, _, p]) => getHeadTk p
|
||||||
|
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "withPosition")), p]) => getHeadTk p
|
||||||
|
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "atomic")), p]) => getHeadTk p
|
||||||
|
| (``ParserDescr.binary, #[.app _ (.lit (.strVal "andthen")), p, _]) => getHeadTk p
|
||||||
|
| (``ParserDescr.nonReservedSymbol, #[.lit (.strVal tk), _]) => some tk
|
||||||
|
| (``ParserDescr.symbol, #[.lit (.strVal tk)]) => some tk
|
||||||
|
| (``Parser.withAntiquot, #[_, p]) => getHeadTk p
|
||||||
|
| (``Parser.leadingNode, #[_, _, p]) => getHeadTk p
|
||||||
|
| (``HAndThen.hAndThen, #[_, _, _, _, p, _]) => getHeadTk p
|
||||||
|
| (``Parser.nonReservedSymbol, #[.lit (.strVal tk), _]) => some tk
|
||||||
|
| (``Parser.symbol, #[.lit (.strVal tk)]) => some tk
|
||||||
|
| _ => none
|
||||||
|
|
||||||
|
/-- Modified from `#help` in `Mathlib.Tactic.HelpCmd` -/
|
||||||
|
def getTacticDocstring (env : Environment) (name: Name) : CommandElabM (Option String) := do
|
||||||
|
let name := name.toString (escape := false)
|
||||||
|
let mut decls : Lean.RBMap String (Array SyntaxNodeKind) compare := {}
|
||||||
|
|
||||||
|
let catName : Name := `tactic
|
||||||
|
let catStx : Ident := mkIdent catName -- TODO
|
||||||
|
let some cat := (Parser.parserExtension.getState env).categories.find? catName
|
||||||
|
| throwErrorAt catStx "{catStx} is not a syntax category"
|
||||||
|
liftTermElabM <| Term.addCategoryInfo catStx catName
|
||||||
|
for (k, _) in cat.kinds do
|
||||||
|
let mut used := false
|
||||||
|
if let some tk := do getHeadTk (← (← env.find? k).value?) then
|
||||||
|
let tk := tk.trim
|
||||||
|
if name ≠ tk then -- was `!name.isPrefixOf tk`
|
||||||
|
continue
|
||||||
|
used := true
|
||||||
|
decls := decls.insert tk ((decls.findD tk #[]).push k)
|
||||||
|
for (_name, ks) in decls do
|
||||||
|
for k in ks do
|
||||||
|
if let some doc ← findDocString? env k then
|
||||||
|
return doc
|
||||||
|
|
||||||
|
logWarning <| m!"Could not find a docstring for this tactic, consider adding one " ++
|
||||||
|
m!"using `TacticDoc {name} \"some doc\"`"
|
||||||
|
return none
|
||||||
|
|
||||||
|
/-- Retrieve the docstring associated to an inventory item. For Tactics, this
|
||||||
|
is not guaranteed to work. -/
|
||||||
|
def getDocstring (env : Environment) (name : Name) (type : InventoryType) :
|
||||||
|
CommandElabM (Option String) :=
|
||||||
|
match type with
|
||||||
|
-- for tactics it's a lookup following mathlib's `#help`. not guaranteed to be the correct one.
|
||||||
|
| .Tactic => getTacticDocstring env name
|
||||||
|
| .Lemma => findDocString? env name
|
||||||
|
-- TODO: for definitions not implemented yet, does it work?
|
||||||
|
| .Definition => findDocString? env name
|
||||||
|
|
||||||
/-- Build the game. This command will precompute various things about the game, such as which
|
/-- Build the game. This command will precompute various things about the game, such as which
|
||||||
tactics are available in each level etc. -/
|
tactics are available in each level etc. -/
|
||||||
@@ -500,6 +589,37 @@ elab "MakeGame" : command => do
|
|||||||
if game.worlds.hasLoops then
|
if game.worlds.hasLoops then
|
||||||
throwError "World graph must not contain loops! Check your `Path` declarations."
|
throwError "World graph must not contain loops! Check your `Path` declarations."
|
||||||
|
|
||||||
|
let env ← getEnv
|
||||||
|
|
||||||
|
-- Now create The doc entries from the templates
|
||||||
|
for item in inventoryTemplateExt.getState env do
|
||||||
|
let name := item.name
|
||||||
|
|
||||||
|
let docstring ← getDocstring env name item.type
|
||||||
|
let content : String := match item.content with
|
||||||
|
| "" =>
|
||||||
|
-- If documentation is missing, try using the docstring instead.
|
||||||
|
match docstring with
|
||||||
|
| some ds => s!"*(lean docstring)*\\\n{ds}" -- TODO `\n` does not work in markdown
|
||||||
|
| none => "(missing)"
|
||||||
|
| template =>
|
||||||
|
-- TODO: Process content template.
|
||||||
|
-- TODO: Add information about inventory items
|
||||||
|
template.replace "[[mathlib_doc]]"
|
||||||
|
s!"[mathlib doc](https://leanprover-community.github.io/mathlib4_docs/find/?pattern={name}#doc)"
|
||||||
|
|
||||||
|
match item.type with
|
||||||
|
| .Lemma =>
|
||||||
|
modifyEnv (inventoryExt.addEntry · { item with
|
||||||
|
content := content
|
||||||
|
-- Add the lemma statement to the doc
|
||||||
|
statement := (← getStatementString name)
|
||||||
|
})
|
||||||
|
| _ =>
|
||||||
|
modifyEnv (inventoryExt.addEntry · { item with
|
||||||
|
content := content
|
||||||
|
})
|
||||||
|
|
||||||
-- Compute which inventory items are available in which level:
|
-- Compute which inventory items are available in which level:
|
||||||
for inventoryType in #[.Tactic, .Definition, .Lemma] do
|
for inventoryType in #[.Tactic, .Definition, .Lemma] do
|
||||||
let mut newItemsInWorld : HashMap Name (HashSet Name) := {}
|
let mut newItemsInWorld : HashMap Name (HashSet Name) := {}
|
||||||
@@ -540,24 +660,25 @@ elab "MakeGame" : command => do
|
|||||||
newItemsInWorld := newItemsInWorld.insert worldId newItems
|
newItemsInWorld := newItemsInWorld.insert worldId newItems
|
||||||
|
|
||||||
-- Basic inventory item availability: all locked.
|
-- Basic inventory item availability: all locked.
|
||||||
let Availability₀ : HashMap Name ComputedInventoryItem :=
|
let Availability₀ : HashMap Name InventoryTile :=
|
||||||
HashMap.ofList $
|
HashMap.ofList $
|
||||||
← allItems.toList.mapM fun item => do
|
← allItems.toList.mapM fun item => do
|
||||||
let data := (← getInventoryDoc? item inventoryType).get!
|
let data := (← getInventoryItem? item inventoryType).get!
|
||||||
|
-- TODO: BUG, panic at `get!` in vscode
|
||||||
return (item, {
|
return (item, {
|
||||||
name := item
|
name := item
|
||||||
displayName := data.displayName
|
displayName := data.displayName
|
||||||
category := data.category })
|
category := data.category })
|
||||||
|
|
||||||
-- Availability after a given world
|
-- Availability after a given world
|
||||||
let mut itemsInWorld : HashMap Name (HashMap Name ComputedInventoryItem) := {}
|
let mut itemsInWorld : HashMap Name (HashMap Name InventoryTile) := {}
|
||||||
for (worldId, _) in game.worlds.nodes.toArray do
|
for (worldId, _) in game.worlds.nodes.toArray do
|
||||||
-- Unlock all items from previous worlds
|
-- Unlock all items from previous worlds
|
||||||
let mut items := Availability₀
|
let mut items := Availability₀
|
||||||
let predecessors := game.worlds.predecessors worldId
|
let predecessors := game.worlds.predecessors worldId
|
||||||
for predWorldId in predecessors do
|
for predWorldId in predecessors do
|
||||||
for item in newItemsInWorld.find! predWorldId do
|
for item in newItemsInWorld.find! predWorldId do
|
||||||
let data := (← getInventoryDoc? item inventoryType).get!
|
let data := (← getInventoryItem? item inventoryType).get!
|
||||||
items := items.insert item {
|
items := items.insert item {
|
||||||
name := item
|
name := item
|
||||||
displayName := data.displayName
|
displayName := data.displayName
|
||||||
@@ -575,7 +696,7 @@ elab "MakeGame" : command => do
|
|||||||
|
|
||||||
-- unlock items that are unlocked in this level
|
-- unlock items that are unlocked in this level
|
||||||
for item in levelInfo.new do
|
for item in levelInfo.new do
|
||||||
let data := (← getInventoryDoc? item inventoryType).get!
|
let data := (← getInventoryItem? item inventoryType).get!
|
||||||
items := items.insert item {
|
items := items.insert item {
|
||||||
name := item
|
name := item
|
||||||
displayName := data.displayName
|
displayName := data.displayName
|
||||||
@@ -588,7 +709,7 @@ elab "MakeGame" : command => do
|
|||||||
match lemmaStatements.find? (worldId, levelId) with
|
match lemmaStatements.find? (worldId, levelId) with
|
||||||
| none => pure ()
|
| none => pure ()
|
||||||
| some name =>
|
| some name =>
|
||||||
let data := (← getInventoryDoc? name inventoryType).get!
|
let data := (← getInventoryItem? name inventoryType).get!
|
||||||
items := items.insert name {
|
items := items.insert name {
|
||||||
name := name
|
name := name
|
||||||
displayName := data.displayName
|
displayName := data.displayName
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import GameServer.AbstractCtx
|
import GameServer.AbstractCtx
|
||||||
import GameServer.Graph
|
import GameServer.Graph
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/-- The default game name if `Game "MyGame"` is not used. -/
|
||||||
|
def defaultGameName: String := "MyGame"
|
||||||
|
-- Note: When changing any of these default names, one also needs to change them in `index.mjs`
|
||||||
|
|
||||||
|
/-- The default game module name. -/
|
||||||
|
def defaultGameModule: String := "Game"
|
||||||
|
|
||||||
/-! # Environment extensions
|
/-! # Environment extensions
|
||||||
|
|
||||||
The game framework stores almost all its game building data in environment extensions
|
The game framework stores almost all its game building data in environment extensions
|
||||||
@@ -9,19 +18,9 @@ defined in this file.
|
|||||||
|
|
||||||
open Lean
|
open Lean
|
||||||
|
|
||||||
-- Note: When changing these, one also needs to change them in `index.mjs`
|
|
||||||
|
|
||||||
/-! The default game name if `Game "MyGame"` is not used. -/
|
|
||||||
def defaultGameName: String := "MyGame"
|
|
||||||
|
|
||||||
/-! The default game module name. -/
|
|
||||||
def defaultGameModule: String := "Game"
|
|
||||||
|
|
||||||
/-! ## Hints -/
|
/-! ## Hints -/
|
||||||
|
|
||||||
/--
|
/-- A hint to help the user with a specific goal state -/
|
||||||
A hint to help the user with a specific goal state
|
|
||||||
-/
|
|
||||||
structure GoalHintEntry where
|
structure GoalHintEntry where
|
||||||
goal : AbstractCtxResult
|
goal : AbstractCtxResult
|
||||||
/-- Text of the hint as an expression of type `Array Expr → MessageData` -/
|
/-- Text of the hint as an expression of type `Array Expr → MessageData` -/
|
||||||
@@ -35,38 +34,58 @@ instance : Repr GoalHintEntry := {
|
|||||||
reprPrec := fun a n => reprPrec a.text n
|
reprPrec := fun a n => reprPrec a.text n
|
||||||
}
|
}
|
||||||
|
|
||||||
/-! ## Tactic/Definition/Lemma documentation -/
|
/-! ## Inventory (documentation)
|
||||||
|
|
||||||
|
The inventory contains documentation that the user can access.
|
||||||
|
There are three inventory types: Lemma, Tactic, Definition. They vary about in the information
|
||||||
|
they carry.
|
||||||
|
|
||||||
|
The commands `LemmaDoc`, `TacticDoc`, and `DefinitionDoc` add keys and templates to an
|
||||||
|
env. extension called `InventoryTemplateExt`. Commands like `NewLemma`, etc. as well as
|
||||||
|
`Statement` check if there is a key registered in this extension and might add a default or
|
||||||
|
print a warning if not.
|
||||||
|
|
||||||
|
Then, `MakeGame` takes the templates from `InventoryTemplateExt` and creates the documentation entries
|
||||||
|
that are sent to the client. This allows us to modify them like adding information from
|
||||||
|
mathlib or from parsing the lemma in question.
|
||||||
|
-/
|
||||||
|
|
||||||
|
/-- The game knows three different inventory types that contain slightly different information -/
|
||||||
inductive InventoryType := | Tactic | Lemma | Definition
|
inductive InventoryType := | Tactic | Lemma | Definition
|
||||||
deriving ToJson, FromJson, Repr, BEq, Hashable, Inhabited
|
deriving ToJson, FromJson, Repr, BEq, Hashable, Inhabited
|
||||||
|
|
||||||
|
-- TODO: golf this?
|
||||||
instance : ToString InventoryType := ⟨fun t => match t with
|
instance : ToString InventoryType := ⟨fun t => match t with
|
||||||
| .Tactic => "Tactic"
|
| .Tactic => "Tactic"
|
||||||
| .Lemma => "Lemma"
|
| .Lemma => "Lemma"
|
||||||
| .Definition => "Definition"
|
| .Definition => "Definition"⟩
|
||||||
⟩
|
|
||||||
|
|
||||||
/-- An inventory item represents the documentation of a tactic/lemma/definitions. -/
|
/-- The keys/templates of the inventory items, stored in `InventoryTemplateExt`. -/
|
||||||
structure InventoryDocEntry where
|
structure InventoryTemplate where
|
||||||
/--
|
/-- Lemma, Tactic, or Definition -/
|
||||||
The name of the item. The restrictions are:
|
type: InventoryType
|
||||||
|
/-- Depends on the type:
|
||||||
* for Tactics: The name of the tactic.
|
* Tactic: the tactic's name
|
||||||
* for Lemmas: *Fully qualified* lemma name.
|
* Lemma: fully qualified lemma name
|
||||||
* for Definitions: no restrictions.
|
* Definition: no restrictions (preferrably the definions fully qualified name)
|
||||||
-/
|
-/
|
||||||
name : Name
|
name: Name
|
||||||
/-- One of `Tactic`, `Lemma` and `Definition`. -/
|
/-- Only for Lemmas. To sort them into tabs -/
|
||||||
type : InventoryType
|
category: String := default
|
||||||
/-- The display name shown in the inventory. This can be free-text. -/
|
/-- Free-text short name -/
|
||||||
displayName : String
|
displayName: String := name.toString
|
||||||
/-- Category to group inventory items by. (currently only used for lemmas) -/
|
/-- Template documentation. Allows for special tags to insert mathlib info [TODO!] -/
|
||||||
category : String
|
content: String := ""
|
||||||
/-- The description (doc) of the item. (free-text) -/
|
|
||||||
content : String
|
|
||||||
deriving ToJson, Repr, Inhabited
|
deriving ToJson, Repr, Inhabited
|
||||||
|
|
||||||
structure ComputedInventoryItem where
|
/-- A full inventory item including the processing by `MakeGame`, which creates these
|
||||||
|
from the `InventoryTemplate`s and modifies them. -/
|
||||||
|
structure InventoryItem extends InventoryTemplate where
|
||||||
|
statement: String := ""
|
||||||
|
deriving ToJson, Repr, Inhabited
|
||||||
|
|
||||||
|
/-- A reduced variant of `InventoryItem` which is used for the tiles in the doc -/
|
||||||
|
structure InventoryTile where
|
||||||
/--
|
/--
|
||||||
The name of the item. The restrictions are:
|
The name of the item. The restrictions are:
|
||||||
|
|
||||||
@@ -87,26 +106,36 @@ structure ComputedInventoryItem where
|
|||||||
new := false
|
new := false
|
||||||
deriving ToJson, FromJson, Repr, Inhabited
|
deriving ToJson, FromJson, Repr, Inhabited
|
||||||
|
|
||||||
/-- Environment extension for inventory documentation. -/
|
/-- The extension that stores the doc templates. Note that you can only add, but never modify
|
||||||
initialize inventoryDocExt : SimplePersistentEnvExtension InventoryDocEntry (Array InventoryDocEntry) ←
|
entries! -/
|
||||||
|
initialize inventoryTemplateExt :
|
||||||
|
SimplePersistentEnvExtension InventoryTemplate (Array InventoryTemplate) ←
|
||||||
|
registerSimplePersistentEnvExtension {
|
||||||
|
name := `inventory_keys
|
||||||
|
addEntryFn := Array.push
|
||||||
|
addImportedFn := Array.concatMap id }
|
||||||
|
|
||||||
|
/-- Receive the template with that matches `(name, type)` -/
|
||||||
|
def getInventoryTemplate? [Monad m] [MonadEnv m] (n : Name) (type : InventoryType) :
|
||||||
|
m (Option InventoryTemplate) := do
|
||||||
|
return (inventoryTemplateExt.getState (← getEnv)).find? (fun x => x.name == n && x.type == type)
|
||||||
|
|
||||||
|
/-- The extension that contains the inventory content after it has been processed.
|
||||||
|
`MakeGame` is the only command adding items here. -/
|
||||||
|
initialize inventoryExt : SimplePersistentEnvExtension InventoryItem (Array InventoryItem) ←
|
||||||
registerSimplePersistentEnvExtension {
|
registerSimplePersistentEnvExtension {
|
||||||
name := `inventory_doc
|
name := `inventory_doc
|
||||||
addEntryFn := Array.push
|
addEntryFn := Array.push
|
||||||
addImportedFn := Array.concatMap id
|
addImportedFn := Array.concatMap id }
|
||||||
}
|
|
||||||
|
|
||||||
def getInventoryDoc? {m : Type → Type} [Monad m] [MonadEnv m] (n : Name) (type : InventoryType) :
|
/-- Receive the item with that matches `(name, type)` -/
|
||||||
m (Option InventoryDocEntry) := do
|
def getInventoryItem? [Monad m] [MonadEnv m] (n : Name) (type : InventoryType) :
|
||||||
return (inventoryDocExt.getState (← getEnv)).find? (fun x => x.name == n && x.type == type)
|
m (Option InventoryItem) := do
|
||||||
|
return (inventoryExt.getState (← getEnv)).find? (fun x => x.name == n && x.type == type)
|
||||||
open Elab Command in
|
|
||||||
/-- 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-/
|
|
||||||
|
/-! ## Environment extensions for game specification -/
|
||||||
|
|
||||||
/-- Register a (non-persistent) environment extension to hold the current level -/
|
/-- Register a (non-persistent) environment extension to hold the current level -/
|
||||||
initialize curGameExt : EnvExtension (Option Name) ← registerEnvExtension (pure none)
|
initialize curGameExt : EnvExtension (Option Name) ← registerEnvExtension (pure none)
|
||||||
@@ -115,37 +144,50 @@ initialize curWorldExt : EnvExtension (Option Name) ← registerEnvExtension (pu
|
|||||||
/-- Register a (non-persistent) environment extension to hold the current level -/
|
/-- Register a (non-persistent) environment extension to hold the current level -/
|
||||||
initialize curLevelExt : EnvExtension (Option Nat) ← registerEnvExtension (pure none)
|
initialize curLevelExt : EnvExtension (Option Nat) ← registerEnvExtension (pure none)
|
||||||
|
|
||||||
|
/--
|
||||||
|
A game has three layers: Game, World, Levels. These are set with the commands
|
||||||
|
`Game`, `World`, and `Level`. Commands like `Introduction` depend on the current level.
|
||||||
|
-/
|
||||||
inductive Layer :=
|
inductive Layer :=
|
||||||
| Game | World | Level
|
| Game | World | Level
|
||||||
|
|
||||||
variable {m: Type → Type} [Monad m] [MonadEnv m]
|
variable {m: Type → Type} [Monad m] [MonadEnv m]
|
||||||
|
|
||||||
|
/-- Set the current game -/
|
||||||
def setCurGameId (game : Name) : m Unit :=
|
def setCurGameId (game : Name) : m Unit :=
|
||||||
modifyEnv (curGameExt.setState · (some game))
|
modifyEnv (curGameExt.setState · game)
|
||||||
|
|
||||||
|
/-- Set the current world -/
|
||||||
def setCurWorldId (world : Name) : m Unit :=
|
def setCurWorldId (world : Name) : m Unit :=
|
||||||
modifyEnv (curWorldExt.setState · (some world))
|
modifyEnv (curWorldExt.setState · world)
|
||||||
|
|
||||||
|
/-- Set the current level -/
|
||||||
def setCurLevelIdx (level : Nat) : m Unit :=
|
def setCurLevelIdx (level : Nat) : m Unit :=
|
||||||
modifyEnv (curLevelExt.setState · (some level))
|
modifyEnv (curLevelExt.setState · level)
|
||||||
|
|
||||||
|
/-- Get the current layer. -/
|
||||||
def getCurLayer [MonadError m] : m Layer := do
|
def getCurLayer [MonadError m] : m Layer := do
|
||||||
match curGameExt.getState (← getEnv), curWorldExt.getState (← getEnv), curLevelExt.getState (← getEnv) with
|
-- previously, we also had `curGameExt.getState (← getEnv), ` in here, which got removed
|
||||||
| _, some _, some _ => return Layer.Level
|
-- when we made the `Game` command optional
|
||||||
| _, some _, none => return Layer.World
|
match curWorldExt.getState (← getEnv), curLevelExt.getState (← getEnv) with
|
||||||
| _, none, none => return Layer.Game
|
| some _, some _ => return Layer.Level
|
||||||
| _, _, _ => throwError "Invalid Layer"
|
| some _, none => return Layer.World
|
||||||
|
| none, none => return Layer.Game
|
||||||
|
| _, _ => throwError "Invalid Layer"
|
||||||
|
|
||||||
|
/-- Get the current game, or default if none is specified -/
|
||||||
def getCurGameId [Monad m] : m Name := do
|
def getCurGameId [Monad m] : m Name := do
|
||||||
match curGameExt.getState (← getEnv) with
|
match curGameExt.getState (← getEnv) with
|
||||||
| some game => return game
|
| some game => return game
|
||||||
| none => return defaultGameName
|
| none => return defaultGameName
|
||||||
|
|
||||||
|
/-- Get the current world -/
|
||||||
def getCurWorldId [MonadError m] : m Name := do
|
def getCurWorldId [MonadError m] : m Name := do
|
||||||
match curWorldExt.getState (← getEnv) with
|
match curWorldExt.getState (← getEnv) with
|
||||||
| some world => return world
|
| some world => return world
|
||||||
| none => throwError "Current world not set"
|
| none => throwError "Current world not set"
|
||||||
|
|
||||||
|
/-- Get the current level -/
|
||||||
def getCurLevelIdx [MonadError m] : m Nat := do
|
def getCurLevelIdx [MonadError m] : m Nat := do
|
||||||
match curLevelExt.getState (← getEnv) with
|
match curLevelExt.getState (← getEnv) with
|
||||||
| some level => return level
|
| some level => return level
|
||||||
@@ -160,14 +202,14 @@ structure LevelId where
|
|||||||
deriving Inhabited
|
deriving Inhabited
|
||||||
|
|
||||||
structure InventoryInfo where
|
structure InventoryInfo where
|
||||||
-- new inventory items introduced by this level:
|
/-- new inventory items introduced by this level -/
|
||||||
new : Array Name
|
new : Array Name
|
||||||
-- inventory items exceptionally forbidden in this level:
|
/-- inventory items exceptionally forbidden in this level -/
|
||||||
disabled : Array Name
|
disabled : Array Name
|
||||||
-- only these inventory items are allowed in this level (ignored if empty):
|
/-- only these inventory items are allowed in this level (ignored if empty) -/
|
||||||
only : Array Name
|
only : Array Name
|
||||||
-- inventory items in this level (computed by `MakeGame`):
|
/-- inventory items in this level (computed by `MakeGame`) -/
|
||||||
computed : Array ComputedInventoryItem
|
tiles : Array InventoryTile
|
||||||
deriving ToJson, FromJson, Repr, Inhabited
|
deriving ToJson, FromJson, Repr, Inhabited
|
||||||
|
|
||||||
def getCurLevelId [MonadError m] : m LevelId := do
|
def getCurLevelId [MonadError m] : m LevelId := do
|
||||||
|
|||||||
+20
-25
@@ -40,9 +40,9 @@ Fields:
|
|||||||
structure LevelInfo where
|
structure LevelInfo where
|
||||||
index : Nat
|
index : Nat
|
||||||
title : String
|
title : String
|
||||||
tactics : Array ComputedInventoryItem
|
tactics : Array InventoryTile
|
||||||
lemmas : Array ComputedInventoryItem
|
lemmas : Array InventoryTile
|
||||||
definitions : Array ComputedInventoryItem
|
definitions : Array InventoryTile
|
||||||
introduction : String
|
introduction : String
|
||||||
conclusion : String
|
conclusion : String
|
||||||
descrText : Option String := none
|
descrText : Option String := none
|
||||||
@@ -60,17 +60,11 @@ structure DidOpenLevelParams where
|
|||||||
uri : String
|
uri : String
|
||||||
gameDir : String
|
gameDir : String
|
||||||
levelModule : Name
|
levelModule : Name
|
||||||
tactics : Array ComputedInventoryItem
|
tactics : Array InventoryTile
|
||||||
lemmas : Array ComputedInventoryItem
|
lemmas : Array InventoryTile
|
||||||
definitions : Array ComputedInventoryItem
|
definitions : Array InventoryTile
|
||||||
deriving ToJson, FromJson
|
deriving ToJson, FromJson
|
||||||
|
|
||||||
structure Doc where
|
|
||||||
name: String
|
|
||||||
displayName: String
|
|
||||||
text: String
|
|
||||||
deriving ToJson
|
|
||||||
|
|
||||||
structure LoadDocParams where
|
structure LoadDocParams where
|
||||||
name : Name
|
name : Name
|
||||||
type : InventoryType
|
type : InventoryType
|
||||||
@@ -95,9 +89,9 @@ def handleDidOpenLevel (params : Json) : GameServerM Unit := do
|
|||||||
uri := m.uri
|
uri := m.uri
|
||||||
gameDir := (← get).gameDir
|
gameDir := (← get).gameDir
|
||||||
levelModule := lvl.module
|
levelModule := lvl.module
|
||||||
tactics := lvl.tactics.computed
|
tactics := lvl.tactics.tiles
|
||||||
lemmas := lvl.lemmas.computed
|
lemmas := lvl.lemmas.tiles
|
||||||
definitions := lvl.definitions.computed
|
definitions := lvl.definitions.tiles
|
||||||
: DidOpenLevelParams
|
: DidOpenLevelParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,9 +120,9 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
|
|||||||
let levelInfo : LevelInfo :=
|
let levelInfo : LevelInfo :=
|
||||||
{ index := lvl.index,
|
{ index := lvl.index,
|
||||||
title := lvl.title,
|
title := lvl.title,
|
||||||
tactics := lvl.tactics.computed,
|
tactics := lvl.tactics.tiles,
|
||||||
lemmas := lvl.lemmas.computed,
|
lemmas := lvl.lemmas.tiles,
|
||||||
definitions := lvl.definitions.computed,
|
definitions := lvl.definitions.tiles,
|
||||||
descrText := lvl.descrText,
|
descrText := lvl.descrText,
|
||||||
descrFormat := lvl.descrFormat --toExpr <| format (lvl.goal.raw) --toString <| Syntax.formatStx (lvl.goal.raw) --Syntax.formatStx (lvl.goal.raw) , -- TODO
|
descrFormat := lvl.descrFormat --toExpr <| format (lvl.goal.raw) --toString <| Syntax.formatStx (lvl.goal.raw) --Syntax.formatStx (lvl.goal.raw) , -- TODO
|
||||||
introduction := lvl.introduction
|
introduction := lvl.introduction
|
||||||
@@ -136,7 +130,7 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
|
|||||||
lemmaTab := lvl.lemmaTab
|
lemmaTab := lvl.lemmaTab
|
||||||
statementName := match lvl.statementName with
|
statementName := match lvl.statementName with
|
||||||
| .anonymous => none
|
| .anonymous => none
|
||||||
| name => match (inventoryDocExt.getState env).find?
|
| name => match (inventoryExt.getState env).find?
|
||||||
(fun x => x.name == name && x.type == .Lemma) with
|
(fun x => x.name == name && x.type == .Lemma) with
|
||||||
| some n => n.displayName
|
| some n => n.displayName
|
||||||
| none => name.toString
|
| none => name.toString
|
||||||
@@ -149,14 +143,15 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
|
|||||||
let p ← parseParams LoadDocParams (toJson params)
|
let p ← parseParams LoadDocParams (toJson params)
|
||||||
-- let s ← get
|
-- let s ← get
|
||||||
let c ← read
|
let c ← read
|
||||||
let some doc ← getInventoryDoc? p.name p.type
|
let some doc ← getInventoryItem? p.name p.type
|
||||||
| do
|
| do
|
||||||
c.hOut.writeLspResponseError ⟨id, .invalidParams, s!"Documentation not found: {p.name}", none⟩
|
c.hOut.writeLspResponseError ⟨id, .invalidParams,
|
||||||
|
s!"Documentation not found: {p.name}", none⟩
|
||||||
return true
|
return true
|
||||||
let doc : Doc :=
|
-- TODO: not necessary at all?
|
||||||
{ name := doc.name.toString
|
-- Here we only need to convert the fields that were not `String` in the `InventoryDocEntry`
|
||||||
displayName := doc.displayName
|
-- let doc : InventoryItem := { doc with
|
||||||
text := doc.content }
|
-- name := doc.name.toString }
|
||||||
c.hOut.writeLspResponse ⟨id, ToJson.toJson doc⟩
|
c.hOut.writeLspResponse ⟨id, ToJson.toJson doc⟩
|
||||||
return true
|
return true
|
||||||
| _ => return false
|
| _ => return false
|
||||||
|
|||||||
Reference in New Issue
Block a user