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.
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
-
IF-BOL means
#IF
should be in the beginning of a line
-
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 byint_of_string
then it is of type int, else if it is parsable byfloat_of_string
then it is float, otherwise it would be string -
In
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
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;;
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
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
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.
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)
|