Zulip Chat Archive
Stream: lean4
Topic: Trouble parsing Lean code from string
Tomas Skrivan (Oct 23 2025 at 12:31):
How do I correctly parse and elaborate Lean code from String?
The parsing of def run (x y z : Int) : List Int := [x,y,z] fails with:
<code>:1:38: expected ']'
It looks like I'm not correctly initializing some state as the syntax for List literals does not work. Parsing def run (x y z : Int) : List Int := List.cons x (List.cons y (List.cons z List.nil)) succeeds.
So the question is, how do I properly run CommandElabM from IO?
mwe
import Lean
open Lean Meta Elab Term Command
def main : IO Unit := do
let code := "def run (x y z : Int) : List Int := [x, y, z]"
initSearchPath (← findSysroot)
let env ← Lean.importModules #[{ module := `Lean }] {}
let ctx : Command.Context := {
fileName := "",
fileMap := .ofString code,
snap? := none,
cancelTk? := none
}
let stateRef : IO.Ref Command.State ← IO.mkRef (mkState env)
let runCommandElabM := fun {α} [Inhabited α] (x : CommandElabM α) => do
match ← (x ctx stateRef).toIO' with
| .ok _ =>
return ()
| .error e =>
IO.println "elaboration failed"
IO.println (← e.toMessageData.toString)
return ()
runCommandElabM do
match Parser.runParserCategory env `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
return 1
| .ok stx =>
Command.elabCommand stx
let info ← getConstInfo `run
liftTermElabM <|
show MetaM _ from do
IO.println s!"run : {← ppExpr info.type}"
pure ()
return 0
#eval main
Tomas Skrivan (Oct 23 2025 at 12:32):
Also how do I parse and elaborate a whole file? with imports and multiple commands?
Kenny Lau (Oct 23 2025 at 12:32):
why are you in the IO monad?
Tomas Skrivan (Oct 23 2025 at 12:33):
yes I'm in IO
Kenny Lau (Oct 23 2025 at 12:33):
i said why
Kenny Lau (Oct 23 2025 at 12:34):
import Lean
open Lean Meta Elab Term Command
def test : CommandElabM Nat := do
let code := "def run (x y z : Int) : List Int := [x, y, z]"
match Parser.runParserCategory (← getEnv) `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
return 1
| .ok stx =>
Command.elabCommand stx
let info ← getConstInfo `run
liftTermElabM <|
show MetaM _ from do
IO.println s!"run : {← ppExpr info.type}"
pure ()
return 0
#eval test
Kenny Lau (Oct 23 2025 at 12:34):
i just changed your monad to CommandElabM and it works fine
Tomas Skrivan (Oct 23 2025 at 12:34):
Ohh sorry ... I want to compile small snippets of Lean code to another language. So I want to create an executable compiler from the command line.
Tomas Skrivan (Oct 23 2025 at 12:37):
Kenny Lau said:
i just changed your monad to CommandElabM and it works fine
I really need to run in IO as I want to create an executable.
Kenny Lau (Oct 23 2025 at 12:40):
@Tomas Skrivan
import Lean
open Lean Meta Elab Term Command
def test : CommandElabM Nat := do
let code := "def run (x y z : Int) : List Int := [x, y, z]"
match Parser.runParserCategory (← getEnv) `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
return 1
| .ok stx =>
Command.elabCommand stx
let info ← getConstInfo `run
liftTermElabM <|
show MetaM _ from do
IO.println s!"run : {← ppExpr info.type}"
pure ()
return 0
def main : IO Unit := do
let env ← Lean.importModules #[{ module := `Lean }] {}
let (result, _) ← Lean.Core.CoreM.toIO (liftCommandElabM test)
{ fileName := "<input>"
fileMap := default }
{ env := env }
IO.println result
#eval test
Kenny Lau (Oct 23 2025 at 12:40):
have you seen the monad map? there's no need to reinvent the wheel
Tomas Skrivan (Oct 23 2025 at 12:44):
It still does not work, you have to change #eval main
Kenny Lau (Oct 23 2025 at 12:46):
oops, forgot that
Kenny Lau (Oct 23 2025 at 12:47):
that's interesting that calling test works and calling main fails, lol
Tomas Skrivan (Oct 23 2025 at 12:47):
main is definitely missing initSearchPath (← findSysroot). When you compile it into a binary, without that line you get
uncaught exception: unknown module prefix 'Lean'
No directory 'Lean' or file 'Lean.olean' in the search path entries:
Tomas Skrivan (Oct 23 2025 at 12:49):
#eval can run IO, MetaM, CommandElabM, ... in the current context of the file, that is why #eval test works and #eval main does not. In main you do not have any Lean context and you have to initialize it yourself.
Kenny Lau (Oct 23 2025 at 12:55):
but it understood the rest of the declaration up until the ], or at least that's what the error message says
Tomas Skrivan (Oct 23 2025 at 12:55):
Yeah I really do not understand what is going on. The parser seems to be quite confused.
Kenny Lau (Oct 23 2025 at 12:57):
moreover (after i added Init to the import list) the import lists are actually the same
Kenny Lau (Oct 23 2025 at 12:57):
maybe the parser is running in a separate file that is not the file which imported init and lean?
Kenny Lau (Oct 23 2025 at 13:00):
import Lean
open Lean Meta Elab Term Command
def test : CommandElabM Nat := do
let code := "def run (x y z : Int) : Nat := 3"
let env ← getEnv
-- IO.println s!"{env.allImportedModuleNames }"
match Parser.runParserCategory env `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
return 1
| .ok stx =>
Command.elabCommand stx
let info ← getConstInfo `run
liftTermElabM <|
show MetaM _ from do
IO.println s!"run : {← ppExpr info.type}"
pure ()
return 0
def main : IO Unit := do
let env ← Lean.importModules #[{ module := `Init.Prelude }] {}
let (result, _) ← Lean.Core.CoreM.toIO (liftCommandElabM test)
{ fileName := "<input>"
fileMap := default }
{ env := env }
IO.println result
#eval test
#eval main
Kenny Lau (Oct 23 2025 at 13:00):
yeah i imported Init.Prelude but it doesn't know about OfNat 3 but it knew about Int and Nat
Kenny Lau (Oct 23 2025 at 13:00):
so the parser didn't import prelude at all
Kenny Lau (Oct 23 2025 at 13:00):
wait no it didn't know about Int as well, it assumed Int is a name (and used auto implicit)
Tomas Skrivan (Oct 23 2025 at 13:01):
Ok here is a simplified code, all the monad stuff is red herring.
import Lean
open Lean
def main : IO Unit := do
initSearchPath (← findSysroot)
let env ← Lean.importModules #[{ module := `Init }, { module := `Lean }, { module := `Init.Notation }, { module := `Init.Prelude }] {}
let code := "def run (x y z : Int) : List Int := [x,y,z]"
match Parser.runParserCategory env `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
| .ok stx =>
IO.println "parsed string"
IO.println stx.prettyPrint
Tomas Skrivan (Oct 23 2025 at 13:02):
but it still does not work
Kenny Lau (Oct 23 2025 at 13:03):
import Lean
open Lean Meta Elab Term Command
def test : CommandElabM Nat := do
let code := "def run (x y z : Int) : List Int := [x,y,z]"
let env ← getEnv
match Parser.runParserCategory env `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
return 1
| .ok stx =>
Command.elabCommand stx
let info ← getConstInfo `run
liftTermElabM <|
show MetaM _ from do
IO.println s!"run : {← ppExpr info.type}"
pure ()
return 0
def main : IO Unit := do
let env ← Lean.importModules #[{ module := `Init }] {} (loadExts := true)
let (result, _) ← Lean.Core.CoreM.toIO (liftCommandElabM test)
{ fileName := "<input>"
fileMap := default }
{ env := env }
IO.println result
#eval test
#eval main
Kenny Lau (Oct 23 2025 at 13:03):
@Tomas Skrivan it turns out that when you make the environment you need to set loadExts to true, which is quite stupid
Tomas Skrivan (Oct 23 2025 at 13:05):
Nice find!
Tomas Skrivan (Oct 23 2025 at 13:05):
The docs says that you should run enableInitializersExecution before
Tomas Skrivan (Oct 23 2025 at 13:11):
Ok here is minimal working example:
import Lean
open Lean
unsafe def main : IO Unit := do
initSearchPath (← findSysroot)
enableInitializersExecution
let env ← Lean.importModules #[{ module := `Lean }] {} (loadExts := true)
let code := "def run (x y z : Int) : List Int := [x,y,z]"
match Parser.runParserCategory env `command code "<code>" with
| .error e =>
IO.println "parsing failed"
IO.println e
| .ok stx =>
IO.println "parsed string"
IO.println stx.prettyPrint
To build an executable you also have to set supportInterpreter = true in lakefile
Last updated: Dec 20 2025 at 21:32 UTC