package dedukti

  1. Overview
  2. Docs

A process module processes entries. It is parameterized by an environment.

How to use processors

As a user of the API of Dedukti, processors are the main entry point. The point of processors is to handle automatically inputs and files for you. The API of Dedukti has already defined 5 processors for you, but you are free to define more. This 5 processors are:

  • TypeChecker : to type check the input
  • SignatureBuilder : to construct a signature without type checking it
  • Printer : to pretty print the input
  • Dependencies : Compute dependencies of the input
  • TopLevel : TypeCheck and prints and standard output

Processors should be used through the methods:

  • handle_input : to process one input
  • handle_files : to process a set of files
  • fold_files : to process a set of files and fold the results

Mose of the time, we want to apply some operations before processing one file and some operations after. To do so, the API declares a type hook that can be given optionaly to the functions mentioned above. In particular, a hook allows to handle errors that may have been raised by the processor. By default, any error raised by the processor stops the execution of the processor and are pretty printed via the module Errors. This applies also for errors generated at parsing/scoping time for the functions which handle files.

We also recall that for handle_input, a processor consumes an input. As such, the input will not be reusable once it has been processed. This is because a Dedukti input is an abstraction for a stream of Entry.entry.

If you want to create your own processor, we invite you to go to Section

of this documentation.

type _ t = ..

Each constructor corresponds to a processor. This type is extensible so that we can use this API with your own processors.

type t +=
  1. | TypeChecker : unit t
    (*

    TypeCheck

    *)
  2. | SignatureBuilder : Kernel.Signature.t t
    (*

    Build a signature without type checking

    *)
  3. | PrettyPrinter : unit t
    (*

    Pretty print

    *)
  4. | Dependencies : Dep.t t
    (*

    Compute dependencies

    *)
  5. | TopLevel : unit t
    (*

    TypeCheck and prints result on standard output

    *)
type processor_error = Env.t * Kernel.Basic.loc * exn

This is the type of errors returned by a processor

type hook = {
  1. before : Env.t -> unit;
    (*

    hook_before is executed by the processor before processing the input

    *)
  2. after : Env.t -> processor_error option -> unit;
    (*

    hook_after is executed by the processor after processing the output

    *)
}

To hook an input before and after it is being processed

module type Interface = sig ... end
include Interface with type 'a t := 'a t
val handle_input : Parsers.Parser.input -> ?hook:hook -> 'a t -> 'a

handle_input input hook processor applies the processor processor on the input. hook.hook_before is executed once before the processor and hook.hook_after is executed once after the processor. By default (without hooks), if an exception exn has been raised while processing the data it is raised at top-level.

val handle_files : string list -> ?hook:hook -> 'a t -> 'a

handle_files files hook processor apply a processor on each file of files. hook is used once by file. The result is the one given once each file has been processed.

val fold_files : string list -> ?hook:hook -> f:('a -> 'b -> 'b) -> default:'b -> 'a t -> 'b

fold_files files fold default processor is similar to handle_files except that the result of a processor is given to the function fold every time a file is processed.

Implement its own processor

The type t given above hides the complexity of a processor. Under the hood, a processor is actually a module of type S (see below). This module declares one type and two function. The type S.t is the type of the result computed by the processor. It is the same as 'a in 'a t, the type declared above. For example, the processor Dependencies as type Deps.t t. It is implemented by a module of type S where S.t = Deps.t.

A processor acts on top-level commands of Dedukti which are represented by the type Parsers.Entry.entry and are paremeterized by an environement Env.t. The module Env is the view of the API of the kernel of Dedukti. The type Env.t are paremeters for the kernel. In particular, it knows where to look for the next entry, how to reduce a term, how to type check a term etc...

Hence, a processor implements a function handle_entry env entry which is called successively on each entry (if no exception are triggered).

Finally, to get the result of a processor, we use the function get_data env.

These are the three things you need to define to declare a new processor. Once your processor has been defined, to be able to use the API above, you need to register you processor. The registration step is actually really easy. To register a processor, you need to do three things. 1. First, you need to extend the type t with a new constructor for your processor 2. Declare a trivial equality function for your new constructor (see below) 3. Call the Register function with your new constructor, your processor (as a module) and your equality function.

If you look at the type of the equality function and the register function it seems ugly. To understand how it works, we will give you an example. Assume your new processor is a module Foo such that Foo.t = foo. Then for the first step you do :

type _ t += Foo : foo t

For the second step you declare the following function

let equal_foo (type a b) : (a t * b t) -> (a t,b t) Registration.equal option = function | Foo, Foo -> Some (Registration.Refl (Foo)) | _ -> None

For the third step you call the registration function this way :

let () = Registration.register_processor Foo qual = equal_foo (module Foo);

And then you are ready to go!

The current version allows you to shadow a previously known Processor, be careful with that.

Finally, in this section we declare an of pure function which allows you to define a processor by calling a function and without using the syntax of modules.

module type S = sig ... end

The actual type of the processor as a module

module Registration : sig ... end
val get_processor : 'a t -> (module S with type t = 'a)

get_processor processor returns the module associated to the processor. Raise Not_registered_processor if the processor has not been registered.

val of_pure : f:('a -> Env.t -> Parsers.Entry.entry -> 'a) -> init:'a -> (module S with type t = 'a)

of_pure ~f ~init returns processor from the fold-like function f. f acc env ent folds entry ent on accumulator acc in environment env.

module T : Interface with type 'a t := (module S with type t = 'a)

Define processors with a custom environement

It may be possible that for the default processors defined in this API, you want to use them with another module Env (for example for debugging or to hook yourself in one of the functions declared by the module). This is possible using the functors below. For your custom processors, this is not needed because you can already do it. The only requirement is to keep the same Env.t type.

module type CustomEnv = module type of Env with type t = Env.t
module MakeTypeChecker (E : CustomEnv) : S with type t = unit
module MakeEntryPrinter (E : CustomEnv) : S with type t = unit
module MakeDependencies (E : CustomEnv) : S with type t = Dep.t