Zulip Chat Archive
Stream: lean4
Topic: read term from file
Ciprian Teodorov (Nov 04 2023 at 09:45):
Hi all,
is there a way to read a term from a file in Lean4?
The simplest example would be the following:
structure TestS := (name : String)
def ss :TestS := ⟨ "toto" ⟩
def main (args: List String) : IO Unit := do
let fileName := args.head!
let s : TestS := readTermFromFileName fileName
IO.println s!"Hello, {s.name}"
Note that the function readTermFromFileName
gets the name at runtime, and not at compile time.
I managed to write a tactic that works with a statically known filename like in the following:
def main (args: List String) : IO Unit := do
let s : TestS := by { readTermFromFileName "test.lean" }
IO.println s!"Hello, {s.name}"
But I have a hard time understanding how to generalize this approach with a statically unknown filename.
Best regards.
--
ciprian
Mario Carneiro (Nov 04 2023 at 10:32):
Mario Carneiro (Nov 04 2023 at 10:33):
what does readTermFromFileName
do?
Mario Carneiro (Nov 04 2023 at 10:34):
What is the storage format in the file you are reading?
Mario Carneiro (Nov 04 2023 at 10:36):
You can parse JSON into an arbitrary data structure fairly easily with deriving FromJson
on TestS
, but if you want to parse lean syntax you will have to load an environment from somewhere to use with the elaborator
Ciprian Teodorov (Nov 04 2023 at 10:43):
Mario Carneiro said:
what does
readTermFromFileName
do?
fileName is a path to a file which contains a lean term as string like ⟨ "toto" ⟩
Ciprian Teodorov (Nov 04 2023 at 11:02):
Mario Carneiro said:
You can parse JSON into an arbitrary data structure fairly easily with
deriving FromJson
onTestS
, but if you want to parse lean syntax you will have to load an environment from somewhere to use with the elaborator
I do not want to pass through JSON.
Yes I guess we need an environment for the elaborator. Here is a more detailed example
import Lean
open Lean
structure TestS :=
(name : String)
def nothing: TestS := ⟨ "nothing" ⟩
def s₁ :TestS := ⟨ "something" ⟩ -- the contents of the file are ` ⟨ "something" ⟩`
def readTermFromFileName(fileName: String) : IO $ TestS := do
let code ← IO.FS.readFile ⟨ fileName ⟩
initSearchPath (← Lean.findSysroot) ["build/lib"]
let env ← importModules #[ ] {}
let estx := Lean.Parser.runParserCategory env `term code
match estx with
| Except.ok stx =>
let s1 : TestS := sorry -- How to get from stx to TestS ?
return s1
| Except.error e => return nothing
def main (args: List String) : IO Unit := do
let fileName := args.head!
let s : TestS ← readTermFromFileName fileName
IO.println s!"Hello, {s.name}"
The question is how to get from the Syntax object returned by the parser to the TestS object.
an even more detailed example, that uses a tactic to read from a statically known filename is at: https://tinyurl.com/yawevj96
Mario Carneiro (Nov 04 2023 at 17:51):
Here's a working example.
Test.lean
:
import Lean
open Lean
structure TestS where
name : String
open Lean Elab Command
def runCommandElabM (env : Environment) (x : CommandElabM α) : IO α := do
let cmdCtx := {
fileName := "<empty>"
fileMap := .ofString ""
tacticCache? := none
}
match (← liftM <| EIO.toIO' <| (x cmdCtx).run { env, maxRecDepth := maxRecDepth.defValue }) with
| .ok (a, _) => return a
| .error e =>
throw <| IO.Error.userError s!"unexpected internal error: {← e.toMessageData.toString}"
unsafe def readTermFromFileName (fileName : String) : IO TestS := do
let code ← IO.FS.readFile ⟨fileName⟩
initSearchPath (← Lean.findSysroot) ["build/lib"]
-- this should be the name of the current module, or the one that contains the `TestS` definition
let env ← importModules #[`Test] {}
runCommandElabM env <| runTermElabM fun _ => do
let .ok stx := Lean.Parser.runParserCategory env `term code | throwError "parse error"
let ty := .const ``TestS []
let e ← Term.elabTerm stx (some ty)
Meta.evalExpr TestS ty e
unsafe def main (args : List String) : IO Unit := do
let fileName := args.head!
let s : TestS ← readTermFromFileName fileName
IO.println s!"Hello, {s.name}"
lakefile.lean
:
import Lake
open Lake DSL
package test
@[default_target]
lean_exe test where
root := `Test
supportInterpreter := true -- this is important
input.txt
:
⟨ "something" ⟩
run as:
$ lake exe test input.txt
info: [0/2] Building Test
info: [1/2] Compiling Test
info: [2/2] Linking test
Hello, something
Ciprian Teodorov (Nov 04 2023 at 19:04):
Nice incantation.
Thanks a lot Mario, I'll play with it tonigh :wink:
Adrien Champion (Dec 12 2023 at 10:13):
This works quite well as long as it runs in the source directory, but how about running it as a standalone somewhere else?
Adrien Champion (Dec 12 2023 at 10:14):
Would you need to have some ~/.config/myProgram
folder where you would put your sources/compilation artifacts (?) so that you have access to it when loading terms?
Mario Carneiro (Dec 12 2023 at 10:18):
you can use lake env
to set up LEAN_PATH
to find the necessary oleans for loading terms at runtime
Mario Carneiro (Dec 12 2023 at 10:19):
As long as you only depend on syntax from lean core you can probably also just depend on the oleans provided by the lean
in the PATH
Adrien Champion (Dec 12 2023 at 10:22):
Here is a plug-and-play repo with Mario's code for current and future readers
Adrien Champion (Dec 12 2023 at 10:24):
Okay so the first time it runs, the program would typically create some ~/.config/myProgram
folder, put olean/sources there. Then just make sure to set its LEAN_PATH
so that term loading can find everything and it should work fine
Adrien Champion (Dec 12 2023 at 10:24):
Thanks!
Last updated: Dec 20 2023 at 11:08 UTC