Skip to content

Latest commit

 

History

History
148 lines (121 loc) · 4.27 KB

Conditional-compilation-support.adoc

File metadata and controls

148 lines (121 loc) · 4.27 KB

Conditional compilation support - static if

It is quite common that people 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. In the OCaml community there are several preprocessors: cppo, ocp-pp, camlp4 IFDEF macros, optcomp and ppx optcomp.

Instead of using a preprocessor, BuckleScript adds language level static if compilation to the language. It is less powerful than other preprocessors since it only supports static if (no #define, #undefine, #include) but there are several advantages.

  • It’s very small (only around 500 LOC) and highly efficient. There is no added pass, allowing everything to be done in a single pass. It is easy to rebuild the pre-processor in a stand alone file, with no dependencies on compiler libs to back-port it to old OCaml compilers.

  • It’s purely functional and type safe, easy to work with IDEs like Merlin.

Concrete syntax

static-if
| HASH-IF-BOL conditional-expression THEN // (1)
   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
  1. 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;;

Examples

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

Built in variables and custom variables

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

Changes to command line options

For BuckleScript users, nothing needs to be done (it is baked in the language level). For non BuckleScript users, we provide an external pre-processor, so it will work with other OCaml compilers too. Note that the {BuckleScript}/blob/master/jscomp/bin/bspp.ml[bspp.ml] is a stand alone file, so that it even works without compilation.

Example
bsc.exe -c lwt_unix.mli
ocamlc -pp 'bspp.exe' -c lwt_unix.mli
ocamlc -pp 'ocaml -w -a bspp.ml' -c lwt_unix.mli
Warning

This is a very small extension to the OCaml language, it is backward compatible with OCaml language with such exceptions.

let f x =
  x
#elif // (1)
  1. #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.