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 the following built-in conditional variables: os, backend, arch, 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

Cangjie is a multi-backend language. <idp:inline displayname="code" id="office_b1ffde89-2484-47f5-9df6-f735c59e85bc">backend</idp:inline> indicates the backend type of the target platform, which is used to support conditional compilation on various backends.The <idp:inline displayname="code" id="office_ab088ba3-921d-4c42-9ac3-b2ba9b7d1d60">backend</idp:inline> condition supports the <idp:inline displayname="code" id="office_cb7c7fb9-3b3f-452e-a384-792569bfd246">==</idp:inline> and <idp:inline displayname="code" id="office_6d4f645b-c658-450f-ad13-022cc9560a0e">!=</idp:inline> operators.

The supported backends are cjnative and cjvm.

The usage is as follows:

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

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

arch

arch indicates the processor architecture of the target platform. The arch condition supports the == and != operators.

The supported processor architectures are x86_64 and aarch64.

The usage is as follows:

@When[arch == "aarch64"]
var arch = "aarch64"

@When[arch == "x86_64"]
var arch = "x86_64"

main() {
    println(arch)
}

If code is compiled and executed on the target platform of the x86_64 architecture, the x86_64 information is obtained. If it is on the target platform of the aarch64 architecture, the aarch64 information is obtained.

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 directory contains the cfg.toml file, the compiler automatically obtains the user-defined compilation conditions configured in the ./cfg/cfg.toml file during compilation. In the cfg.toml file, you need to use key-value pairs, each occupying a line, to configure user-defined conditional variables. The key name must be a valid common identifier of Cangjie. The key value must be a character string that is enclosed in double quotation marks (") and cannot be escaped. The cfg.toml file also supports full-line comments and end-of-line comments. For example:

    feature = "lion"
    platform = "dsp"
    # Full-line comments
    feature = "meta" # End-of-line comments
    
  • 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