Powered by AppSignal & Oban Pro

IsabelleClient

livebook_examples/IsabelleClient.livemd

IsabelleClient

IsabelleClient is the ordinary user-facing client. It keeps the socket and the current session_id in a struct, awaits Isabelle tasks for you, and adds helpers for checking text or files.

This notebook shows the ergonomic workflow: start a local HOL session, check theory text and a .thy file, use messages/2 line filters, and clean 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 A Local Session

start/1 starts a resident Isabelle server, connects to it, and starts a session. with_session/2 is the same lifecycle wrapped around a function.

{:ok, client, start_task} =
  IsabelleClient.start(
    session: "HOL",
    server_name: "livebook_client_#{System.unique_integer([:positive])}",
    timeout: 120_000
  )

{is_binary(client.session_id), start_task.status}
{:ok, "stateful"} = IsabelleClient.echo(client, "stateful")

Check Text

When the text is not a complete theory, check_text/5 wraps it as a theory importing Main.

text_body = """
lemma "x = x"
  sledgehammer
  by simp

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

{:ok, text_task} =
  IsabelleClient.check_text(client, "LiveTextExample", text_body, [], 120_000)

true = text_task.result["ok"]

Diagnostic maps include source positions. messages/2, diagnostics/2, errors/2, and warnings/2 accept line: n, line: first..last, line: [n, ...], and offset: n. The offset is matched against pos.offset..pos.end_offset.

proof_offsets =
  for line <- [6, 10],
      diagnostic <- IsabelleClient.diagnostics(text_task, line: line),
      offset = get_in(diagnostic, ["pos", "offset"]),
      is_integer(offset) do
    {line, offset}
  end

%{
  all_messages: IsabelleClient.messages(text_task),
  first_sledgehammer_command: IsabelleClient.messages(text_task, line: 5),
  first_proof_command: IsabelleClient.messages(text_task, line: 6),
  both_sledgehammer_commands: IsabelleClient.messages(text_task, line: [5, 9]),
  raw_diagnostics_on_proofs: IsabelleClient.diagnostics(text_task, line: 6..10),
  proof_offsets: proof_offsets,
  proof_messages_by_line_and_offset:
    for {line, offset} <- proof_offsets do
      {line, offset, IsabelleClient.messages(text_task, line: line, offset: offset)}
    end
}
true =
  text_task
  |> IsabelleClient.messages(line: [5, 9])
  |> Enum.any?(&amp;String.contains?(&amp;1, "Sledgehammer"))

true =
  text_task
  |> IsabelleClient.messages(line: [6, 10])
  |> Enum.any?(&amp;String.contains?(&amp;1, "theorem"))

Check A File

check_file/4 derives master_dir and theory name from the file path.

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

File.mkdir_p!(theory_dir)

theory_path = Path.join(theory_dir, "LiveFileExample.thy")

File.write!(theory_path, """
theory LiveFileExample imports Main
begin

lemma "x = x"
  sledgehammer
  by simp

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

end
""")
{:ok, file_task} = IsabelleClient.check_file(client, theory_path, [], 120_000)

%{
  ok?: file_task.result["ok"],
  sledgehammer_lines: IsabelleClient.messages(file_task, line: [5, 9]),
  proof_lines: IsabelleClient.messages(file_task, line: [6, 10])
}

Scoped Sessions

{:ok, scoped_messages} =
  IsabelleClient.with_session([timeout: 120_000], fn scoped ->
    {:ok, task} =
      IsabelleClient.check_text(scoped, "ScopedExample", "lemma \"x = x\"\n  by simp", [], 120_000)

    {:ok, IsabelleClient.messages(task)}
  end)

scoped_messages

Stop And Shutdown

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