Writing your own OCaml toplevel directives
Category: OCaml
The OCaml directives that we all know and love (#use
, #load
and so on) are literally not a part of the language.
They never even get to the OCaml parser and are processed by the toplevel binary (bin/ocaml) itself. This is why trying to use them in source code that
is to be compiled with ocamlc or ocamlopt will cause compilation errors.
Somewhat confusingly, they do work if you execute source files with ocaml non-interactively, but that's just because it's equivalent to starting ocaml as a REPL and pasting the code into it.
Anyway, the most interesting thing about the directives is that they are not hardcoded and it's possible to make your own.
In fact, some of the most widely used directives such as #require
are provided by separate modules, which is why #require
doesn't work until you
load the findlib module.
There is one secret you need to know though: the libraries that provide that functions are not in the default search path
and you need to add the path explicitly with #directory "+compiler-libs";;
.
The directives are simply functions that are stored in a hash table called Toploop.directive_table
along with their help data.
You can add them directly with Hashtbl.add Toploop.directive_table
but this is deprecated and you should better use the
Toploop.add_directive
function.
That function and the types it uses are defined as follows:
type directive_fun = | Directive_none of (unit -> unit) | Directive_string of (string -> unit) | Directive_int of (int -> unit) | Directive_ident of (Longident.t -> unit) | Directive_bool of (bool -> unit) type directive_info = { section: string; doc: string; } val add_directive : string -> directive_fun -> directive_info -> unit
The first argument is the directive name. This is enough knowledge to create some useless directives. Here's a snippet you can paste into the REPL and play with:
(* Important! *) #directory "+compiler-libs";; let () = Toploop.add_directive "hello" (Toploop.Directive_none (fun () -> print_endline "Hello world")) Toploop.{section="Goofing around"; doc="Prints \"hello world\""} ;; let () = Toploop.add_directive "greet" (Toploop.Directive_string (fun s -> Printf.printf "Hello %s\n" s)) Toploop.{section="Goofing around"; doc="Prints a greeting"} ;;
Now you can try:
#hello;; #greet "jrandomhacker";; #help;; (* You'll see your section and commands there *)