Merge branch 'main' of github.com:leanprover-community/lean4game

pull/43/head
Jon Eugster 2 years ago
commit c065dcc5e3

@ -22,9 +22,9 @@ import './level.css'
import { Button } from './Button'
import { ConnectionContext, useLeanClient } from '../connection';
import { useGetGameInfoQuery, useLoadLevelQuery } from '../state/api';
import { codeEdited, selectCode } from '../state/progress';
import { useAppDispatch } from '../hooks';
import { useSelector } from 'react-redux';
import { codeEdited, selectCode, progressSlice } from '../state/progress';
import { useAppDispatch, useAppSelector } from '../hooks';
import { useStore } from 'react-redux';
import { EditorContext, ConfigContext, ProgressContext, VersionContext } from '../../../node_modules/lean4-infoview/src/infoview/contexts';
import { EditorConnection, EditorEvents } from '../../../node_modules/lean4-infoview/src/infoview/editorConnection';
@ -63,7 +63,7 @@ function Level() {
const codeviewRef = useRef<HTMLDivElement>(null)
const introductionPanelRef = useRef<HTMLDivElement>(null)
const initialCode = useSelector(selectCode(worldId, levelId))
const initialCode = useAppSelector(selectCode(worldId, levelId))
const [commandLineMode, setCommandLineMode] = useState(true)
const [commandLineInput, setCommandLineInput] = useState("")
@ -114,6 +114,8 @@ function Level() {
const gameInfo = useGetGameInfoQuery()
useLoadWorldFiles(worldId)
const level = useLoadLevelQuery({world: worldId, level: levelId})
const dispatch = useAppDispatch()
@ -277,8 +279,8 @@ function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCo
let model = monaco.editor.getModel(uri)
if (!model) {
model = monaco.editor.createModel(initialCode, 'lean4', uri)
model.onDidChangeContent(() => onDidChangeContent(model.getValue()))
}
model.onDidChangeContent(() => onDidChangeContent(model.getValue()))
editor.setModel(model)
editor.setPosition(model.getFullModelRange().getEndPosition())
@ -288,9 +290,32 @@ function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCo
const taskGutter = new LeanTaskGutter(infoProvider.client, editor)
const abbrevRewriter = new AbbreviationRewriter(new AbbreviationProvider(), model, editor)
return () => { abbrevRewriter.dispose(); taskGutter.dispose(); model.dispose() }
return () => { abbrevRewriter.dispose(); taskGutter.dispose(); }
}
}, [editor, levelId, connection, leanClientStarted])
return {editor, infoProvider, editorConnection}
}
/** Open all files in this world on the server so that they will load faster when accessed */
function useLoadWorldFiles(worldId) {
const gameInfo = useGetGameInfoQuery()
const store = useStore()
useEffect(() => {
if (gameInfo.data) {
const models = []
for (let levelId = 1; levelId <= gameInfo.data.worldSize[worldId]; levelId++) {
const uri = monaco.Uri.parse(`file:///${worldId}/${levelId}`)
let model = monaco.editor.getModel(uri)
if (model) {
models.push(model)
} else {
const code = selectCode(worldId, levelId)(store.getState())
models.push(monaco.editor.createModel(code, 'lean4', uri))
}
}
return () => { for (let model of models) { model.dispose() } }
}
}, [gameInfo.data, worldId])
}

6
package-lock.json generated

