Conditional Compilation
This only works with the old OCaml syntax.
Sometimes you want to write code that works with different versions of compilers and libraries.
People used to use preprocessors like C preprocessor for the C family languages. The OCaml community uses several preprocessors: cppo, ocp-pp, camlp4 IFDEF macros, optcomp and ppx optcomp.
Instead of a preprocessor, ReScript adds language-level static if
compilation. It's less powerful than other preprocessors since it only supports static if
(no #define
, #undefine
, #include
), but there are several advantages.
It’s tiny (only ~500 lines) and highly efficient. Everything can be done in a single pass. It's easy to rebuild the pre-processor into a standalone file, with no dependencies on compiler libs, to back-port it to old OCaml compilers.
It’s purely functional and type-safe, and cooperates with editor tooling like Merlin.
Usage
lwt_unix.mli
MLtype open_flag =
Unix.open_flag =
| O_RDONLY
| O_WRONLY
| O_RDWR
| O_NONBLOCK
| O_APPEND
| O_CREAT
| O_TRUNC
| O_EXCL
| O_NOCTTY
| O_DSYNC
| O_SYNC
| O_RSYNC
#if OCAML_VERSION =~ ">=3.13" then
| O_SHARE_DELETE
#end
#if OCAML_VERSION =~ ">=4.01" then
| O_CLOEXEC
#end
You don't have to add anything to the build to have these work. The compiler bsc
understands these already.
Built-in & Custom Variables
See the output of bsc.exe -bs-list-conditionals
:
SH> bsc.exe -bs-D CUSTOM_A="ghsigh" -bs-list-conditionals
OCAML_PATCH "BS"
BS_VERSION "1.2.1"
OS_TYPE "Unix"
BS true
CUSTOM_A "ghsigh"
WORD_SIZE 64
OCAML_VERSION "4.02.3+BS"
BIG_ENDIAN false
Add your custom variable to the mix with -bs-D MY_VAR="bla"
:
SH> bsc.exe -bs-D MY_VAR="bla" -bs-list-conditionals
OCAML_PATCH "BS"
BS_VERSION "1.2.1"
OS_TYPE "Unix"
BS true
MY_VAR="bla"
...
Concrete Syntax
MLstatic-if
| HASH-IF-BOL conditional-expression THEN //
tokens
(HASH-ELIF-BOL conditional-expression THEN) *
(ELSE-BOL tokens)?
HASH-END-BOL
conditional-expression
| conditional-expression && conditional-expression
| conditional-expression || conditional-expression
| atom-predicate
atom-predicate
| atom operator atom
| defined UIDENT
| undefined UIDENT
operator
| (= | < | > | <= | >= | =~ )
atom
| UIDENT | INT | STRING | FLOAT
IF-BOL means
#IF
should be in the beginning of a line.
Typing Rules
type of INT is
int
type of STRING is
string
type of FLOAT is
float
value of UIDENT comes from either built-in values (with documented types) or an environment variable, if it is literally
true
,false
then it isbool
, else if it is parsable byBelt.Int.fromString
then it is of type int, else if it is parsable byBelt.Float.fromString
then it is float, otherwise it would be stringIn
lhs operator rhs
,lhs
andrhs
are always the same type and return boolean.=~
is a semantic version operator which requires both sides to be string.
Evaluation rules are obvious. =~
respect semantic version, for example, the underlying engine
MLsemver Location.none "1.2.3" "~1.3.0" = false;;
semver Location.none "1.2.3" "^1.3.0" = true ;;
semver Location.none "1.2.3" ">1.3.0" = false ;;
semver Location.none "1.2.3" ">=1.3.0" = false ;;
semver Location.none "1.2.3" "<1.3.0" = true ;;
semver Location.none "1.2.3" "<=1.3.0" = true ;;
semver Location.none "1.2.3" "1.2.3" = true;;
Tips & Tricks
This is a very small extension to OCaml. It's backward compatible with OCaml except in the following case:
MLlet f x =
x
#elif //
#elif
at the beginning of a line is interpreted as static if
. there is no issue with #if
or #end
, since they are already keywords.