Version blocks use the following syntax:
version (<version expression>) {
<body>
} else {
<alternative body>
}
Where can be any of:
<version name>
!<version expression>
<version expression> && <version expression>
<version expression> || <version expression>
Version blocks aren't if-else blocks - they aren't evaluated at runtime. In rock, version blocks aren't evaluated at ooc-compile-time either. They're evaluated at C compile time. Which means the C code generated by rock should be the same on any platform.
Practically, in rock, version blocks are an abstraction for #ifdef / #endif blocks. The syntax makes it harder to forget to close a version block than an #ifdef / #endif block, and a few handy aliases (listed below) for commonly used version names are standard, so that developers don't have to remember the convoluted corresponding C defines.
For other compilers not based on the C language, version block handling may happen at any stage of the compilation (if any), as long as the version expressions are correctly evaluated and have the correct meaning (for example, a 'windows' version block should be ignored on OSX)
C defines are included here for completeness, but are only relevant for people who want to implement ooc on top of C.
Name | corresponding C define |
---|---|
windows | WIN32 |
linux | linux |
solaris | __sun |
unix | unix |
beos | BEOS |
haiku | HAIKU |
apple | APPLE |
gnuc | GNUC |
i386 | i386 |
x86 | X86 |
x86_64 | x86_64 |
ppc | ppc |
ppc64 | ppc64 |
64 | x86_64 |
gc | OOC_USE_GC |
Most of the standard version names above depend on your building environment, and the 'gc' name depends on the compiler setting -gc=[off,static,dynamic].
Custom version names can be used, and turned on/off with the -D and -U compiler flags, for example:
version(debug) {
"[%d] Saving database %s" println(timestamp(), db name)
}
db save()
The code inside the version(debug) block will be compiled if -Ddebug is used. It is common practise for ooc developers to use the -Ddebug switch to debug their applications.
Version blocks can be used in function bodies, to make certain parts of the code OS-specific, or they can be used at the mdule-level to make functions or types OS-specific.
If different types are defined in different version blocks, make sure they expose the same interface. It's not necessary for them to have the exact same class layout, but they should at least have all the methods/fields that are used in every OS.
version(windows) {
"Hi, Bill!" println()
}
version(apple) {
"Hi, Steve!" println()
}
version(linux) {
"Hi, Linus!" println()
}
version(!windows && !apple && !linux) {
"Who are you, and what did you do to my OS?"
}
version(apple) {
"Nice Hardware!" println()
} else {
"So you like your computer made of plastic then!" println()
}
See also io/File and os/Time in the SDK for real-world examples of heavily versioned code.
In ooc, 'new' isn't a keyword but a static method. As a result, you can define new yourself. This allows an interesting pattern for OS-specific classes in ooc:
// io/File
import io/[FileUnix, FileWin32]
File: class {
path: String
new: static func (.path) -> This {
version(windows) { return FileWin32 new(path) }
version(unix) { return FileUnix new(path) }
Exception new(This, "Unsupported platform") throw()
}
// abstract methods
}
// io/FileUnix
FileUnix: class extends File {
init: func (=path) {}
// implement abstract methods for unix
}
// io/FileWin32
FileWin32: class extends File {
init: func (=path) {}
// implement abstract methods for Win32
}