Current section
Files
Jump to
Current section
Files
lib/isabelle_client.ex
defmodule IsabelleClient do
@moduledoc """
Stateful, single-process Isabelle client.
This is the ergonomic client for scripts and LiveBooks. It keeps the socket
and current `session_id` in a struct, but it is not meant to be shared by
multiple concurrent processes. Use `IsabelleClientFull` for that.
"""
alias IsabelleClient.Task
defstruct [:socket, :session_id]
@type t :: %__MODULE__{socket: port(), session_id: String.t() | nil}
@doc "Connects to an Isabelle server and returns a stateful client struct."
def connect(password, opts \\ []) do
host = Keyword.get(opts, :host, "127.0.0.1")
port = Keyword.get(opts, :port, 9999)
timeout = Keyword.get(opts, :timeout, 30_000)
with {:ok, socket} <- IsabelleClientMini.connect(password, host, port, timeout) do
{:ok, %__MODULE__{socket: socket}}
end
end
@doc "Closes the client's socket."
def close(%__MODULE__{socket: socket}), do: IsabelleClientMini.close(socket)
@doc "Runs a synchronous Isabelle command."
def command(%__MODULE__{socket: socket}, name, arg \\ nil) do
IsabelleClientMini.command(socket, name, arg)
end
@doc "Round-trips a JSON value through Isabelle's `echo` command."
def echo(client, value), do: command(client, "echo", value)
@doc "Returns the server command names supported by Isabelle."
def help(client), do: command(client, "help")
@doc "Asks the Isabelle server process to shut down."
def shutdown_server(client), do: command(client, "shutdown")
@doc "Builds an Isabelle session image and waits for the task result."
def build_session(%__MODULE__{socket: socket}, args, timeout \\ :infinity) do
with {:ok, task} <- IsabelleClientMini.build_session(socket, args) do
IsabelleClientMini.await_task(socket, task, timeout)
end
end
@doc "Starts an Isabelle session, stores its `session_id`, and returns the updated client."
def start_session(%__MODULE__{socket: socket} = client, args, timeout \\ :infinity) do
with {:ok, task} <- IsabelleClientMini.start_session(socket, args),
{:ok, %Task{result: %{"session_id" => session_id}} = task} <-
IsabelleClientMini.await_task(socket, task, timeout) do
{:ok, %{client | session_id: session_id}, task}
end
end
@doc "Stops the active Isabelle session and clears `session_id`."
def stop_session(%__MODULE__{session_id: nil}), do: {:error, :no_session}
def stop_session(
%__MODULE__{socket: socket, session_id: session_id} = client,
timeout \\ :infinity
) do
with {:ok, task} <- IsabelleClientMini.stop_session(socket, session_id),
result <- IsabelleClientMini.await_task(socket, task, timeout) do
case result do
{:ok, task} -> {:ok, %{client | session_id: nil}, task}
{:error, task} -> {:error, %{client | session_id: nil}, task}
end
end
end
@doc "Checks theories in the active session and waits for the task result."
def use_theories(client, args \\ nil, timeout \\ :infinity)
def use_theories(%__MODULE__{session_id: nil}, _args, _timeout),
do: {:error, :no_session}
def use_theories(
%__MODULE__{socket: socket, session_id: session_id},
args,
timeout
) do
args = Map.put_new(args, "session_id", session_id)
with {:ok, task} <- IsabelleClientMini.use_theories(socket, args) do
IsabelleClientMini.await_task(socket, task, timeout)
end
end
@doc "Purges theories from the active session."
def purge_theories(%__MODULE__{session_id: nil}), do: {:error, :no_session}
def purge_theories(%__MODULE__{session_id: nil}, _args), do: {:error, :no_session}
def purge_theories(%__MODULE__{socket: socket, session_id: session_id}, args) do
args = Map.put_new(args, "session_id", session_id)
IsabelleClientMini.purge_theories(socket, args)
end
end