setInventory

nowatchdog
Alexander Bentkamp 3 years ago
parent f871078869
commit 12825f2121

@ -57,6 +57,28 @@ open IO
open Snapshots open Snapshots
open JsonRpc open JsonRpc
-- TODO: check what's necessary and what is constant:
structure GameWorkerState :=
-- uri : String
-- gameDir : String
-- levelModule : Name
-- tactics : Array InventoryTile
-- lemmas : Array InventoryTile
-- definitions : Array InventoryTile
inventory : Array String
/--
Check for tactics/theorems that are not unlocked.
0: no check
1: give warnings
2: give errors
-/
difficulty : Nat
-- /-- The name of the theorem to be proven in this level. -/
-- statementName : Name
deriving ToJson, FromJson
abbrev GameWorkerM := StateT GameWorkerState Server.FileWorker.WorkerM
section Elab section Elab
def addErrorMessage (info : SourceInfo) (inputCtx : Parser.InputContext) (s : MessageData) : def addErrorMessage (info : SourceInfo) (inputCtx : Parser.InputContext) (s : MessageData) :
@ -70,75 +92,75 @@ def addErrorMessage (info : SourceInfo) (inputCtx : Parser.InputContext) (s : Me
} }
} }
-- -- TODO: use HashSet for allowed tactics? -- TODO: use HashSet for allowed tactics?
-- /-- Find all tactics in syntax object that are forbidden according to a /-- Find all tactics in syntax object that are forbidden according to a
-- set `allowed` of allowed tactics. -/ set `allowed` of allowed tactics. -/
-- partial def findForbiddenTactics (inputCtx : Parser.InputContext) partial def findForbiddenTactics (inputCtx : Parser.InputContext)
-- (levelParams : Game.DidOpenLevelParams) (stx : Syntax) : (gameWorkerState : GameWorkerState) (level : GameLevel) (stx : Syntax) :
-- Elab.Command.CommandElabM Unit := do Elab.Command.CommandElabM Unit := do
-- match stx with match stx with
-- | .missing => return () | .missing => return ()
-- | .node _info _kind args => | .node _info _kind args =>
-- for arg in args do for arg in args do
-- findForbiddenTactics inputCtx levelParams arg findForbiddenTactics inputCtx gameWorkerState level arg
-- | .atom info val => | .atom info val =>
-- -- ignore syntax elements that do not start with a letter -- ignore syntax elements that do not start with a letter
-- -- and ignore "with" keyword -- and ignore "with" keyword
-- let allowed := ["with", "fun", "at", "only", "by", "to"] let allowed := ["with", "fun", "at", "only", "by", "to"]
-- if 0 < val.length ∧ val.data[0]!.isAlpha ∧ not (allowed.contains val) then if 0 < val.length ∧ val.data[0]!.isAlpha ∧ not (allowed.contains val) then
-- let val := val.dropRightWhile (fun c => c == '!' || c == '?') -- treat `simp?` and `simp!` like `simp` let val := val.dropRightWhile (fun c => c == '!' || c == '?') -- treat `simp?` and `simp!` like `simp`
-- match levelParams.tactics.find? (·.name.toString == val) with match level.tactics.tiles.find? (·.name.toString == val) with
-- | none => | none =>
-- -- Note: This case means that the tactic will never be introduced in the game. -- Note: This case means that the tactic will never be introduced in the game.
-- match levelParams.inventory.find? (· == val) with match gameWorkerState.inventory.find? (· == val) with
-- | none => | none =>
-- addWarningMessage info s!"You have not unlocked the tactic '{val}' yet!" addWarningMessage info s!"You have not unlocked the tactic '{val}' yet!"
-- | some _ => pure () -- tactic is in the inventory, allow it. | some _ => pure () -- tactic is in the inventory, allow it.
-- | some tac => | some tac =>
-- if tac.locked then if tac.locked then
-- match levelParams.inventory.find? (· == val) with match gameWorkerState.inventory.find? (· == val) with
-- | none => | none =>
-- addWarningMessage info s!"You have not unlocked the tactic '{val}' yet!" addWarningMessage info s!"You have not unlocked the tactic '{val}' yet!"
-- | some _ => pure () -- tactic is in the inventory, allow it. | some _ => pure () -- tactic is in the inventory, allow it.
-- else if tac.disabled then else if tac.disabled then
-- addWarningMessage info s!"The tactic '{val}' is disabled in this level!" addWarningMessage info s!"The tactic '{val}' is disabled in this level!"
-- | .ident info _rawVal val _preresolved => | .ident info _rawVal val _preresolved =>
-- let ns ← let ns ←
-- try resolveGlobalConst (mkIdent val) try resolveGlobalConst (mkIdent val)
-- catch | _ => pure [] -- catch "unknown constant" error catch | _ => pure [] -- catch "unknown constant" error
-- for n in ns do for n in ns do
-- let some (.thmInfo ..) := (← getEnv).find? n let some (.thmInfo ..) := (← getEnv).find? n
-- | return () -- not a theorem -> ignore | return () -- not a theorem -> ignore
-- -- Forbid the theorem we are proving currently -- Forbid the theorem we are proving currently
-- if n = levelParams.statementName then if n = level.statementName then
-- addErrorMessage info inputCtx s!"Structural recursion: you can't use '{n}' to proof itself!" addErrorMessage info inputCtx s!"Structural recursion: you can't use '{n}' to proof itself!"
-- let lemmasAndDefs := levelParams.lemmas ++ levelParams.definitions let lemmasAndDefs := level.lemmas.tiles ++ level.definitions.tiles
-- match lemmasAndDefs.find? (fun l => l.name == n) with match lemmasAndDefs.find? (fun l => l.name == n) with
-- | none => addWarningMessage info s!"You have not unlocked the lemma/definition '{n}' yet!" | none => addWarningMessage info s!"You have not unlocked the lemma/definition '{n}' yet!"
-- | some lem => | some lem =>
-- if lem.locked then if lem.locked then
-- addWarningMessage info s!"You have not unlocked the lemma/definition '{n}' yet!" addWarningMessage info s!"You have not unlocked the lemma/definition '{n}' yet!"
-- else if lem.disabled then else if lem.disabled then
-- addWarningMessage info s!"The lemma/definition '{n}' is disabled in this level!" addWarningMessage info s!"The lemma/definition '{n}' is disabled in this level!"
-- where addWarningMessage (info : SourceInfo) (s : MessageData) := where addWarningMessage (info : SourceInfo) (s : MessageData) :=
-- let difficulty := levelParams.difficulty let difficulty := gameWorkerState.difficulty
-- if difficulty > 0 then if difficulty > 0 then
-- modify fun st => { st with modify fun st => { st with
-- messages := st.messages.add { messages := st.messages.add {
-- fileName := inputCtx.fileName fileName := inputCtx.fileName
-- severity := if difficulty > 1 then MessageSeverity.error else MessageSeverity.warning severity := if difficulty > 1 then MessageSeverity.error else MessageSeverity.warning
-- pos := inputCtx.fileMap.toPosition (info.getPos?.getD 0) pos := inputCtx.fileMap.toPosition (info.getPos?.getD 0)
-- data := s data := s
-- } }
-- } }
-- else pure () else pure ()
-- -- where addErrorMessage (info : SourceInfo) (s : MessageData) := -- where addErrorMessage (info : SourceInfo) (s : MessageData) :=
-- -- pure () -- pure ()
open Elab Meta Expr in open Elab Meta Expr in
def compileProof (inputCtx : Parser.InputContext) (snap : Snapshot) (hasWidgets : Bool) def compileProof (inputCtx : Parser.InputContext) (snap : Snapshot) (hasWidgets : Bool)
(couldBeEndSnap : Bool) (couldBeEndSnap : Bool) (gameWorkerState : GameWorkerState)
(initParams : Lsp.InitializeParams) : IO Snapshot := do (initParams : Lsp.InitializeParams) : IO Snapshot := do
-- Recognize end snap -- Recognize end snap
if inputCtx.input.atEnd snap.mpState.pos ∧ couldBeEndSnap then if inputCtx.input.atEnd snap.mpState.pos ∧ couldBeEndSnap then
@ -192,7 +214,7 @@ def compileProof (inputCtx : Parser.InputContext) (snap : Snapshot) (hasWidgets
parseResultRef.set (tacticStx, cmdParserState) parseResultRef.set (tacticStx, cmdParserState)
-- TODO: Check for forbidden tactics -- TODO: Check for forbidden tactics
-- findForbiddenTactics inputCtx levelParams tacticStx findForbiddenTactics inputCtx gameWorkerState level tacticStx
-- Insert invisible `skip` command to make sure we always display the initial goal -- Insert invisible `skip` command to make sure we always display the initial goal
let skip := Syntax.node (.original default 0 default endOfWhitespace) ``Lean.Parser.Tactic.skip #[] let skip := Syntax.node (.original default 0 default endOfWhitespace) ``Lean.Parser.Tactic.skip #[]
@ -272,7 +294,7 @@ where
/-- Elaborates the next command after `parentSnap` and emits diagnostics into `hOut`. -/ /-- Elaborates the next command after `parentSnap` and emits diagnostics into `hOut`. -/
private def nextSnap (ctx : WorkerContext) (m : DocumentMeta) (cancelTk : CancelToken) private def nextSnap (ctx : WorkerContext) (m : DocumentMeta) (cancelTk : CancelToken)
(initParams : Lsp.InitializeParams) (gameWorkerState : GameWorkerState) (initParams : Lsp.InitializeParams)
: AsyncElabM (Option Snapshot) := do : AsyncElabM (Option Snapshot) := do
cancelTk.check cancelTk.check
let s ← get let s ← get
@ -290,7 +312,7 @@ where
-- we can see the current goal even on an empty document -- we can see the current goal even on an empty document
let couldBeEndSnap := s.snaps.size > 1 let couldBeEndSnap := s.snaps.size > 1
let snap ← compileProof m.mkInputContext lastSnap ctx.clientHasWidgets couldBeEndSnap let snap ← compileProof m.mkInputContext lastSnap ctx.clientHasWidgets couldBeEndSnap
initParams gameWorkerState initParams
set { s with snaps := s.snaps.push snap } set { s with snaps := s.snaps.push snap }
-- TODO(MH): check for interrupt with increased precision -- TODO(MH): check for interrupt with increased precision
cancelTk.check cancelTk.check
@ -312,7 +334,7 @@ where
/-- Elaborates all commands after the last snap (at least the header snap is assumed to exist), emitting the diagnostics into `hOut`. -/ /-- Elaborates all commands after the last snap (at least the header snap is assumed to exist), emitting the diagnostics into `hOut`. -/
def unfoldSnaps (m : DocumentMeta) (snaps : Array Snapshot) (cancelTk : CancelToken) def unfoldSnaps (m : DocumentMeta) (snaps : Array Snapshot) (cancelTk : CancelToken)
(startAfterMs : UInt32) (startAfterMs : UInt32) (gameWorkerState : GameWorkerState)
: ReaderT WorkerContext IO (AsyncList ElabTaskError Snapshot) := do : ReaderT WorkerContext IO (AsyncList ElabTaskError Snapshot) := do
let ctx ← read let ctx ← read
let some headerSnap := snaps[0]? | panic! "empty snapshots" let some headerSnap := snaps[0]? | panic! "empty snapshots"
@ -328,15 +350,10 @@ where
publishIleanInfoUpdate m ctx.hOut snaps publishIleanInfoUpdate m ctx.hOut snaps
return AsyncList.ofList snaps.toList ++ AsyncList.delayed (← EIO.asTask (ε := ElabTaskError) (prio := .dedicated) do return AsyncList.ofList snaps.toList ++ AsyncList.delayed (← EIO.asTask (ε := ElabTaskError) (prio := .dedicated) do
IO.sleep startAfterMs IO.sleep startAfterMs
AsyncList.unfoldAsync (nextSnap ctx m cancelTk ctx.initParams) { snaps }) AsyncList.unfoldAsync (nextSnap ctx m cancelTk gameWorkerState ctx.initParams) { snaps })
end Elab end Elab
-- TODO: remove?
structure GameWorkerState :=
abbrev GameWorkerM := StateT GameWorkerState Server.FileWorker.WorkerM
section Updates section Updates
/-- Given the new document, updates editable doc state. -/ /-- Given the new document, updates editable doc state. -/
@ -383,7 +400,7 @@ section Updates
validSnaps := validSnaps.dropLast validSnaps := validSnaps.dropLast
-- wait for a bit, giving the initial `cancelTk.check` in `nextCmdSnap` time to trigger -- wait for a bit, giving the initial `cancelTk.check` in `nextCmdSnap` time to trigger
-- before kicking off any expensive elaboration (TODO: make expensive elaboration cancelable) -- before kicking off any expensive elaboration (TODO: make expensive elaboration cancelable)
unfoldSnaps newMeta validSnaps.toArray cancelTk ctx unfoldSnaps newMeta validSnaps.toArray cancelTk s ctx
(startAfterMs := ctx.initParams.editDelay.toUInt32) (startAfterMs := ctx.initParams.editDelay.toUInt32)
StateT.lift <| modify fun st => { st with StateT.lift <| modify fun st => { st with
doc := { meta := newMeta, cmdSnaps := AsyncList.delayed newSnaps, cancelTk }} doc := { meta := newMeta, cmdSnaps := AsyncList.delayed newSnaps, cancelTk }}
@ -461,8 +478,7 @@ section Initialization
return (headerSnap, srcSearchPath) return (headerSnap, srcSearchPath)
def initializeWorker (meta : DocumentMeta) (i o e : FS.Stream) (initParams : InitializeParams) (opts : Options) def initializeWorker (meta : DocumentMeta) (i o e : FS.Stream) (initParams : InitializeParams) (opts : Options)
(gameDir : String) : IO (WorkerContext × WorkerState) := do (gameDir : String) (game : Game) (gameWorkerState : GameWorkerState) : IO (WorkerContext × WorkerState) := do
let game ← loadGameData gameDir
-- TODO: We misuse the `rootUri` field to the gameName -- TODO: We misuse the `rootUri` field to the gameName
let rootUri? : Option String := some (toString game.name) let rootUri? : Option String := some (toString game.name)
let initParams := {initParams with rootUri?} let initParams := {initParams with rootUri?}
@ -479,7 +495,7 @@ section Initialization
clientHasWidgets clientHasWidgets
} }
let cmdSnaps ← EIO.mapTask (t := headerTask) (match · with let cmdSnaps ← EIO.mapTask (t := headerTask) (match · with
| Except.ok (s, _) => unfoldSnaps meta #[s] cancelTk ctx (startAfterMs := 0) | Except.ok (s, _) => unfoldSnaps meta #[s] cancelTk gameWorkerState ctx (startAfterMs := 0)
| Except.error e => throw (e : ElabTaskError)) | Except.error e => throw (e : ElabTaskError))
let doc : EditableDocument := { meta, cmdSnaps := AsyncList.delayed cmdSnaps, cancelTk } let doc : EditableDocument := { meta, cmdSnaps := AsyncList.delayed cmdSnaps, cancelTk }
return (ctx, return (ctx,
@ -561,10 +577,9 @@ section MainLoop
| Message.notification "$/game/setInventory" params => | Message.notification "$/game/setInventory" params =>
let p := (← parseParams Game.SetInventoryParams (toJson params)) let p := (← parseParams Game.SetInventoryParams (toJson params))
let s ← get let s ← get
-- TODO set {s with
-- set {s with levelParams := {s.levelParams with inventory := p.inventory,
-- inventory := p.inventory, difficulty := p.difficulty}
-- difficulty := p.difficulty}}
mainLoop mainLoop
| Message.notification method (some params) => | Message.notification method (some params) =>
handleNotification method (toJson params) handleNotification method (toJson params)
@ -600,9 +615,21 @@ def initAndRunWorker (i o e : FS.Stream) (opts : Options) (gameDir : String) : I
let e := e.withPrefix s!"[{param.textDocument.uri}] " let e := e.withPrefix s!"[{param.textDocument.uri}] "
let _ ← IO.setStderr e let _ ← IO.setStderr e
try try
let (ctx, st) ← initializeWorker meta i o e initRequest.param opts gameDir let game ← loadGameData gameDir
let s : GameWorkerState:= {
-- uri := doc.uri
-- gameDir := gameDir
-- levelModule := sorry
-- tactics := sorry
-- lemmas := sorry
-- definitions := sorry
inventory := #[] -- TODO: Ensure that this is set in time!
difficulty := 0 -- TODO: Ensure that this is set in time!
-- statementName := sorry
}
let (ctx, st) ← initializeWorker meta i o e initRequest.param opts gameDir game s
let _ ← StateRefT'.run (s := st) <| ReaderT.run (r := ctx) <| let _ ← StateRefT'.run (s := st) <| ReaderT.run (r := ctx) <|
StateT.run (s := {}) <| (mainLoop) StateT.run (s := s) <| (mainLoop)
return (0 : UInt32) return (0 : UInt32)
catch e => catch e =>
IO.eprintln e IO.eprintln e

@ -25,60 +25,11 @@ open Lsp
open JsonRpc open JsonRpc
open IO open IO
structure DidOpenLevelParams where
uri : String
gameDir : String
levelModule : Name
tactics : Array InventoryTile
lemmas : Array InventoryTile
definitions : Array InventoryTile
inventory : Array String
/--
Check for tactics/theorems that are not unlocked.
0: no check
1: give warnings
2: give errors
-/
difficulty : Nat
/-- The name of the theorem to be proven in this level. -/
statementName : Name
deriving ToJson, FromJson
structure SetInventoryParams where structure SetInventoryParams where
inventory : Array String inventory : Array String
difficulty : Nat difficulty : Nat
deriving ToJson, FromJson deriving ToJson, FromJson
def handleDidOpenLevel (params : Json) : GameServerM Unit := do
let p ← parseParams _ params
let m := p.textDocument
-- Execute the regular handling of the `didOpen` event
handleDidOpen p
let fw ← findFileWorker! m.uri
-- let s ← get
let c ← read
let some lvl ← GameServer.getLevelByFileName? c.initParams ((System.Uri.fileUriToPath? m.uri).getD m.uri |>.toString)
| do
c.hLog.putStr s!"Level not found: {m.uri} {c.initParams.rootUri?}"
c.hLog.flush
-- Send an extra notification to the file worker to inform it about the level data
let s ← get
fw.stdin.writeLspNotification {
method := "$/game/didOpenLevel"
param := {
uri := m.uri
gameDir := s.gameDir
levelModule := lvl.module
tactics := lvl.tactics.tiles
lemmas := lvl.lemmas.tiles
definitions := lvl.definitions.tiles
inventory := s.inventory
difficulty := s.difficulty
statementName := lvl.statementName
: DidOpenLevelParams
}
}
partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
match ev with match ev with
| ServerEvent.clientMsg msg => | ServerEvent.clientMsg msg =>

@ -39,10 +39,6 @@ open System.Uri
| Message.responseError _ _ e .. => | Message.responseError _ _ e .. =>
throwServerError s!"Unhandled response error: {e}" throwServerError s!"Unhandled response error: {e}"
| Message.notification method (some params) => | Message.notification method (some params) =>
if method == "textDocument/didOpen" then
-- for lean4game, we need to pass in extra information when a level is opened:
Game.handleDidOpenLevel (← parseParams _ (toJson params))
else
handleNotification method (toJson params) handleNotification method (toJson params)
mainLoop (←runClientTask) mainLoop (←runClientTask)
| _ => throwServerError "Got invalid JSON-RPC message" | _ => throwServerError "Got invalid JSON-RPC message"

Loading…
Cancel
Save