@ -21,7 +21,7 @@
"cytoscape-klay": "^3.1.4",
"debounce": "^1.2.1",
"express": "^4.18.2",
"lean4-infoview": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?master",
"lean4-infoview": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?de0062c",
"lean4web": "github:hhu-adam/lean4web",
"path-browserify": "^1.0.1",
"react": "^18.2.0",
@ -6048,8 +6048,8 @@
"node_modules/lean4-infoview": {
"name": "@leanprover/infoview",
"version": "0.4.2",
"resolved": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?master",
"integrity": "sha512-XthMPhgRGoP/3lClX5LEQNXQYmzEcIQQBuK6AZ4RC5NKcTU7XUpA0qtZTQcWkPeZbOqz02FX5Dg8f4P1CDMuGw==",
"resolved": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?de0062c",
"integrity": "sha512-xrKjl6/+Myn0UeBmEgkba6LStBfjb4iIC7fSJ3tAJxG0CrsKBmBCeU/sfjah3RNw7ieetLeFGd6YvxtoI4nPhQ==",
"license": "Apache-2.0",
"dependencies": {
"@leanprover/infoview-api": "~0.2.1",

@ -17,7 +17,7 @@
"cytoscape-klay": "^3.1.4",
"debounce": "^1.2.1",
"express": "^4.18.2",
"lean4-infoview": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?master",
"lean4-infoview": "https://gitpkg.now.sh/leanprover/vscode-lean4/lean4-infoview?de0062c",
"lean4web": "github:hhu-adam/lean4web",
"path-browserify": "^1.0.1",
"react": "^18.2.0",

@ -1,7 +1,7 @@
import { WebSocketServer } from 'ws';
import express from 'express'
import path from 'path'
import { spawn } from 'child_process';
import * as cp from 'child_process';
import * as url from 'url';
import * as rpc from 'vscode-ws-jsonrpc';
import * as jsonrpcserver from 'vscode-ws-jsonrpc/server';
@ -34,7 +34,34 @@ if (isDevelopment) {
cwd = "."
}
/** We keep a queue of started Lean Server processes to be ready when a user arrives */
const queue = []
const queueLength = 5
/** start Lean Server processes to refill the queue */
function fillQueue() {
while (queue.length < queueLength) {
const serverProcess = cp.spawn(cmd, cmdArgs, { cwd })
serverProcess.on('error', error =>
console.error(`Launching Lean Server failed: ${error}`)
);
if (serverProcess.stderr !== null) {
serverProcess.stderr.on('data', data =>
console.error(`Lean Server: ${data}`)
);
}
queue.push(serverProcess)
}
}
fillQueue()
wss.addListener("connection", function(ws) {
const ps = queue.shift() // Pick the first Lean process; it's likely to be ready immediately
fillQueue()
const socket = {
onMessage: (cb) => { ws.on("message", cb) },
onError: (cb) => { ws.on("error", cb) },
@ -44,7 +71,7 @@ wss.addListener("connection", function(ws) {
const reader = new rpc.WebSocketMessageReader(socket);
const writer = new rpc.WebSocketMessageWriter(socket);
const socketConnection = jsonrpcserver.createConnection(reader, writer, () => ws.close())
const serverConnection = jsonrpcserver.createServerProcess('Lean Server', cmd, cmdArgs, { cwd });
const serverConnection = jsonrpcserver.createProcessStreamConnection(ps);
socketConnection.forward(serverConnection, message => {
console.log(`CLIENT: ${JSON.stringify(message)}`)
return message;

@ -79,9 +79,12 @@ elab "Statement" statementName:ident ? descr:str sig:declSig val:declVal : comma
-- | none => `(lemma $(mkIdent "XX") $sig $val) -- TODO: Make it into an `example`
-- | some name => `(lemma $name $sig $val)
let scope ← getScope
elabCommand thmStatement
modifyCurLevel fun level => pure {level with
goal := sig,
scope := scope,
descrText := descr.getString,
descrFormat := match statementName with
| none => "example " ++ (toString <| reprint sig.raw) ++ " := by"
@ -187,17 +190,28 @@ elab "TacticDoc" name:ident content:str : command =>
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
modifyCurLevel fun level => pure {level with newTactics := args.map (·.getId)}
let names := args.map (·.getId)
for name in names do checkTacticDoc name
modifyCurLevel fun level => pure {level with newTactics := names}
/-- Declare tactics that are temporarily disabled in this level -/
elab "DisabledTactics" args:ident* : command => do
modifyCurLevel fun level => pure {level with disabledTactics := args.map (·.getId)}
let names := args.map (·.getId)
for name in names do checkTacticDoc name
modifyCurLevel fun level => pure {level with disabledTactics := names}
/-- Temporarily disable all tactics except the ones declared here -/
elab "OnlyTactics" args:ident* : command => do
modifyCurLevel fun level => pure {level with onlyTactics := args.map (·.getId)}
let names := args.map (·.getId)
for name in names do checkTacticDoc name
modifyCurLevel fun level => pure {level with onlyTactics := names}
/-! ## Lemmas -/

@ -161,9 +161,10 @@ deriving ToJson, FromJson, Repr
def getCurLevelId [MonadError m] : m LevelId := do
return { game := ← getCurGameId, world := ← getCurWorldId, level := ← getCurLevelIdx}
/--
/-- 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.
@ -195,6 +196,7 @@ structure GameLevel where
lemmas: Array Availability := default
hints: Array GoalHintEntry := default
goal : TSyntax `Lean.Parser.Command.declSig := default
scope : Elab.Command.Scope := default
descrText: String := default
descrFormat : String := default
deriving Inhabited, Repr

@ -77,13 +77,24 @@ partial def findForbiddenTactics (inputCtx : Parser.InputContext) (level : GameL
| .atom info val =>
-- ignore syntax elements that do not start with a letter
-- and ignore "with" keyword
if 0 < val.length ∧ val.data[0]!.isAlpha ∧ val != "with" then
if 0 < val.length ∧ val.data[0]!.isAlpha ∧ val != "with" ∧ val != "at" then
if ¬ ((level.tactics.map (·.name.toString))).contains val then
addErrorMessage info s!"You have not unlocked the tactic '{val}' yet!"
else if level.disabledTactics.contains val
(¬ level.onlyTactics.isEmpty ∧ ¬ level.onlyTactics.contains val)then
addErrorMessage info s!"The tactic '{val}' is disabled in this level!"
| .ident info rawVal val preresolved => return ()
| .ident info rawVal val preresolved =>
let ns ←
try resolveGlobalConst (mkIdent val)
catch | _ => pure [] -- catch "unknown constant" error
for n in ns do
let some (.thmInfo ..) := (← getEnv).find? n
| return () -- not a theroem -> ignore
if ¬ ((level.lemmas.map (·.name))).contains n then
addErrorMessage info s!"You have not unlocked the lemma '{n}' yet!"
else if level.disabledLemmas.contains n
(¬ level.onlyLemmas.isEmpty ∧ ¬ level.onlyLemmas.contains n)then
addErrorMessage info s!"The lemma '{n}' is disabled in this level!"
where addErrorMessage (info : SourceInfo) (s : MessageData) :=
modify fun st => { st with
messages := st.messages.add {
@ -139,10 +150,10 @@ def compileProof (inputCtx : Parser.InputContext) (snap : Snapshot) (hasWidgets
let tacticStx := (#[skip] ++ tacticStx.getArgs ++ #[done]).map (⟨.⟩)
let tacticStx := ← `(Lean.Parser.Tactic.tacticSeq| $[$(tacticStx)]*)
let cmdStx ← `(command|
set_option tactic.hygienic false in
theorem the_theorem $(level.goal) := by {$(⟨tacticStx⟩)} )
Elab.Command.elabCommandTopLevel cmdStx)
let cmdStx ← `(command| theorem the_theorem $(level.goal) := by {$(⟨tacticStx⟩)} )
Elab.Command.withScope (fun _ => level.scope) do -- use open namespaces and options as in the file
Elab.Command.elabCommandTopLevel cmdStx)
cmdCtx cmdStateRef
let postNew := (← tacticCacheNew.get).post
snap.tacticCache.modify fun _ => { pre := postNew, post := {} }

@ -102,6 +102,7 @@ def initAndRunWatchdog (args : List String) (i o e : FS.Stream) : IO Unit := do
let i ← maybeTee "wdIn.txt" false i
let o ← maybeTee "wdOut.txt" true o
let e ← maybeTee "wdErr.txt" true e
let state := {env := ← createEnv, game := `TestGame}
let initRequest ← i.readLspRequestAs "initialize" InitializeParams
o.writeLspResponse {
id := initRequest.id
@ -114,7 +115,6 @@ def initAndRunWatchdog (args : List String) (i o e : FS.Stream) : IO Unit := do
: InitializeResult
}
}
let state := {env := ← createEnv, game := `TestGame}
let context : ServerContext := {
hIn := i
hOut := o

@ -54,7 +54,7 @@ HiddenHint (a : ) (b : ) (c : ) (d : ) (h₁ : c = d) (h₂ : a = b)
"Auch eine Möglichkeit.. Kannst du das Goal so umschreiben, dass es mit einer Annahme übereinstimmt?"
Hint (a : ) (b : ) (h : b = a) : a = b =>
"Naja auch Umwege führen ans Ziel... Wenn du das Goal zu `a = A` umschreibst, kann man es mit
"Naja auch Umwege führen ans Ziel... Wenn du das Goal zu `a = a` umschreibst, kann man es mit
`rfl` beweisen (rsp. das passiert automatisch.)"
Hint (a : ) (b : ) (c : ) (d : ) (h₁ : c = d) (h₂ : d = b) (h₃ : d = a) : b = c =>

Loading…
Cancel
Save