Introduction
This manual attempts to provide documentation for people wishing to develop HOL4, a process that will likely involve frequent re-compilations or rebuilds of the core sources.
As per the standard installation instructions, once one has an SML installation, there are two stages to the process of building HOL4:
-
The first step is the configuration of the system (see Configuration below). This is achieved with the command
smlsystem < tools/smart-configure.smlwhere
smlsystemis eitherpolyormosml. This command must be issued from the root of the HOL installation, where the pathtools/smart-configure.smlmakes sense. This should complete quickly. -
Second, one needs to build the system (see Build below). This is achieved with the command
bin/buildassuming one is still in the root HOL directory. Note however, that
buildcan be executed from anywhere. In particular, if<holdir>/binis in one’s path, it is reasonable to runbuildfrom any directory.When first executed, the build process will take a while because it proves all of the theorems in the core system, writing theories to disk as it goes. Building should terminate with the message
Hol built successfully.Repeated calls to
buildshould complete quickly: theorem-proving work will not be redone unnecessarily. The building of the theory graph can be slow however, and this does happen with every invocation ofbuildby default. To avoid this, pass--no-mdbookor--no-helpdocstobuild: either flag skips the documentation pipeline (which includes the theory graph).
Configuration
The configuration process is responsible for
- calculating important environmental details, such as the SML implementation, the nature of the operating system, and important paths;
- the creation of core system tools: standard SML tools (
mlyacc,mllex), and HOL-specific tools (most importantly, the quotation filter,Holmakeandbuild).
The first thing that configuration does is to figure out which SML implementation is being run.
This is important because the compilation and creation of executables differs so dramatically in Poly/ML and Moscow\ ML.
Once this determination is made, actual configuration work is done in either tools-poly/configure.sml or tools/configure.sml
(See Sources below for more on how these sources are organised.)
One of the core outputs of configuration is the Systeml structure.
This has a fixed signature, given in tools/Holmake/Systeml.sig, but has a structure that is filled with installation- and ML implementation-specific details.
The generated file is tools/Holmake/Systeml.sml, which is generated from either tools/Holmake/unix-systeml.sml (for Moscow ML) or tools-poly/Holmake/unix-systeml.sml (for Poly/ML).
There is an additional tools/Holmake/winNT-systeml.sml meant for Moscow ML on Windows, but this code hasn’t been tested and is probably bit-rotted.
Building (and Rebuilding) Holmake
Because Holmake is not assumed to exist when Holmake is built, the configuration script is responsible for assembling the constituent sources.
In the case of Moscow\ ML, this means that it makes the successive calls to mosmlc as necessary.
With Poly/ML, the work is orchestrated by tools/Holmake/poly/poly-Holmake.ML.
That file is a sequence of calls to use, followed by a simple definition of a main function.
This can then be compiled with polyc.
Note that we can’t simplify the Moscow\ ML build in a way analogous to what happens with Poly/ML because the complicated sequence of instructions are command-line invocations of mosmlc, rather than calls to use within a Poly/ML program.
We might attempt to create a shell-script containing the calls to mosmlc, but it seems more OS-independent to invoke OS.Process.system from within configure.sml.
Quick rebuilds of Poly/ML Holmake are possible by making the call
polyc -o ../../../bin/Holmake poly-Holmake.ML
in the tools/Holmake/poly directory.
It is necessary to specify the output path (with the -o option) to replace the old Holmake if one is going to test/use the new tool in the existing HOL sources.
Without doing this, the new implementation will see that it is in a HOL source–tree and then switch to call the Holmake in that source-tree’s bin directory.
Build
The standard options to build are described in its help documentation, which is accessible by invoking build --help (or build -h, or build -?, but not build help because this builds the HOL documentation).
The file containing this information about options is located at tools/build/buildhelp.txt.
The most frequently used options to build are those to do with “selftest” level, and the selection of kernel.
Regression Testing
The build program’s --selftest option can be given as is (in which case the selftest level is 1), or followed by a positive number, which gives the selftest level explicitly.
The higher the number, the more regression tests are executed.
Developers are expected to categorise their tests so that those at level 1 will complete quickly, those at level 2 will execute in moderate time, and those at level 3 can take as long as is necessary.1
As of 2022, there are no regression tests that require a level greater than 3, and we will likely keep things this way.
There are two standard ways to the install a test that can be run by build:
-
Create a
selftest.exeexecutable in an existing directory that build works on. TheHolmakefilein this directory will need to specify how to build this executable, and then additionally include anifdef HOLSELFTESTLEVEL endifblock to get the executable to be run. Just checking if the variable is set, will cause execution at all non-zero levels. To fire a test only at particular levels, use the
ifeqand related commands. It may also be a good idea to have this block produce a log-file recording the execution of the selftest; this can be done effectively with theHolmakefunction$(tee ...).There are a number of examples of constructing
selftest.exeexecutables in the sources. See for examplesrc/boss/selftest.smlandsrc/boss/Holmakefile. Though theHolmakefilegives build commands in terms of$(HOLMOSMLC), theselftest.exeexecutable will also be built correctly if running Poly/ML. -
Create a new directory for build to operate on. This directory can be inserted into the early stages of the build sequence, as explained in the documentation at the head of
tools/build/build-sequence. If the testing happens afterbossLiband (in Poly/ML) the creation of the standardhol.stateheap, the directory should be included in theHolmakefileinsrc/parallel_builds/core. The various tests in that file can be used to insert regression test directories into the big parallel build of all the post-bossLibdirectories. Using a test-directory is necessary if the tests need to examine behaviours to do with theory export and loading.
Kernel Selection
There are currently three kernels that can be built to underlie a HOL installation.
The standard kernel uses a de\ Bruijn representation for terms, with bound variables represented as numbers.
Free variables are represented as a pair of name and type.
This kernel also implements explicit substitutions internally, allowing for efficient call-by-value execution with tools such as EVAL.
This kernel is the default choice, and can be explicitly selected by passing the --stdknl option to build.
The experimental kernel uses name-type pairs for all sorts of variables.
This means that the functions mk_abs and dest_abs operate in constant time.
(In the standard kernel, these functions must switch between de\ Bruijn indices and free variables in the body when called, making them run in time linear in the size of the body.)
The experimental kernel can be selected by passing the --expk option to build.
The OpenTheory kernel is based on the experimental kernel, but adds proof-logging to the primitive inference rules so that OpenTheory theory packages can be exported from HOL.
This kernel can be selected by passing the --otknl option to build.
The tracing kernel uses the same proof-logging to the primitive inference rules as OpenTheory, but exports inference rules without additional translation.
This kernel can be selected by passing the --trknl option to build.
Build Sequences
When build runs, it choreographs its calls to Holmake by referring to a specified sequence of directories.
By default this sequence is that specified in the file tools/build/build-sequence, which in turn refers to other files via #include directives.
It is possible to provide a different sequence by using the --seq commandline option to build.
Such sequences can be constructed more easily by referring to sequence fragments in the tools/sequences directory, and including these with #include commands.
The details of the required format for sequence files is spelled out in a comment at the head of the tools/build/build-sequence file.
Past the initial prefix of this process, most directories in the build sequence are actually listed in the Holmakefile in src/parallel_builds/core.
This arrangement allows parallel processing of lots of directories at once.
The sequence file upto-parallel gives the sequence of build targets up this point, so is a reasonable argument to --seq for tests of the core system.
Poly/ML Build Phases
When building under Poly/ML, build proceeds through three phases, distinguished by which Poly/ML heap the per-directory Holmake invocations use.
The transitions are driven by the phase_extras function in tools-poly/build.sml.
-
Initial. No HOL-specific heap exists yet, so
Holmakeis invoked with--poly_not_hol. This phase covers the kernel throughsrc/proofman, the directory in which the bare heap,bin/hol.state0, is built (along with its accompanyingproofManagerLib.uo). -
Bare. Once both
bin/hol.state0andsigobj/proofManagerLib.uoexist, subsequentHolmakeinvocations are passed--holstate <HOLDIR>/bin/hol.state0so that they load against the bare heap. This phase covers everything from aftersrc/proofmanup to and includingsrc/boss, in which the fullbin/hol.stateheap (which embodiesbossLib) is built. -
Full. After
src/boss(signalled by the build reaching thebin/holentry in the sequence),Holmakeis invoked with no--holstateargument and so falls back to the defaultbin/hol.stateheap. This phase covers all post-bossLibdirectories, includingsrc/parallel_builds/and theexamples/tree.
When editing in a directory that the build visits in the Initial or Bare phase (e.g., src/marker, src/q, src/combin, src/simp/src, src/IndDef, src/list/src, …), a plain Holmake invocation in that directory will either fail — when bin/hol.state hasn't been built yet, as on a fresh tree — or, worse, succeed by compiling against bin/hol.state, processing the file under edit in a context that already contains material built on top of it.
To do a local re-compile before re-running the full build, pass --holstate $HOLDIR/bin/hol.state0 to Holmake.
Otherwise, just rerun bin/build; on incremental changes this is fast and rebuilds downstream theories as well.
Rebuilding
It is often possible to repeat build to get the system to rebuild itself in the face of changed source files.
If source files have moved directories, or disappeared entirely, build (more accurately Holmake when build calls it) may get confused by stale dependency information.
In this situation, cleaning everything first with build cleanall may be necessary.
Things in bin
The build process deposits various tools in the bin directory.
Under both Moscow ML and Poly/ML the following are created:
build- The build tool as above
hol- The standard executable, which loads a
bossLibbased logical context. This is designed for use by “every user”. hol --bare- the “bare” mode, which includes
boolLiband the goalstack infrastructure but no theories pastbool. Holmake- The Holmake tool, again designed for every user.
linkToSigobj- When the multi-directory, potentially parallel, build begins, this special-purpose tool is run in every directory after that directory’s build completes. It is responsible for linking to relevant
srcfiles in thesigobjdirectory, allowing HOL users to see/find those files without needing to explicitly mention the originalsrcdirectory in anINCLUDES-directive. mkmunge.exe- This tool creates LaTeX mungers, as described in the DESCRIPTION manual.
unquote- This is the quotation filter embodied as a Unix filter, with a variety of options to specify behaviour.
Note that this is not used by Poly/ML HOL, but can be useful there to see what the filter (as embodied by the
HOLSourcemodule) is doing when it messes with user input.
Under Poly/ML, the following additional files will appear:
hol-
this is the main Poly/ML HOL executable with subcommand-based CLI. It supports the following subcommands:
holorhol repl: Start an interactive REPL (default)hol --bare: Start REPL with minimal heap (hol.state0)hol lsp: Start LSP serverhol buildheap -o <file>: Build a heap from object fileshol run: Run script files for side effects (used by Holmake)hol heapname: Print the heap path (reads HOLHEAP from Holmakefile)
It embodies the quotation handling by implementing a copy of the standard Poly/ML REPL that fiddles with the lexer.
genscriptdep-
Given a filename, this utility executable will generate a list of a script files dependencies.
hol.state-
The Poly/ML heap used by
holby default. This embodiesbossLiband is created insrc/boss. hol.state0-
the Poly/ML heap used by
hol --bare. This is built insrc/proofman.
Sources and Their Organisation
HOL comes with two tools directories, tools and tools-poly, as well as a developers directory.
The tools-poly directory is for sources that are specific to the Poly/ML implementation.
The tools directory is for general sources, and for sources specific to the Moscow\ ML implementation.
Apart from sources for tools that are genuine command-line executables, the tools directory also includes some configuration files and editor “modes”.
Tool Executables
The tools distributed with HOL are described below. Unless otherwise noted, they are built by the configuration process.
build- Described above.
The top-level driver code is in files called
build.smlintools/buildandtools-poly. Shared code is intools/build/buildutils.sml. The executable is inbin/. cmp- A simple-minded tool for comparing two files, returning (via exit code) 0 (success) if the two command-line arguments are byte-for-byte identical.
Useful in regression testing of other tools.
Built on demand via a Holmakefile.
The executable is in
tools/cmp. dat-printer- A utility designed to pretty-print the theorem statements from theory files, using a simple-minded s-expression based format.
The source files are located in
src/portableML/rawtheory/. Holmake- The user-facing tool for building HOL developments.
Use of this tool is described in the Description manual.
The
tools/Holmakedirectory contains almost all of the sources, but the Poly/ML-specific template for theSystemlmodule (on top of which everything else in the system is built) is intools-poly/Holmake. The Poly/ML specific code implementing concurrentHolmakeis intools/Holmake/poly. The executable is inbin/. mllex.exe- The tool from SML/NJ.
The executable is in
tools/mllex. mlyacc.exe- The tool from SML/NJ.
The executable is in
tools/mlyacc/src/. theorytool- A dependency and definition analysis tool.
Running it allows you to dynamically dump
.datkeys, or output a full hierarchical ancestry graph of the theories usingdotformat via the--thygraphoption. The source files are located insrc/portableML/rawtheory/. unquote- The quotation filter that runs over sources before they are seen by SML implementations.
This is used interactively (via a Unix filter that preprocesses all user-input under Moscow ML, or built into the Poly/ML REPL), and non-interactively (by being applied to source files).
The core sources are in
tools/Holmake, but the standalone executable is built intools/quote-filterand it is moved tobin/as part of configuration. h4pedant- Our tool for enforcing code style (as documented below).
The command-line specifies the directories to scan, and options dictate which requirements are enforced/checked for.
The enforcing of style is done by the
Holmakefileinsrc/portableML/testsrc. The executable is intools/h4pedant.
Other Tools Directories
tools/build-logsandtools-poly/build-logs-
As each build proceeds, log files recording execution times per theory are generated and stored in these directories.
tools-poly/poly-
Implementations of
Binarymap,Binaryset,ListsortandHelp(from the Moscow\ ML library) so that these libraries can be used in Poly/ML. These implementations are alluse-d inpoly-init.ML. That file also provides an implementation of a structure calledMosml, which provides a simple way of calling a shell command-line and getting back the string of that command’s output.In
poly-init2.ML, there is a definition ofload, which implements the functionality that automatically loads “object code” and dependencies into running sessions. Thepoly-init2.MLfile alsouse-spoly-init.ML. Calls are made touse "poly-init2.ML"in the construction of the first HOL heap (hol.state0), and in the scripts generated byHolmakerun before that heap is built. These calls ensure thatloadis available to interactive and non-interactive uses thereafter.The module
holpathdbimplements a very simple mapping from “environment variables” to paths. These environment variables are used byloadto let “object files” list dependencies without having to use absolute paths. This file isuse-d in, and so made available by,poly-init.ML. Subsequently, there needs to be a call made to initialise the database with an entry for theHOLDIRkey. This is done inHolmakewithinpoly/BuildCommand.sml, and also withinpoly-init2.ML(for interactive use). User-supplied entries come fromholproject.tomlfiles: when a project file'snamekey is set, the project root is registered under that name duringHolmake's upward walk for project files. An optionalholpathkey overridesnamefor this registration only, leavingnamefree to serve as a human-facing project label distinct from the variable name (e.g.name = "cakeml"together withholpath = "CAKEMLDIR"). Conflicting registrations (same name pointing at different directories) are reported as fatal startup errors. tools/sequences-
Build sequence files. These are “modularised” so that, in principle, custom build sequences can be constructed more easily.
tools/editor-modes-
Implementation of the editor modes.
Coding Standards/Requirements
We are fairly liberal in the style of code we accept, which is almost required given the long history of our sources (see arithmeticScript.sml for lots of old comments).
However, the regression machinery does enforce some coding requirements, and these requirements may tighten over time.
As of March 2025, the requirements are:
-
No use of TABs anywhere.
-
No trailing whitespace.
We encourage developers to keep their lines under 80 columns in width.
Glossary of Common Abbreviations in the Source Code
These appear with either capitalisation. There is a slight tendency to having all upper-case SML identifiers refer to theorems, or functions that return theorems.
abs- Abstraction.
ant- Antecedent of an implication.
asl,asm- Assumptions of a theorem or goal. The
aslname is particularly commonly used to name a goal’s assumptions (used, e.g., when writing tactics). conseq- Consequent of an implication.
_conv- Optional suffix to indicate a conversion. Useful to distinguish between a conversion, tactic and derived inference rule that perform the same basic function.
dest_- “destroy”, i.e.: decompose an object into simpler constituents.
g- Goal of a tactic.
gen_- As a prefix of functions indicates that this function is the more general version of the function without the prefix.
ho- “higher-order”, as opposed to first-order. Typically in the context of term matching.
l-
- A list.
-
- Suffix for a variant of a function whose difference is that it operates on a list instead of a single element. E.g: the tactical
THENLcompared toTHEN.
- Suffix for a variant of a function whose difference is that it operates on a list instead of a single element. E.g: the tactical
lhs- Left-hand side of an equation.
mk_- “make”, i.e.: create an object from simpler constituents.
prim_- “primitive”. Optional prefix for the name of internal functions that contain most of the implementation. The function without the prefix is a thin wrapper that implements the public interface.
q_or justq- Optional prefix in the name of a tactical to indicates that it takes a term quotation which is parsed in the context of the goal. Example:
qabbrev_tac. rand- Operand of a combination.
rator- Operator of a combination.
rhs- Right-hand side of an equation.
_rule- Optional suffix to indicate a derived inference rule. Often used for a variant of a conversion that applies the conversion to the conclusion of a theorem. See
CONV_RULE. t- A term.
_tac- Optional suffix for tactics or tacticals.
th- A theorem.
_then- Optional suffix for theorem-tacticals. See the section on tactics in the HOL description manual.
thm- A theorem.
x_-
- Optional prefix to indicate a variant that takes a term or quotation. E.g.: The tactical
X_GEN_TACcompared toGEN_TAC.
- Optional prefix to indicate a variant that takes a term or quotation. E.g.: The tactical
-
- In a tactical that takes a theorem-tactic and applies it to assumptions, indicates a variant that removes the assumption which was acted on.
-
As of 2022, our automatic testing infrastructure runs one selftest at level 3 each day, and one at level 2, with the latter testing the experimental kernel. Yes, this means that things only in level 3 are not getting tested for the experimental kernel. ↩