Skip to main content

Conditional Compilation

This doesn't work with the Reason syntax right now, only the 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, BuckleScript 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.

Note: BuckleScript's conditional compilation doesn't work with Reason yet.

Usage​

lwt_unix.mli

type 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:

> 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":

> 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​

static-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 is bool, else if it is parsable by int_of_string then it is of type int, else if it is parsable by float_of_string then it is float, otherwise it would be string
  • In lhs operator rhs, lhs and rhs 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

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" = 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:

let 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.