Powered by AppSignal & Oban Pro

IsabelleClientMini

IsabelleClientMini.livemd

IsabelleClientMini

IsabelleClientMini is the low-level client. It exposes the TCP socket, keeps no session state, and leaves async task waiting explicit. Use it when you want to drive the Isabelle server protocol directly.

This notebook starts a local server, connects to it, runs a few commands, starts HOL, checks a small theory, extracts messages, and cleans up.

Setup

When using the published package, replace the dependency with {:isabelle_elixir, "~> 0.2"}.

Mix.install([
  {:isabelle_elixir, path: Path.expand("..", __DIR__)}
])
IsabelleClient.Server.executable()

Start And Connect

Passing port 0 asks Isabelle to choose a free local port.

server_name = "livebook_mini_#{System.unique_integer([:positive])}"
{:ok, [server]} = IsabelleClientMini.new_server(server_name, 0)
{:ok, socket} = IsabelleClientMini.connect(server["password"], server["host"], server["port"])
{:ok, servers} = IsabelleClientMini.list_servers()
Enum.any?(servers, &(&1["name"] == server_name))

Synchronous Commands

{:ok, commands} = IsabelleClientMini.help(socket)

for command <- ~w(cancel echo help purge_theories session_build session_start session_stop shutdown use_theories) do
  true = command in commands
end
payload = %{client: "mini", framed: String.duplicate("x", 120)}
{:ok, %{"client" => "mini", "framed" => framed}} = IsabelleClientMini.echo(socket, payload)
String.length(framed)

command/4 is the escape hatch for server commands without a dedicated helper.

{:ok, %{"raw" => true}} = IsabelleClientMini.command(socket, "echo", raw: true)

Session Tasks

Asynchronous commands first return OK {"task": ...}. Mini returns that task id as an %IsabelleClient.Task{} and leaves await_task/3 explicit.

{:ok, build_task} = IsabelleClientMini.build_session(socket, session: "HOL")
{:ok, build_task} = IsabelleClientMini.await_task(socket, build_task, 120_000)

{build_task.status, build_task.result["ok"], length(build_task.notes)}
{:ok, start_task} = IsabelleClientMini.start_session(socket, session: "HOL")
{:ok, start_task} = IsabelleClientMini.await_task(socket, start_task, 120_000)

session_id = IsabelleClientMini.extract_session(start_task)
{start_task.status, is_binary(session_id)}

Check A Theory

Mini does not remember the active session, so session_id is passed explicitly.

theory_dir =
  Path.join(System.tmp_dir!(), "isabelle_elixir_livebook_mini_#{System.unique_integer([:positive])}")

File.mkdir_p!(theory_dir)

File.write!(Path.join(theory_dir, "MiniExample.thy"), """
theory MiniExample imports Main
begin

lemma "x = x"
  sledgehammer
  by simp

lemma "xs @ [] = xs"
  sledgehammer
  by simp

end
""")
{:ok, use_task} =
  IsabelleClientMini.use_theories(socket,
    session_id: session_id,
    theories: ["MiniExample"],
    master_dir: theory_dir
  )

{:ok, use_task} = IsabelleClientMini.await_task(socket, use_task, 120_000)
true = use_task.result["ok"]

The result helpers live on IsabelleClient and work with Mini task results too.

%{
  messages: IsabelleClient.messages(use_task),
  sledgehammer_lines: IsabelleClient.messages(use_task, line: [5, 9]),
  proof_lines: IsabelleClient.messages(use_task, line: [6, 10]),
  first_proof_at_offset:
    use_task
    |> IsabelleClient.diagnostics(line: 6)
    |> List.first()
    |> get_in(["pos", "offset"])
    |> then(&amp;IsabelleClient.messages(use_task, line: 6, offset: &amp;1))
}
{:ok, purge_result} =
  IsabelleClientMini.purge_theories(socket,
    session_id: session_id,
    theories: ["MiniExample"],
    master_dir: theory_dir
  )

{purge_result["purged"], purge_result["retained"]}

Stop And Shutdown

{:ok, stop_task} = IsabelleClientMini.stop_session(socket, session_id)
{:ok, stop_task} = IsabelleClientMini.await_task(socket, stop_task, 120_000)
stop_task.status
{:ok, nil} = IsabelleClientMini.shutdown_server(socket)
:ok = IsabelleClientMini.close(socket)
IsabelleClientMini.kill_server(server_name)