Defining and Importing Macro Packages

Macro definitions must be placed in the package declared as a macro package in the Cangjie language.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 noted that in macro packages, declarations of other macro packages and non-macro packages can be re-exported. However, in non-macro packages, only declarations of 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 "Appendix > 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.