Defining and Importing Macro Packages

The definition of a Cangjie macro must be placed in the package declared as a macro package. The package constrained by the macro package allows only the macro definition to be visible to external systems.

Note:

Re-exported declarations are also visible to external systems. For details about package management and re-export, see Package Import.

// file define.cj
macro package define         // Compile define.cjo with the macro attributes.
import std.ast.*

public func A() {}          // Error, macro package does not allow external visible non-macro definitions. An error is reported.

public macro M(input: Tokens): Tokens { // macro M is visible to external systems.
    return input
}

It should be specifically noted that in a macro package, symbols from both macro packages and non-macro packages can be re-exported. In a non-macro package, only symbols from non-macro packages can be re-exported.

See the following example.

  • Define the M1 macro in a macro package A

    macro package A
    import std.ast.*
    
    public macro M1(input: Tokens): Tokens {
        return input
    }
    

    The compilation command is as follows.

    cjc A.cj --compile-macro
    
  • Define a public function f1 in a non-macro package B. Note that the macro package symbols cannot be re-exported in a non-macro package.

    package B
    // public import A.* // Error, it is not allowed to re-export a macro package in a package.
    
    public func f1(input: Int64): Int64 {
        return input
    }
    

    The following is the compilation command. Here we use the --output-type option to compile the B package into the dynamic library. For details about the cjc compilation options, see cjc Compilation Options.

    cjc B.cj --output-type=dylib -o libB.so
    
  • Define the M2 macro in macro package C, which relies on the content of packages A and B. It can be observed that macro package and non-macro package symbols can be re-exported in the macro package.

    macro package C
    public import A.* // correct: macro package is allowed to re-export in a macro package.
    public import B.* // correct: non-macro package is also allowed to re-export in a macro package.
    import std.ast.*
    
    public macro M2(input: Tokens): Tokens {
        return @M1(input) + Token(TokenKind.NL) + quote(f1(1))
    }
    

    The following shows the compilation command. Note that the dynamic library of the B package needs to be explicitly linked.

    cjc C.cj --compile-macro -L. -lB
    
  • Use the M2 macro in main.cj

    import C.*
    
    main() {
        @M2(let a = 1)
    }
    

    The compilation command is as follows.

    cjc main.cj -o main -L. -lB
    

    The result obtained by expanding the M2 macro in main.cj is:

    import C.*
    
    main() {
        let a = 1
        f1(1)
    }
    

It is obvious that the symbol f1 from package B appears in main.cj. The macro compiler can re-export the symbols in the B package in the C package, enabling users to correctly compile the code after macro expansion by importing only the macro package. If only import C.M2 is used to import macro symbols in main.cj, the error message undeclared identifier 'f1' is reported.