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.
The OPAM description file ensures that the following necessary OCaml dependencies are present when installing via OPAM:
cppo_ocamlbuild packages.ppx_tools and ppx_deriving.diff and filterdiff utilities; diff usually comes as standard, but filterdiff can be found in the patchutils package.rlwrap utility, for running the utils/toplevel.sh script.The Jane Street testbed code requires the following packages:
Building the testbed requires jbuilder (version <= 1.0+beta11). The test scripts assume that there is an OPAM switch named rotor-testbed with these packages installed.
Note that the testbed can only be compiled with compiler versions 4.04.x or 4.05.x.
The testbed tarball is versioned via Git LFS, which you will need to have installed in order to download it from the repository.
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 .A man page for ROTOR explaining its commands and options can be displayed by running:
> rotor --helpTo 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 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.
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.
A docker image of the tool is available on docker hub.
> docker pull reubenrowe/ocaml-rotorTo execute the image, run
> docker run -ti reubenrowe/ocaml-rotorcompiler-libs, rather than a custom packaging of the compiler. Set-up should be more straightforward now, and cross-compiling with different versions of the compiler too.Various bug fixes, including some major functional problems:
use dependencies.The two value renaming refactoring modules (rename_val_impl and rename_val_intf) have now been merged, as further development has caused them to converge to compatible implementations. Thus it makes sense to merge them in order to avoid code duplication.
Development splits onto combine_renames branch
A rich representation of identifiers implemented, allowing the language kind (e.g. value, module, module type, etc.) of each segment of a long identifier to be specified. The module also encapsulates the valid nestings of each kind (e.g. a module can contain a value, but not vice-versa).
Currently only modules, module types, and values are supported.
[@name] and [@build] attributes.module type of construct.Tmty_alias case in module types represents.