Packages

package control

This package contains classes for instantiating and controlling an Isabelle process. The main class is Isabelle, see there for documentation. Controlling the process via the classes in this package is quite low-level. See mlvalue.MLValue for a higher level mechanism.

Source
package.scala
Linear Supertypes
Ordering
  1. Alphabetic
  2. By Inheritance
Inherited
  1. control
  2. AnyRef
  3. Any
  1. Hide All
  2. Show All
Visibility
  1. Public
  2. All

Type Members

  1. class DefaultExceptionManager extends ExceptionManager

    Default implementation of ExceptionManager.

    Default implementation of ExceptionManager. Produces messages for all exceptions by pretty printing them in Isabelle with a Pure context. Generated exceptions are always IsabelleMLException instances (no exception specific subclasses).

    See de.unruh.isabelle.pure.Exn.ExceptionManager for an alternative ExceptionManager that supports subclasses.

  2. trait ExceptionManager extends AnyRef

    Handles the conversion of exceptions from the Isabelle process (in ML) to Scala/JVM exceptions.

    Handles the conversion of exceptions from the Isabelle process (in ML) to Scala/JVM exceptions. An ExceptionManager is always associated with a given Isabelle instance. You can set the ExceptionManager using the de.unruh.isabelle.control.Isabelle.SetupGeneral.exceptionManager option. The current instance can be accessed as Isabelle.exceptionManager. Own implementations can start by subclassing DefaultExceptionManager (but do not have to).

  3. class Isabelle extends FutureValue

    A running instance of Isabelle.

    A running instance of Isabelle.

    The Isabelle process is initialized with some ML code that allows this class to remote control Isabelle. In more detail:

    The Isabelle process maintains a map from IDs to values (the "object store"). Those values can be of any type (e.g., integers, terms, functions, etc.). (How this works in a strongly typed language such as ML is described below.) The Isabelle class has functions to operate on the values in the object store (e.g., creating new objects, retrieving the value of an object, performing operations on objects).

    The operations provided by this class are very lowlevel. For more convenient and type-safe operations on values in the object store, see de.unruh.isabelle.mlvalue.MLValue.

    Operations on objects are asynchronous and return futures.

    On the Scala side, the IDs of objects are represented by the class de.unruh.isabelle.control.Isabelle.ID. These IDs ensure garbage collection – if an ID is not referenced any more on the Scala side, it will be removed from the object store in the Isabelle process, too.

    To be able to store objects of different types in the object store, even though ML does not support subtyping, we make use of the fact that all exceptions share the same ML type exn. The object store stores only values of type exn. To store, e.g., integers, we define an exception exception E_Int of int. Then for an integer i, E_Int i is an exception that can be stored in the object store. We can convert the exception back to an integer using the function fn E_Int i => i that uses pattern matching. (This will raise Match if the given exception is does not contain an integer. This way we achieve dynamic typing of our object store.) The following exceptions are predefined in structure Control_Isabelle:

    exception E_Function of exn -> exn
    exception E_Int of int
    exception E_String of string
    exception E_Pair of exn * exn

    (That structure also exports functions store and handleLines which are for internal use only and must not be used in the ML code.)

    Note that some of the exception again refer to the exn type, e.g., E_Pair. Thus, to store a pair of integers, we use the term E_Pair (E_Int 1, E_Int 2).

    New exceptions for storing other types can be defined at runtime using executeMLCode.

  4. case class IsabelleBuildException(message: String, errors: List[String]) extends IsabelleControllerException with Product with Serializable

    Thrown if the build process of Isabelle fails

  5. abstract class IsabelleControllerException extends Exception

    Ancestor of all exceptions specific to Isabelle

  6. case class IsabelleDestroyedException(message: String) extends IsabelleControllerException with Product with Serializable

    Thrown if an operation cannot be executed because Isabelle.destroy has already been invoked.

  7. case class IsabelleJEditException(message: String) extends IsabelleControllerException with Product with Serializable

    Thrown if running Isabelle/jEdit fails

  8. class IsabelleMLException extends IsabelleControllerException

    Thrown in case of an error in the ML process (ML compilation errors, exceptions thrown by ML code)

  9. case class IsabelleMiscException(message: String) extends IsabelleControllerException with Product with Serializable
  10. case class IsabelleProtocolException(message: String) extends IsabelleControllerException with Product with Serializable

    Thrown in case of protocol errors in Isabelle process

  11. trait OperationCollection extends AnyRef

    This is a utility trait for handling a common use case when working with Isabelle.

    This is a utility trait for handling a common use case when working with Isabelle. We illustrate this with an example:

    Say we want to create a library with functions that can operate on floating point numbers on the Isabelle side. (For simplicity, in this example we will provide only on operation: converting strings to reals.) For this, we need to declare an exception type E_Real for storing reals (ML code: exception E_Real of real) and we need to compile and store the code converting strings to reals.

    The very first approach to this problem would be the following:

    object Real {
      private val isabelle : Isabelle = ???
      isabelle.executeMLCodeNow("exception E_Real of real")
      private val fromStringID : Future[Isabelle.ID] = // Converts 'E_String str' into 'E_Real real'
        isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))")
      def fromString(string: String)(implicit ec: ExecutionContext) : Future[Isabelle.ID] = for (
          strId <- isabelle.storeString(string);
          fromStr <- fromStringID;
          real <- isabelle.applyFunction(fromStr, strId))
        yield real
    }

    Here isabelle.executeMLCodeNow sets up the required exception type, and fromStringID contains the (ID of the) compiled ML code for converting strings to ints. And fromString is the user-facing function converting a string to an ID of a real (on the ML side).

    The problem here is that to we need an instance of Isabelle to perform those operations. In the example above we simply wrote val isabelle = ??? because we did not know where to get it from. An obvious solution would be to make Real a class with an isabelle: Isabelle parameter. However, that would make it less convenient to use Real: We need to explicitly create the Real and keep track of it. Especially if the code that is responsible for the Real instance is not the code that creates the Isabelle instance, this might be compilcated.

    A more user-friendly solution is therefore to keep Real as an object and to pass the Isabelle instance as an implicit parameter to Real.fromString. However, this means isabelle is not available outside Real.fromString, so we have to perform the initialization inside fromString:

    object Real {
      def fromString(string: String)(implicit isabelle: Isabelle, ec: ExecutionContext) : Future[Isabelle.ID] = {
        isabelle.executeMLCodeNow("exception E_Real of real")
        val fromStringID : Future[Isabelle.ID] =
          isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))")
        for (
          strId <- isabelle.storeString(string);
          fromStr <- fromStringID;
          real <- isabelle.applyFunction(fromStr, strId))
        yield real
      }
    }

    This has two problems, however: First, the initialization code is executed every time when fromString is executed. Since this involves invoking the ML compiler each time, this should be avoided. (Rule of thumb: ML code should only occur in one-time initializations.) Even worse: by executing the ML code exception E_Real of real, we actually create a different incompatible exception type E_Real each time (and override the name space element E_Real each time). This will lead to failing code (at least if we would extend our example to actually use the created real values). Also, in more complex examples, we might want several functions (not just fromString) to share the same setup. To achieve this, we need global variables to track whether the initialization code has already been executed and to store fromStringID, and – if we don't want our code to fail in the presence of several simultaneous instances of Isabelle – keep track for which instances of Isabelle the initialization has happened already.

    All this is made easy by the OperationCollection trait. The above code can be rewritten as follows:

    object Real extends OperationCollection {
      override protected def newOps(implicit isabelle: Isabelle, ec: ExecutionContext): Ops = new Ops()
      protected class Ops(implicit val isabelle: Isabelle, ec: ExecutionContext) {
        isabelle.executeMLCodeNow("exception E_Real of real")
        val fromStringID : Future[Isabelle.ID] = // Converts 'E_String str' into 'E_Real real'
          isabelle.storeValue("E_Function (fn E_String str => E_Real (Option.valOf (Real.fromString str)))")
      }
      def fromString(string: String)(implicit isabelle: Isabelle, ec: ExecutionContext) : Future[Isabelle.ID] = for (
          strId <- isabelle.storeString(string);
          fromStr <- Ops.fromStringID;
          real <- isabelle.applyFunction(fromStr, strId))
        yield real
    }

    Note that we have defines an inner class Ops that performs the initialization and may depend on the Isabelle instance isabelle. Yet we use it (in fromStr <- Ops.fromStringID) as if it were an object. The trait OperationCollection makes this possible. Under the hood, Ops when used like an object is a function with implicit parameters that creates a new class Ops instance only when needed (i.e., when a previously unknown Isabelle instance is used).

    In general, OperationCollection is used with the following boilerplate:

    object ObjectName extends OperationCollection {
      override protected def newOps(implicit isabelle: Isabelle, ec: ExecutionContext): Ops = new Ops()
      protected class Ops(implicit val isabelle: Isabelle, ec: ExecutionContext) {
        // arbitrary initialization code that is specific to the Isabelle instance `isabelle`
      }
      // code that uses Ops like an object
    }

    Note the following: - Ops must be not be called differently - In protected class Ops, protected can be replaced by something weaker if the operations should be accessible outside the current object (e.g. protected[packagename]) - When Obs is used like an object, implicit of types Isabelle and scala.concurrent.ExecutionContext must be in scope - The function newOps must be defined exactly as specified here

Inherited from AnyRef

Inherited from Any

Ungrouped