Version blocks

Version blocks allows one to write platform-specific code. For example:

runThing: class {
  version (windows) {
    // use CreateProcess ...

  version (!windows) {
    // use something more unix-y

Version blocks support complex binary expressions, like &&, ||, !, and any depth of parenthesis nesting. For example:

version (!(osx || linux)) {
  // not on osx, nor on linux

Available version blocks and their corresponding C defines are as follow:

Identifier Description
windows Windows OS, both 32 and 64-bit
linux Linux
solaris Solaris
unix Unices (Apple products do not define this)
beos BeOS
haiku Haiku (BeOS-like)
apple All things apple: iOS, OSX
ios iOS: iPhone, iPad
ios_simulator iOS compiled for the ios simulator
osx Mac OSX (consider using apple instead)
freebsd FreeBSD
openbsd OpenBSD
netbsd NetBSD
dragonfly DragonFly BSD
cygwin Cygwin toolchain
mingw MinGW 32 or 64-bit toolchain
mingw64 MinGW 64-bit toolchain
gnuc GCC (GNU C)
msvc Microsoft Visual C++
android Android toolchain
arm ARM processor architecture
i386 Intel x86 architecture (defined by GNU C)
x86 Intel x86 architecture (defined by MinGW)
x86_64 AMD64 architecture
ppc PPC architecture
ppc64 PPC 64-bit architecture
64 64-bit processor architecture and toolchain
gc Garbage Collector activated on compilation

The specs in italics are “complex” specs - they can’t be in a composed version expression, e.g. this is invalid:

version (64 && osx) {
    // code here

Consider doing this instead:

version (64) {
    version (osx) {
        // code here

The reason for this is that osx, ios, and ios_simulator are not simple #ifdefs in the generated code, but they require an include and an equality test.

Line continuations

Any line can be broken down on several lines, by using the backslash character, \, as a line continuation:

someList \
map(|el| Something new(el))

If it wasn’t for that \ before the end of the line, map would be interpreted as a separate function call, and not a method call.


Some constants are accessible anywhere and will be replaced at compile time with strings. Those are:


Call chaining

While not technically a preprocessor feature, the following code:

a := ArrayList<Int> new(). add(1). add(2). add(3)

Is equivalent to:

a := ArrayList<Int> new()
a add(1). add(2). add(3)

Itself equivalent to:

a := ArrayList<Int> new()
a add(1)
a add(2)
a add(3)


The compiler can read a text file at compile time, and insert it as a string literal in the code.

// assuming 'secret-assets' is a directory we don't ship
secrets := slurp("../secret-assets/secrets.txt")

secrets split("\n") each(|secret|
  secret println()

slurp only takes a single argument, and it must be a string literal (so you can’t compute paths, as that would requiring interpreting ooc code at compile time, effectively requiring macros).

It is not guaranteed to work with non-text files as it is transformed into an (escaped) string literal.

The path passed to slurp should be relative to the SourcePath of the .use file to which the source file using slurp corresponds. It is not relative to the source file itself.

Example directory structure for the example above:

├── secret-assets
│   └── secrets.txt
├── source
│   └── foobar
│       └── package
│           └── something.ooc
└── foobar.use

In this example, foobar.use contains SourcePath: source, as is usual with ooc libraries.

slurp is used for example in dye, to ship shader files in the executable. Note that dye uses that as a fallback, only if it fails to read shader files from disk, which allows customization without recompiling.