Conditional Compilation

Developers can complete conditional compilation based on predefined or user-defined conditions. Currently, Cangjie supports conditional compilation for imports and declarations.

Conditional Compilation for Imports and Declarations

Cangjie can use the built-in compilation tag @When to complete conditional compilation. One or more sets of conditions are enclosed in square brackets ([]). @When can be used for the import nodes and declaration nodes except the package nodes.

Usage

The following uses the built-in OS compilation conditions as an example:

@When[os == "Linux"]
class mc{}

main(): Int64 {
    var a = mc()
    return 0
}

In the preceding code, developers can correctly compile and execute the code in the Linux system. In a non-Linux system, a compilation error indicating that the mc class definition cannot be found occurs.

Note that:

  • Cangjie does not support compilation condition nesting. The following is not allowed:

    @When[os == "Windows"]
    @When[os == "Linux"]    // Error, illegal nested when conditional compilation
    import std.ast.*
    @When[os == "Windows"]
    @When[os == "Linux"]    // Error, illegal nested when conditional compilation
    func A(){}
    
  • @When[...] is a built-in compilation tag and is processed before import. If the code generated by expanding the macro contains @When[...], a compilation error is reported. For example:

    @M0                     // macro which returns the input
    @When[os == "Linux"]    // Error, unexpected when conditional compilation directive
    func A(){}
    

Built-in Compilation Condition Variables

Cangjie provides five built-in condition variables: os, backend, cjc_version, debug, and test.

os

os indicates the operating system of the target platform. os supports the == and != operators. The following operating systems are supported: Windows, Linux, and macOS.

The usage is as follows:

@When[os == "Linux"]
func foo() {
    print("Linux, ")
}
@When[os == "Windows"]
func foo() {
    print("Windows, ")
}
@When[os != "Windows"]
func fee() {
    println("NOT Windows")
}
@When[os != "Linux"]
func fee() {
    println("NOT Linux")
}
main() {
    foo()
    fee()
}

If the compilation is performed in the Windows environment, the information Windows, NOT Linux is displayed. If the compilation is performed in the Linux environment, the information Linux, NOT Windows is displayed.

backend

backend is a built-in condition of Cangjie. Cangjie is a multi-backend language that supports various backend conditional compilations. The backend condition supports the == and != operators.

The following backends are supported: cjnative, cjnative-x86_64, cjnative-aarch64.

When the condition is cjnative, the arch information is automatically supplemented based on the environment information when the compiler is executed.

The usage is as follows:

@When[backend == "cjnative-x86_64"]
func foo() {
    print("cjnative-x86_64 backend, ")
}
@When[backend == "cjnative-aarch64"]
func foo() {
    print("cjnative-aarch64 backend, ")
}
@When[backend != "cjnative-x86_64"]
func fee() {
    println("NOT cjnative-x86_64 backend")
}
@When[backend != "cjnative-aarch64"]
func fee() {
    println("NOT cjnative-aarch64 backend")
}

main() {
    foo()
    fee()
}

If the compilation is performed using a release package of the cjnative-x86_64 backend, you will obtain the cjnative-x86_64 backend, NOT cjnative-aarch64 backend information. If the compilation is performed using a release package of the cjnative-aarch64 backend, you will obtain the cjnative-aarch64 backend, NOT cjnative-x86_64 backend information.

cjc_version

cjc_version is a built-in condition of Cangjie. Developers can select the code to be compiled based on the current Cangjie compiler version. The cjc_version condition supports the ==, !=, >, <, >=, and <= operators. The format is xx.xx.xx, where each xx can be one or two digits. The comparison rule is based on zero-padding (padding to 2 digits), for example, 0.18.8 < 0.18.11, 0.18.8 == 0.18.08.

The usage is as follows:

@When[cjc_version == "0.18.6"]
func foo() {
    println("cjc_version equals 0.18.6")
}
@When[cjc_version != "0.18.6"]
func foo() {
    println("cjc_version is NOT equal to 0.18.6")
}
@When[cjc_version > "0.18.6"]
func fnn() {
    println("cjc_version is greater than 0.18.6")
}
@When[cjc_version <= "0.18.6"]
func fnn() {
    println("cjc_version is less than or equal to 0.18.6")
}
@When[cjc_version < "0.18.6"]
func fee() {
    println("cjc_version is less than 0.18.6")
}
@When[cjc_version >= "0.18.6"]
func fee() {
    println("cjc_version is greater than or equal to 0.18.6")
}
main() {
    foo()
    fnn()
    fee()
}

