|
|
import Lean.Server.Watchdog
|
|
|
import GameServer.EnvExtensions
|
|
|
import GameServer.Game
|
|
|
|
|
|
namespace WasmServer.Watchdog
|
|
|
open Lean
|
|
|
open Server
|
|
|
open Watchdog
|
|
|
open IO
|
|
|
open Lsp
|
|
|
open JsonRpc
|
|
|
open System.Uri
|
|
|
|
|
|
structure WasmServerState :=
|
|
|
initParams? : Option InitializeParams
|
|
|
gameServerState : GameServerState
|
|
|
|
|
|
@[export game_make_state]
|
|
|
unsafe def makeState : IO WasmServerState := do
|
|
|
let e ← IO.getStderr
|
|
|
try
|
|
|
Lean.enableInitializersExecution
|
|
|
searchPathRef.set ["/lib", "/gamelib"]
|
|
|
let env ← importModules #[
|
|
|
{ module := `GameServer : Import }
|
|
|
] {} 0
|
|
|
let state : GameServerState := {
|
|
|
env,
|
|
|
game := `TestGame,
|
|
|
gameDir := "test",
|
|
|
inventory := #[]
|
|
|
difficulty := 0
|
|
|
}
|
|
|
return ⟨none, state⟩
|
|
|
catch err =>
|
|
|
e.putStrLn s!"Import error: {err}"
|
|
|
throw err
|
|
|
|
|
|
def readMessage (s : String) : IO JsonRpc.Message := do
|
|
|
let j ← ofExcept (Json.parse s)
|
|
|
let m ← match fromJson? j with
|
|
|
| Except.ok (m : JsonRpc.Message) => pure m
|
|
|
| Except.error inner => throw $ userError s!"JSON '{j.compress}' did not have the format of a JSON-RPC message.\n{inner}"
|
|
|
return m
|
|
|
|
|
|
def readLspRequestAs (s : String) (expectedMethod : String) (α : Type) [FromJson α] : IO (Request α) := do
|
|
|
let m ← readMessage s
|
|
|
match m with
|
|
|
| Message.request id method params? =>
|
|
|
if method = expectedMethod then
|
|
|
let j := toJson params?
|
|
|
match fromJson? j with
|
|
|
| Except.ok v => pure $ JsonRpc.Request.mk id expectedMethod (v : α)
|
|
|
| Except.error inner => throw $ userError s!"Unexpected param '{j.compress}' for method '{expectedMethod}'\n{inner}"
|
|
|
else
|
|
|
throw $ userError s!"Expected method '{expectedMethod}', got method '{method}'"
|
|
|
| _ => throw $ userError s!"Expected JSON-RPC request, got: '{(toJson m).compress}'"
|
|
|
|
|
|
def initializeServer (id : RequestID) : IO Unit := do
|
|
|
let o ← IO.getStdout
|
|
|
o.writeLspResponse {
|
|
|
id := id
|
|
|
result := {
|
|
|
capabilities := mkLeanServerCapabilities
|
|
|
serverInfo? := some {
|
|
|
name := "Lean 4 Game Server"
|
|
|
version? := "0.1.1"
|
|
|
}
|
|
|
: InitializeResult
|
|
|
}
|
|
|
}
|
|
|
return ()
|
|
|
|
|
|
def mkContext (state : WasmServerState) : IO ServerContext := do
|
|
|
let i ← IO.getStdin
|
|
|
let o ← IO.getStdout
|
|
|
let e ← IO.getStderr
|
|
|
let srcSearchPath ← searchPathRef.get
|
|
|
let references ← IO.mkRef (← loadReferences)
|
|
|
let fileWorkersRef ← IO.mkRef (RBMap.empty : FileWorkerMap)
|
|
|
let workerPath := "no-worker-path"
|
|
|
let some initParams := state.initParams?
|
|
|
| throwServerError "no yet initialized"
|
|
|
return {
|
|
|
hIn := i
|
|
|
hOut := o
|
|
|
hLog := e
|
|
|
args := []
|
|
|
fileWorkersRef := fileWorkersRef
|
|
|
initParams
|
|
|
workerPath
|
|
|
srcSearchPath
|
|
|
references
|
|
|
}
|
|
|
|
|
|
def runGameServerM (state : WasmServerState) (x : GameServerM α) : IO (α × WasmServerState) := do
|
|
|
let (res, gameServerState) ← ReaderT.run
|
|
|
(StateT.run x state.gameServerState)
|
|
|
(← mkContext state)
|
|
|
return (res, {state with gameServerState})
|
|
|
|
|
|
def readParams (params? : Option Json.Structured) [FromJson α] : IO α :=
|
|
|
let j := toJson params?
|
|
|
match fromJson? j with
|
|
|
| Except.ok v => pure v
|
|
|
| Except.error inner => throw $ userError s!"Unexpected param '{j.compress}' \n{inner}"
|
|
|
|
|
|
@[export game_send_message]
|
|
|
unsafe def sendMessage (s : String) (state : WasmServerState) : IO WasmServerState := do
|
|
|
let e ← IO.getStderr
|
|
|
try
|
|
|
let m ← readMessage s
|
|
|
match m with
|
|
|
| Message.request id "initialize" params? =>
|
|
|
let p : InitializeParams ← readParams params?
|
|
|
initializeServer id
|
|
|
let p := {p with rootUri? := some (toString state.gameServerState.game)}
|
|
|
return {state with initParams? := some p}
|
|
|
| Message.notification "textDocument/didOpen" params? =>
|
|
|
let some initParams := state.initParams?
|
|
|
| throwServerError "no yet initialized"
|
|
|
let p : LeanDidOpenTextDocumentParams ← readParams params?
|
|
|
let (_, state) ← runGameServerM state do
|
|
|
let some lvl ← GameServer.getLevelByFileName? initParams
|
|
|
((System.Uri.fileUriToPath? p.textDocument.uri).getD p.textDocument.uri |>.toString)
|
|
|
| throwServerError s!"Level not found: {p.textDocument.uri} | {initParams.rootUri?}"
|
|
|
e.putStrLn s!"{lvl.module}"
|
|
|
return state
|
|
|
| _ =>
|
|
|
let (isGameEv, state) ← runGameServerM state (Game.handleServerEvent (.clientMsg m))
|
|
|
if isGameEv then
|
|
|
return state
|
|
|
else
|
|
|
match m with
|
|
|
| _ =>
|
|
|
e.putStrLn s!"Expected JSON-RPC request, got: '{(toJson m).compress}'"
|
|
|
return state
|
|
|
catch err =>
|
|
|
e.putStrLn s!"Server error: {err}"
|
|
|
return state
|