Sometimes you want to write code that works with different versions of compilers and libraries.
Instead of a preprocessor, BuckleScript adds language-level static
if compilation. It's less powerful than other preprocessors since it only supports static
#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.
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-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
> 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" ...
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
#IFshould be in the beginning of a line.
- type of INT is
- type of STRING is
- type of FLOAT is
- value of UIDENT comes from either built-in values (with documented types) or an environment variable, if it is literally
falsethen it is
bool, else if it is parsable by
int_of_stringthen it is of type int, else if it is parsable by
float_of_stringthen it is float, otherwise it would be string
lhs operator rhs,
rhsare 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 at the beginning of a line is interpreted as static
if. there is no issue with
#end, since they are already keywords.