The execution result of the preceding code varies according to the cjc version.

debug

debug specifies whether the debugging mode is enabled, that is, whether the -g compilation option is enabled. This option can be used to switch between the debugging and release versions during code compilation. The debug condition supports only the logical NOT operator (!).

The usage is as follows:

@When[debug]
func foo() {
    println("debug")
}
@When[!debug]
func foo() {
    println("NOT debug")
}
main() {
    foo()
}

If -g is enabled, the cjc debug information is displayed. If -g is not enabled, the NOT debug information is displayed.

test

test specifies whether the unit test option --test is enabled. The test condition supports only the logical NOT operator (!). It can be used to distinguish test code from common code. The usage is as follows:

@When[test]
@Test
class Tests {
    @TestCase
    public func case1(): Unit {
        @Expect("run", foo())
    }
}

func foo() {
    "run"
}

@When[!test]
main () {
    println(foo())
}

Using --test for compilation and execution will yield test results, while compiling and running without --test will complete normally and provide the run information.

User-defined Compilation Condition Variables

Cangjie allows developers to define compilation condition variables and values. A defined condition variable must be a valid identifier and cannot have the same name as built-in condition variables. Its value is a character string literal. User-defined conditions support the == and != operators. Different from built-in condition variables, user-defined conditions need to be defined by using the --cfg compilation option during compilation or in the cfg.toml configuration file.

Configuring User-defined Condition Variables

You can configure user-defined condition variables in either of the following ways: Configure key-value pairs in the compilation options or configure key-value pairs in the configuration file.

You can use --cfg <value> to pass user-defined compilation condition variables to the compiler in the form of key-value pairs or specify the search path of the cfg.toml configuration file.

  • The option value must be enclosed in double quotation marks ("").

  • If the option value contains an equal sign (=), the value is configured in the form of key-value pairs. If the path contains an equal sign (=), use a backslash (\) to escape the equal sign. Multiple key-value pairs can be separated by commas (,). Example:

    $ cjc --cfg "feature = lion, platform = dsp" source.cj
    
  • The --cfg compilation option can be used for multiple times, for example:

    $ cjc --cfg "feature = lion" --cfg "platform = dsp" source.cj
    
  • The same condition variable cannot be defined for multiple times, for example:

    $ cjc --cfg "feature = lion" --cfg "feature = meta" source.cj
    
    $ cjc --cfg "feature = lion, feature = meta" source.cj
    

    Errors are reported for the preceding two compilation commands.

  • If the option value does not contain the equal sign (=) or contains the equal sign (=) that is escaped by the backslash (\), the option value is passed to the compiler as the search path of the cfg.toml configuration file, for example:

    $ cjc --cfg "./cfg" source.cj
    

    If the cfg.toml file exists in the ./cfg directory, the user-defined compilation conditions configured in the ./cfg/cfg.toml file are passed to the compiler during compilation. In the cfg.toml file, you need to use key-value pairs to configure user-defined condition variables. Each key-value pair occupies a line. The key name is a valid identifier, and the key value is a character string enclosed in double quotation marks (""). Example:

    feature = "lion"
    platform = "dsp"
    
  • When --cfg is used to configure the search path of the cfg.toml file for multiple times, the cfg.toml file is searched in the input sequence. If the cfg.toml file is not found in any input search path, the cfg.toml file is searched in the default path.

  • If the --cfg compilation option is used for multiple times and the configuration is performed in the form of key-value pairs, the configuration in the cfg.toml configuration file is ignored.

  • If the --cfg compilation option is not used, the compiler searches for the cfg.toml configuration file in the default path (the package directory or cjc execution directory specified by --package or -p).

Multi-condition Compilation

Cangjie conditional compilation allows developers to combine multiple conditional compilation options. Logical operators can be used to combine multiple conditions, and parentheses can be used to specify priorities.

The usage is as follows:

//source.cj
@When[(test || feature == "lion") && !debug]
func fee() {
    println("feature lion")
}
main() {
    fee()
}

Run the following command to compile and run the preceding code:

$ cjc --cfg="feature=lion" source.cj -o runner.out

The output is as follows:

feature lion