ROTOR: A Reliable OCaml Tool for OCaml Refactoring - Trustworthy Refactoring for OCaml

ROTOR is a tool for refactoring OCaml code, written in OCaml.

The eventual aim is for ROTOR to be able to not only perform automatic refactoring, but also integrate formal verification that the refactorings are correct. It is intended that the CakeML project will be used for this.

More details can be found at the project website.

Dependencies


Installing ROTOR

To install from a local copy of the source via opam, run the following from the root of the directory containing the source code:

> opam pin -k path add rotor .

Running ROTOR

A man page for ROTOR explaining its commands and options can be displayed by running:

> rotor --help

To rename a value in an OCaml program, within the directory containing the modules of the program, invoke the rename command:

> rotor rename <identifier> <new-name>

The syntax for specifying identifiers is described in the subsection below.

ROTOR looks for compiled .cmt and .cmti files corresponding to the modules of a program. These can be produced by passing the -bin-annot flag to the compiler. If the .cmt and .cmti files are located in a different directory to the source files, you can pass these to ROTOR using the -I option:

> rotor rename [-I <dir>]* <identifier> <new-name>

If the program depends on any libraries (in addition to OCaml's standard Pervasives library), then the directories containing the interface files for these libraries must be passed to ROTOR using the -I option.

If .cmt and .cmti files are not present, then ROTOR will attempt to parse and type the source files itself directly.

If the program's source files are distributed across many different directories then you can pass these to ROTOR using the -i option:

> rotor rename -i <src_dir> <identifier> <new-name>

You also specify that an individual file is part of the codebase using the -i option:

> rotor rename -i <src_file> <identifier> <new-name>

If the files in a source directory, or individual source file, should be processed with a sequence of particular PPX preprocessors, this can also be specified as follows:

> rotor rename -i [ppx:<ppx_exe>,]*<src_file_or_dir> <identifier> <new-name>

Additionally, if the OCaml program has been compiled with dune, then you must specify the name of the library that each source file belongs to, as follows:

> rotor rename -i [ppx:<ppx_exe>,]*[lib:<lib_name>]?<src_file_or_dir> <identifier> <new-name>

ROTOR's output can be redirected to a file using the -o option.

> rotor rename -o <file> <identifier> <new-name>

You can display ROTOR's progress as it is computing a refactoring:

> rotor rename --show-progress <identifier> <new-name>

Debugging information can be saved to a file using the --log-file flag:

> rotor rename --log-file <file> <identifier> <new-name>

The module dependencies of a codebase can be output using the mod-deps command:

> rotor mod-deps [-I <dir>]* [-i <item_spec>]*

ROTOR's Identifier Syntax

ROTOR uses an extended syntax for OCaml identifiers. OCaml programs have a hierarchical structure, in which both modules and module types can be nested within one another. OCaml uses 'dot notation' for identifiers, in which the infix operator dot (.) indicates this hierarchical nesting. ROTOR generalises OCaml's identifier notation in two ways. Firstly, instead of treating the dot as an infix operator, it uses it as a prefix operator on names to indicate an element of a particular sort and introduces new prefix operators to express other sorts (e.g. module, module type, value). Secondly, the hierarchical structure is now represented by the sequencing of prefixed names. ROTOR currently uses the operators ., #, %, *, and : to indicate structures, functors, structure types (i.e. signatures), functor types, and values, respectively. ROTOR also uses an indexer element of the form [i], to stand for the ith parameter of a functor or functor type.

Specifically, ROTOR uses the following syntax for identifiers where the nonterminal <name> denotes a standard OCaml (short) identifier, and <number> denotes a positive integer literal.

    <signifier>  ::= '.' | '#' | '%' | '*' | ':'
    <id_link>    ::= <signifier> <name> | '[' <index> ']'
    <identifier> ::= <id_link> | <id_link> <identifier>

So, for example, to specify a function foo nested within a number of (sub)modules, you could use the identifier .A.B.Bar.Baz:foo.

To give a more complex example, .Set%S:add would refer to the add value declaration within the S module type within the Set module.

Similarly, .Set#Make[1]:compare refers to the declaration of the compare value in the first parameter of the Make functor within the Set module.

Note that when specifying the new name in the invocation of ROTOR

> rotor rename <identifier> <new-name>

you should give simply a short identifier (e.g. foo), i.e. you do not need to specify a full path; indeed doing so will cause ROTOR to raise an error.


Test Suite

The test suites can be run by calling make with targets having the prefix tests (e.g. tests.jane-street.all). See the Test Suite README for more details.


Docker Image

A docker image of the tool is available on docker hub.

> docker pull reubenrowe/ocaml-rotor

To execute the image, run

> docker run -ti reubenrowe/ocaml-rotor

Progress Log

23 August 2019

25 May 2019

19 January 2019

20 March 2018

03 March 2018

25 February 2018

23 February 2018

15 January 2018

30 December 2017

06 November 2017

17 September 2017

08 September 2017

07 August 2017

25 June 2017

24 May 2017

19 May 2017

11 May 2017

03 April 2017

30 March 2017

19 Mar 2017

02 Mar 2017

01 Mar 2017

24 Feb 2017

22 Feb 2017

9 Feb 2017

7 Feb 2017


Language featuers which are not yet handled