author: Huawei Technologies Co., Ltd. title: Cangjie Multiplatform Programmer's Guide subtitle: for iOS™ titlepage: true numbersections: true toc-own-page: true listings-no-page-break: true textables: false

Introduction

The Cangjie Multiplatform technology enables Cangjie developers to incorporate their code into new Android/iOS applications and/or gradually replace the Java/Objective-C parts of existing applications with Cangjie code. The central mechanism that enables the cross-language, cross-runtime interoperability is mirror types, which expose the types of one language to the other.

On the Cangjie side, mirror types enable the inheritance of Java/Objective-C classes with method overriding, as well as the implementation of Java interfaces and Objective-C protocols, all while using conventional Cangjie syntax. On the Java/Objective-C side, the mirror types that represent Cangjie types are expressed in terms of the respective language's own types. All in all, this enables seamless transitions between the Java/Objective-C and Cangjie parts of the application, which includes usage of the target O/S APIs in Cangjie code.

The Challenge and the Solution

Java, Objective-C and Cangjie are object-oriented languages that support inheritance and polymorphism. However, the differences in their semantics, object models, and execution models preclude direct usage of Cangjie objects in Java or Objective-C code and vice versa.

Then, all three languages have managed runtimes that support automatic memory management, threading, exception handling and other low-level features, but again they do that differently. Making two separately designed complex language runtimes aware of each other might push the complexity of the entire system beyond human comprehension.

Instead, the interoperability between Cangjie and Java is achieved by disguising them both as low-level languages. Cangjie and Java parts of the application see each other through the lens of the Java Native Interface (JNI), originally designed to facilitate the development of Java native methods in languages such as C and C++. JNI is a rich, but low-level API, and writing native Java methods is known to be cumbersome. Fortunately, CJMP automates away the complexity of JNI.

The Objective-C Runtime module API serves a similar role on iOS. It is also designed specifically to enable the creation of bridge layers between Objective-C and other languages. And just like with JNI, CJMP takes care about the cumbersome parts.

Additional Setup

The current version of the Cangjie SDK for iOS requires you to download the Cangjie.h header file from the Cangjie open source repository:

https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h

and integrate it into your XCode project in order to use the interoperability features described in this document.

Key Concepts

Mirror Types

Considering Cangjie and Objective-C as a pair of interoperating languages, a mirror type T' defined in one language of the pair is a type that represents an existing type T defined in the other language, enabling the code written in the first language to use that type, possibly with some limitations.

The Boolean types and numeric types that are essentially the same in both languages naturally mirror each other: the Objective-C mirror type for the Cangjie type Int32 is int and vice versa. Mirroring of numeric types that the other language does not naturally support is not currently possible, so e.g. the Cangjie type Float16 cannot be mirrored to an Objective-C primitive type.

For a user-defined type such as class, struct, protocol, or interface, the mirror type would be its closest equivalent in the other language. For instance, the only Objective-C type that can approximate a Cangjie struct type with reasonable accuracy is an Objective-C class attributed with objc_subclassing_restricted.

A mirror type exposes the members and constructors of the original user-defined type that are accessible and can be used in the other language, so again a Cangjie function that returns a value of type Float16 cannot be mirrored straight to Objective-C.

Normally you would obtain mirror type definitions for a type that you want to use in the other language and its required dependencies automatically. For Objective-C types, a standalone mirror generator is provided, whereas Objective-C mirrors of Cangjie types are generated by the cjc compiler as a byproduct of the normal compilation process.

Mirroring Objective-C Types to Cangjie

The cjc compiler replaces any uses of Objective-C mirror types with the appropriate glue code, so only the names of types themselves and names and types of their accessible members matter. Therefore, mirrors of Objective-C types only contain member declarations, not definitions: constructors and member functions/properties have no bodies and member variables have no initializers. For the same reason, @private members are omitted.

For such an extension of the regular Cangjie syntax to work, each mirror type must be marked with an @ObjCMirror annotation. It helps the compiler distinguish between mirror type declarations and normal Cangjie type definitions.

For example, a mirror class for the following Objective-C class:

@interface Node : NSObject {
}
- (id)initWith:(int)x;
- (int)getX;
@end

might look like this:

@ObjCMirror
public open class Node <: NSObject {
    @ForeignName["initWith:"]
    public init(x: Int32) 
    public open func getX(): Int32
}

Mirroring Cangjie Types to Objective-C

Unlike the mirrors of Objective-C types, for which cjc has dedicated support, mirrors of Cangjie types must look exactly like normal Objective-C types from the outside, for the iOS toolchain to pick them up. The cjc compiler therefore generates those mirrors in the form of Objective-C source code files containing glue code responsible for linking the Objective-C and Cangjie worlds together.

For instance, if the class Node from the example in the previous section was originally defined in Cangjie as follows:

public class Node {
    private let _x: Int
    public func x(): Int {
        _x
    }
    public init(x: Int) {
        this._x = x
    }
}

the cjc compiler could generate the following Objective-C mirror for it (glue code mostly omitted for brevity):

// Node.h
@interface Node : NSObject
- (id)init:(int64_t)x;
- (int)x;
@end
// Node.m
@implementation Node
- (id)init:(int64_t)x {
    /* Glue code constructing a Cangjie Node instance and associating
     * it with the Objective-C Node instance being constructed, i.e. 'self'.
     */
}
- (int64_t)x {
    /* Glue code invoking the 'x' member function of the associated
     * Cangjie Node instance and returning the result.
     */
     }
@end

Mirror Functions

Both Objective-C and Cangjie support top-level, global functions that are not members of any other type. They are exposed to the other language as mirror functions, which are essentially automatically generated pieces of glue code that pass control and data through the inter-language barrier.

Interop Classes

An interop class is essentially a Cangjie class that is derived from one or more mirror types and is usable from Objective-C. All its constructors and non-inherited public member functions are exposed to Objective-C code via a conjugate wrapper class, automatically generated by the cjc compiler. The wrapper class itself defines no other user-callable methods or constructors, but any methods it may have inherited from its supertypes may be called from both Objective-C and Cangjie code.

For example, when compiling the following Objective-C interop class:

@ObjCImpl
public class BooleanNode <: Node {
    private let _flag: Bool
    public init(x: Int32, flag: Bool) {
        super.init(x)
        this._flag = flag
    }
    public func flag(): Bool {
        _flag
    }
}

the cjc compiler will also yield a pair of Objective-C source code files similar to the following:

// BooleandNode.h
@interface BooleanNode : Node
/* glue code */
- (id)init:(int32_t)x:(BOOL)flag;
- (BOOL)flag;
/* more glue code */
@end
// BooleandNode.m
@implementation BooleanNode : Node
/* glue code */
- (id)init:(int32_t)x:(BOOL)flag {
    /* Glue code constructing a Cangjie BooleanNode(x, flag) instance and
     * associating it with the Objective-C instance being constructed,
     * i.e. 'self'.
     */
}
- (BOOL)flag {
    /* Glue code invoking the 'flag' member function of the Cangjie
     * BooleanNode instance associated with 'self' and returning the result.
     */
}
/* more glue code */
@end

Foreign Types

The mirror types and interop classes are not perfectly native to the language in which they are defined. Extending the analogy, their status is more like temporary visitors and work visa holders respectively, so they are collectively called foreign types throughout this document.

Objective-C-Compatible Types

The following Cangjie types are called Objective-C-compatible:

  • Value types that have direct equivalents among Objective-C primitive types (Int16 is included, but Float16 is not)
  • Foreign types
  • Types of the form Option<T> where T is a foreign type (see null Handling for reasoning)

For obvious reasons, the parameters and return values of public member functions of a foreign type may only have types compatible with the respective language.

The member variables of an interop class may have any types. Their public member variables that have Objective-C-compatible types may be accessed from both Objective-C and Cangjie.

Interop Scenarios

There are essentially two Objective-C-Cangjie interoperation scenarios:

The above scenarios are not mutually exclusive, one application may as well be utilizing both. Also, class inheritance and interface implementation are supported in both scenarios (an Objective-C class may subclass a Cangjie class and vice versa). That enables seamless callbacks, so neither scenario is unidirectional. However, each scenario has its own feature set, limitations, tooling, etc., so they are described separately.

Using Cangjie in Objective-C

To enable access to a Cangjie library, API, or some other piece of code from Objective-C, you need to generate Objective-C mirror types for the Cangjie types that you want to expose. The cjc compiler can do that for you as described in Cangjie Mirror Generation Reference.

NOTE: The type system of the Cangjie language is more versatile than its Objectiove-C counterpart. There are also some principal differences in the features shared by the two languages, most notably generics. Expressing certain features of Cangjie in Objective-C is therefore impossible, and other features can be difficult to express in a natural and compact manner. See Cangjie to Objective-C Mapping for the exact list of supported features and associated restrictions.

Example:

Suppose you want to expose to Objective-C a Cangjie struct Vector defined as follows:

package cj

import interoplib.objc.*

public struct Vector {
    private let _x: Int32
    private let _y: Int32

    public prop x: Int32 {
        get() {
            _x
        }
    }
    public prop y: Int32 {
        get() {
            _y
        }
    }

    public init(x: Int32, y: Int32) {
        _x = x
        _y = y
    }

    public func add(v: Vector): Vector {
        Vector(x + v.x, y + v.y)
    }
}

First, you need to import the interoplib.objc package into the source code file where Vector is defined:

import interoplib.objc.*

When compiling Vector.cj, you would invoke the cjc compiler with two additional options: --experimental --enable-interop-cjmapping=ObjC. The compiler would then generate two extra files, Vector.h and Vector.m, similar to the following:

// Vector.h
#import <Foundation/Foundation.h>
#import <stddef.h>

__attribute__((objc_subclassing_restricted))
@interface Vector : NSObject

/* glue code */

- (id)init:(int32_t)x :(int32_t)y;

@property (readonly, getter=x) int32_t x;
- (int32_t)x;
@property (readonly, getter=y) int32_t y;
- (int32_t)y;

- (Vector*)add:(Vector*)v;

/* glue code */

@end
// Vector.m

/* glue code */

@implementation Vector

/* glue code */

- (id)init:(int32_t)x:(int32_t)y {
    /* Glue code constructing an instance of Cangjie Vector(x,y) and associating
     * it with 'self'.
     */
}
- (int32_t)x {
    /* Glue code retrieving the value of the 'x' property of the
     * assocated instance of Cangjie Vector.
     */
}
- (int32_t)y {
    /* Glue code retrieving the value of the 'y' property of the
     * assocated instance of Cangjie Vector.
     */
}
- (Vector*)add:(Vector*)v {
    /* Glue code invoking the 'add' mmber function of the Cangjie Vector
     * instance associated with 'self', passing over the Cangjie Vector
     * instance associated with 'v', and wrapping the result in a new
     * instance of the Objective-C Vector class.
     */
}

/* more glue code */

@end

Now you can add Vector.h and Vector.m to your XCode project and use the class Vector in your Objective-C code as if it was a normal Objective-C class: define variables of the type Vector*, create instances, pass them around, including to Cangjie code via [Vector add], etc.

IMPORTANT: The current version of the Cangjie SDK for iOS also requires downloading and integrating into the XCode project the Cangjie.h header file from the Cangjie open source repository:

https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h

Limiting the Exposure

The option --enable-interop-cjmapping instructs the cjc compiler to generate mirror types for all public Cangjie types that it compiles, and expose all public member functions and constructors of those types. Oftentimes, however, such total exposure is undesirable, as what you want to use in Objective-C may be just a small percentage of the functionality provided by a Cangjie library or framework.

You may precisely control the exposure of Cangjie types and their members by passing the pathname of a dedicated configuration file to cjc via the option --import-interop-cj-package-config-path. That file is a plain text file the contents of which must conform to the TOML syntax.

In the [default] section of that file, you can specify the default API exposure strategy for all Cangjie packages, and then modify it for particular packages in the respective [[package]] entries. For example:

[default]
APIStrategy="None"       # Expose nothing by default

[[packages]]
name="com.example.pkg1"  # From this specific package,
APIStrategy="Full"       # expose everything
#   .  .  .

Furthermore, for each package you can use either the included_apis or excluded_apis property to respectively expose or hide particular types and/or members:

#   .  .  .
[[packages]]
name="com.example.pkg2"          # From this specific package,
included_apis = [ "Vector",      # Only the type 'Vector'
                  "Vector.add"   # and its member function 'add'
                ]                # are exposed

[[packages]]
name="com.example.pkg3"          # From this specific package,
APIStrategy="Full"               # everything
excluded_apis = [ "TopSecret",   # but the type 'TopSecret' and
                  "Auth.getPwd"  # member function 'Auth.getPwd'
                ]                # are exposed

Refer to Cangjie Mirror Generation Reference for more details.

Generic Cangjie Types Mirroring

Many important Cangjie types are parameterized. Unfortunately, certain fundamental differences between Cangjie and Objective-C generics preclude mirroring of the former into the latter. As a workaround, the Cangjie SDK supports monomorphization of mirror types: it generates a separate non-generic Objective-C mirror type for each supplied set of valid type arguments for the given parameterized Cangjie type.

NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments.

For instance, if you have a generic Cangjie class p.Pair<T,U>:

package p
public class Pair<T,U> { . . . }

and want to manipulate instances of Pair<Int, Bool> in your Objective-C code, you would add a generic_object_configuration property to the [[package]] entry for p in the cjc mirror generation configuration file, with the following content:

#   .  .  .
[[package]]
name = "p"
generic_object_configuration = [
    { name = "Pair", type_arguments = [ "Int, Bool" ] },
#       .  .  .

cjc would then generate a non-generic Objective-C class:

@interface PairIntBool
   .  .  .
@end

representing that particular instantiation of the type Pair<T,U>.

Refer to Generics Instantiations for more details.

Cangjie to Objective-C Mapping

The current version of the cjc compiler uses Cangjie → Objective-C mapping described in this chapter. See Cangjie Mirror Generation Reference for applicable cjc command-line options.

General Considerations {#cangjie-general-considerations}

The Cangjie type system is different from that of the Objective-C language. Certain Cangjie types and their features do not have direct or even reasonably close equivalents in Objective-C, so they have limited support in the current version, and some are not supported at all.

Consequently, if the type of a public member, function/constructor parameter, or function return type is not supported, the respective member, function or constructor cannot be mirrored. The current compiler does not report such usages as errors, the respective entity is simply not mirrored.

Mirror types should be compiled into a separate dynamic library with ARC support disabled (-fno-objc-arc Objective-C compiler option). For details, see the notice in the [Classes and Interfaces(#cangjie-classes-and-interfaces) section.

Names {#cangjie-names}

The original names of Cangjie packages, functions, types and type members are currently preserved, which means that some of them may clash with Objective-C keywords, such as "int", or contain characters that are not permitted in Objective-C names.

Boolean and Numeric Types

Cangjie Boolean and numeric types that have equivalents among Objective-C types are mirrored to those equivalent; those that don't (Float16) are not supported:

Cangjie type Objective-C type
Bool BOOL
Int8 int8_t
Int16 int16_t
Int32 int32_t
Int64 int64_t
Int int64_t
IntNative ssize_t
UInt8 uint8_t
UInt16 uint16_t
UInt32 uint32_t
UInt64 uint64_t
UInt uint64_t
UIntNative size_t
Float16 Not supported
Float32 float
Float64 double

See General Considerations for information about the handling of unsupported types.

Rune

The type Rune is not supported.

See General Considerations for information about the handling of unsupported types.

Special Types

The type Unit is only supported as a function return type, mapped to Objective-C void.

The type Nothing cannot be supported.

The type Any is not supported yet.

See General Considerations for information about the handling of unsupported types.

Tuple

Tuple types are not supported yet.

See General Considerations for information about the handling of unsupported types.

Struct Types

Public Cangjie struct type definitions are mirrored into Objective-C class definitions attributed with objc_subclassing_restricted. Those definitions are generated automatically by the cjc compiler and contain glue code that transfers control and data between Objective-C and Cangjie. They should not be modified manually.

package cj

import interoplib.objc.*

public struct Vector {
    let x: Int32
    let y: Int32

    public init(x: Int32, y: Int32) {
        this.x = x
        this.y = y
    }

    public func add(v: Vector): Vector {
        Vector(x + v.x, y + v.y)
    }
}
// Vector.h
#import <Foundation/Foundation.h>
#import <stddef.h>
__attribute__((objc_subclassing_restricted))
@interface Vector : NSObject
// Auxiliary glue code methods
- (id)init:(int32_t)x :(int32_t)y;
- (Vector*)add:(Vector*)v;
// More auxiliary glue code methods
@end
// Vector.m

// Glue code

@implementation Vector

// Glue code

- (id)init:(int32_t)x :(int32_t)y {
    // Glue code creating an instance of Cangjie Vector(x, y) and
    // associating it with 'self'.
}
- (Vector*)add:(Vector*)v {
    // Glue code retireving instances of Cangjie Vector associated
    // with 'self' and 'v', invoking the add() member function
    // of Cangjie-self with Cangjie-v passed a parameter, and then
    // creating a new instance of Vector and associating it with the
    // result of the add() call.
}

// Glue code

@end

Only public struct members and common constructors are mirrored.

Member functions are mirrored into methods with the respective mirror types substituted for parameter types and return value type. Mirrors of member functions returning Unit are mirrored into void methods. Instance member functions are mirrored into instance methods (prefixed with a minus sign -), static member functions are mirrored into class methods (prefixed with a plus sign +).

Member properties of supported types are mirrored into @property declarations with the same name and with type that mirrors the Cangjie property type, attributed with readonly. The getter is mirrored into a parameterless method with the same name as the property and return type that is the mirror of the Cangjie property type. NOTICE: Setters are currently not mirrored, so mirrors of mut member properties are also readonly.

Member variables of supported types are mirrored into @property declarations with the same name and with type that mirrors the Cangjie variable type, attributed with readonly. A getter is synthesized to enable access. NOTICE: Setters are currently not synthesized for var member variables, so mirrors of the latter are also readonly.

Constructors are mirrored into constructors with the respective mirror types substituted for parameter types. This includes the default constructor that might have been implicitly declared.

The current version imposes a number of severe limitations on the struct types that can be mirrored, to the extent that it may be fair to say that a struct type needs to be designed specifically for exposing it to Cangjie:

  • Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.

  • Both let and var member variables are mirrored into readonly properties in the current version, so the latter may not be altered from Objective-C code. This limitation will be removed in a future version. In the meantime, you may add setter functions as a workaround.

  • Mirroring of mut instance member functions is a work in progress. They are mirrored, but incorrect glue code is generated in some circumstances.

  • Mirrored structs may implement interfaces other than Any, but the "implements" relationship is not propagated to Objecttive-C: th generated @interface directive does not bear the names of mirrors of implemented interfaces in angle brackets <> after the class name.

  • Mirroring of member operator functions is not supported. They are skipped during mirror generation.

  • Mirroring of generic structs is supported through monomorphization. See Generics for details.

  • Struct member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored.

  • A mirrored struct may have direct and/or interface extensions, but they are ignored during mirror generation.

  • Memeber functions may not have any type parameters.

Classes and Interfaces {#cangjie-classes-and-interfaces}

Cangjie class and interface definitions are mirrored respectively into Objective-C class and protocol definitions. Those definitions contain automatically generated glue code and should not be altered in any way.

package cj

import interoplib.objc.*

public interface Valuable {
    public func value(): Int
}

public open class Singleton <: Valuable {
    private let _v: Int
    public init(v: Int) {
        _v = v
    }
    public func value(): Int {
        _v
    }
}

public class Zero <: Singleton {
    public init() {
        super(0)
    }
}
// Valuable.h
@protocol Valuable
- (int64_t)value;
@end
// Singleton.h
// Glue code
@interface Singleton : NSObject
// Glue code
-(id)init:(int64_t)v;
- (int64_t)value;
// Glue code
@end
// Singleton.m
// Glue code
@implementation Singleton
// Glue code
-(id)init:(int64_t)v {
    if (self = [super init]) {
        // Glue code creating an instance of Cangjie Singleton(v)
        // and associating it with `self`.
    }
    return self;
}
- (int64_t)value {
    // Glue code calling the value() member function of the Cangjie
    // Singleton instance associated with 'self' and returning the result
}
// Glue code
@end

// Zero.h
#import "Singleton.h"
__attribute__((objc_subclassing_restricted))
@interface Zero : Singleton
// Glue code
- (id)init;
// Glue code
@end
// Zero.m
// Glue code
@implementation Zero
- (id)init {
    if (self = [super init]) {
        // Glue code creating an instance of Cangjie Zero()
        // and associating it with `self`.
    }
    return self;
}
@end

Identifiers that serve as names of mirrored types and their members are currently preserved even if they clash with Objective-C keywords such as int.

Mirrors of non-open classes are attributed with objc_subclassing_restricted.

public members and constructors are mirrored. In addition, protected open instance member functions of open classes are also mirrored, so that they could be overridden in subclasses. No other class or interface members are mirrored.

CAUTION: As there are neither packages nor namespaces of any other kind in Objective-C, the mirrors of such protected open member functions are accessible from anywhere in Objective-C code.

Member variables are not mirrored in the current version and no means for accessing them from Objective-C is provided.

Member properties are not mirrored in the current version.

Member functions are mirrored into methods with the respective mirror types substituted for parameter types and return value type. Mirrors of member functions returning Unit are mirrored into void methods. static member functions are mirrored into class methods (prefixed with "+"), instance member functions -- into instance methods (prefixed with "-").

Common constructors are mirrored into constructors with the respective mirror types substituted for parameter types. This includes the default constructor that might have been implicitly declared.

IMPORTANT: From the outside, types that mirror Canglie classes and interfaces are normal Objective-C classes and protocols in all aspects: they can be inherited/implemented, their methods can be overloaded and overridden with conventional Objective-C methods, types of their instances can be queried, and so on. The differences between the languages, however, pose a few potential problems that require extra care when using such types in Objective-C code:

  1. There is no attribute that would make Objective-C methods non-overrideable, so methods that mirror non-open member functions of open classes can, but obviously should not, be overridden.

  2. The current implementation does not support overriding of mirrors of static Cangjie methods of open classes. They are mirrored into Objective-C class methods (those prefixed with a plus sign +), and should never be overridden in subclasses. There is no way to enforce this restriction at compile-time.

  3. Constructors are not inherited in Cangjie, whereas init methods in Objective-C are inherited just like other methods, which may lead to undesirable effects. For instance, consider the following Cangjie classes:

    public class A {
        public init() {...}
    }
    public class B <: A {
        private init() {...}
        public init(x: Int) {...}
    }
    

    In Cangjie, the class B does not expose its parameterless common constructor. However, the mirror of B will inherit the parameterless init method from the mirror of A, so the expression [[B alloc] init] will compile and run, but the init method inherited from A will create and associate with self an instance of the Cangjie class A, not B.

NOTICE: Mirrors of open Cangjie classes rely on low-level Objective-C features that are not compatible with Automatic Reference Counting (ARC). That is why all mirror types should generally be compiled into a separate dynamic library with ARC support disabled (-fno-objc-arc Objective-C compiler option).

The current version imposes a number of severe limitations on the class and interface types that can be mirrored, to the extent that it may be fair to say that such types need to be designed specifically for exposing them to Cangjie:

  • Abstract classes are not mirrored and mirrors of concrete classes that implement abstract classes won't compile.

  • Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.

  • Member variables are not mirrored and no means for accessing them is provided. This limitation will be removed in a future version. In the meantime, you may add getter/setter functions to Cangjie classes manually as a workaround.

  • Mirroring of classes that implement interfaces other than Any is not currently supported in the sense that explicit interface implementation information gets lost during mirror generation.

  • Mirroring of generic classes is supported via monomorphization. See Generics for more information.

  • Mirroring of classes/interfaces that contain public member properties, member operator functions, or primary constructors is not supported. Such entities are simply not mirrored without a warning.

  • For member functions and constructors, the only universally supported parameter types are Bool and numeric types. For member functions, the only universally supported return value types are Bool, numeric types and Unit. Specifically for member functions and constructors of non-open classes, structs, enums, and non-open classes are also supportted as parameter and return value types, provided that they are defined in the same package. Member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored.

  • Inheritance relationships between interfaces are not preserved during mirror generation.

  • Interface implementation information is not propagated to Objective-C during mirror generation (notice how in the above example the @interface directive in Singleton.h does not end with <Valuable>).

  • A mirrored class may have direct and/or interface extensions, but they are ignored during mirror generation.

Enums

A Cangjie enum is mirrored into an Objective-C class attributed with objc_subclassing_restricted, with static methods matching the enum constructors (factory methods) and without a public constructor.

public enum TimeUnit {
    | Year(Int)
    | Month(Int)
    | Year
    | Month
}
__attribute__((objc_subclassing_restricted))
@interface TimeUnit : NSObject
// Glue code
+ (TimeUnit*)Year:(int64_t)p1;
+ (TimeUnit*)Month:(int64_t)p1;
+ (TimeUnit*)Year;
+ (TimeUnit*)Month;
// Glue code
@end

Enum constructors are mirrored into static factory methods with the same names and matching types and numbers of parameters; names of parameters are synthesized (p1, p2, ...).

Only the public enum members are mirrored.

Member functions are mirrored into methods with the respective mirror types substituted for parameter types and return value type. Mirrors of member functions returning Unit are mirrored into void methods. static member functions are mirrored into class methods (prefixed with "+"), instance member functions -- into instance methods (prefixed with "-").

Member properties of supported types are mirrored into @property declarations with the same name and with type that mirrors the Cangjie property type, attributed with readonly. The getter is mirrored into a parameterless method with the same name as the property and return type that is the mirror of the Cangjie property type. NOTICE: Setters are currently not mirrored, so mirrors of mut member properties are also readonly.

The current version imposes a number of limitations on the enum that can be mirrored:

  • Function overloading is not supported: mirrors of overloaded member functions are generated with the same name, which results in a name clash.

  • Mirrored enums may implement interfaces other than Any, but the "implements" relationship is not propagated to Objecttive-C: th generated @interface directive does not bear the names of mirrors of implemented interfaces in angle brackets <> after the class name.

  • Mirroring of generic enums is not supported yet; will be supported through monomorphization. See Generics for details.

  • For member functions and constructors, the only supported parameter types are Bool and numeric types. For member functions, the only universally supported return value types are Bool, numeric types and Unit. Member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored.

  • Mirroring of enums that contain member operator functions is not supported. An attempt to mirror an enum containing such an entity currently results in the generation of invalid Objectove-C code.

  • Enum member functions and constructors with unsupported parameter types, as well as member functions with unsupported return types, are not mirrored and no warning is issued.

  • A mirrored enum may have direct and/or interface extensions, but they are ignored during mirror generation.

Recursively defined enums are supported:

public enum Peano {
    | Z
    | S(Peano)

    public func toInt(): Int {
        match (this) {
            case Z => 0
            case S(x) => 1 + x.toInt()
        }
    }
}

Generics {#cangjie-generics}

Objective-C and Cangjie generics are fundamentally different, so the latter cannot be mirrored into the former. However, specific instantiations of parameterized Cangjie types, such as G<Int64>, are concrete types, and therefore can be mirrored into non-generic Objective-C types. Such types are also called monomorphized generics.

NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments. Mirroring of member functions that have their own type arguments is not supported either.

Use the respective configuration file of the cjc compiler to specify which instantiations of which types to mirror. Refer to Generics Instantiations for details.

Example:

When compiling the following Cangjie class definition:

public class G<T> {
    private let _t: T
    public init(t: T) {
        _t = t
    }
    public func get(): T {
        _t
    }
}

with the following lines in the respective section of the configuration file:

       .  .  .
    generic_object_configuration  = [
        { name = "G", type_arguments = ["Bool", "Int"] },
    ]
       .  .  .

the compiler will generate Objective-C mirror classes for G<Bool> and G<Int>, named respectively GBool and GInt.

Refer to the Cangjie Mirror Generation Reference section for complete instructions.

null Handling {#cangjie-null-handling}

As Cangjie has no null type, passing nil as a parameter to a mirrored Cangjie method results in a run-time exception in glue code:

public class Node {
    private let next: ?Node
    public init() {
        next = None
    }
    public init(next: Node) { /* Pure Cangjie */
        this.next = Some(next)
    }
}
__attribute__((objc_subclassing_restricted))
@interface Node
/* glue code */
- (id)init;
- (id)init:(Node *)next;
/* glue code */
@end
   .  .  .
     Node *list = [[Node alloc] init:nil];   /* NullPointerException */

Conversely, there is currently no way to return nil from a public member function of an Objective-C-compatible type.

NOTICE: The current version supports automatic Option<T> (un)wrapping with nil mapped to None, but only for Objective-C pointers to classes and protocols mirrored to Cangjie, not the other way round. See nil Handling for details.

Cangjie Mirror Generation Reference

There is no standalone mirror generator facilitating the exposure of Cangjie types to Java and Objective-C code in the Cangjie SDK. The cjc compiler itself is capable of generating Java/Objective-C mirror type definitions for Cangjie types right when it compiles the Cangjie source code of those types.

Command-line Syntax {#cjc-command-line-syntax}

The following additional cjc options enable and control the generation of mirrors for Cangjie types during compilation:

--experimental    (mandatory)

This option must be specified as of the current version, since the whole interop feature is still in development, but will not be necessary in the future.

--enable-interop-cjmapping=Java or
--enable-interop-cjmapping=ObjC        (mandatory)

Enables the generation of mirror type definitions for Cangjie types respectively in the form of Java or Objective-C source code files.

--output-interop-cjmapping-dir pathname     (optional)

The pathname must point to a directory into which the cjc compiler shall place its output Java or Objective-C source code files containing mirror type definitions. If pathname does not point to an existing file system location, cjc attempts to create a directory there. If pathname points to something other than a directory, cjc terminates with an error message.

The default value of pathname is either ./java-gen or ./objc-gen, depending on whether --enable-interop-cjmapping is set respectively to "Java" or "ObjC".

--import-interop-cj-package-config-path pathname     (optional)

The pathname must point to a configuration file that defines which Cangjie types are exposed to Java or Objective-C as mirror types. See Cangjie Mirror Generation Configuration for details.

Cangjie Mirror Generation Configuration

A Cangjie mirror generation configuration file is a plain text file in the TOML syntax. It specifies:

  • Non-generic Cangjie types to mirror
  • Specific instances of generic Cangjie types to mirror

Defaults

The [default] table defines the default settings for packages for which either no package-specific settings are provided at all, or those settings don't override the defaults.

Properties:

APIStrategy    (optional)

A string defining whether all public entities shall be mirrored by default or not. Valid values:

  • "Full" - all public entities shall be mirrored by default
  • "None" - no public entities shall be mirrored by default

GenericTypeStrategy    (optional)

A string defining whether generic public entities shall be mirrored by default or not. Valid values:

  • "Partial" - instantiations explicitly listed in generic_object_configuration shall be mirrored
  • "None" - no generic public entities shall be mirrored by default

Packages

Each entry in the [[packages]] array specifies the name of a separate source Cangjie package, and, optionally:

  • A filter that defines which types from that package to include in, or exclude from, the set of mirrors
  • API mirroring strategy (if other than the default)
  • Generic type mirroring strategy (if other than the default)
  • Specific generic type instantiations to mirror

Properties:

name    (mandatory)

A string containing the name of the source Cangjie package to which the settings in this [[packages]] array entry apply.

Example:

name = "com.example.VectorMath"

APIStrategy    (optional)

A string defining whether all public entities of the given package shall be mirrored by default or not. Valid values:

  • "Full" - all public entities shall be mirrored by default
  • "None" - no public entities shall be mirrored by default

If this property is absent, the default value is used.

included_apis    (optional)

An array of strings each containing the name of a public entity that needs to be mirrored. If the array contains the qualified name of a type member, the type itself is also mirrored. Either included_apis or excluded_apis may be present in a given [[packages]] array entry, but not both.

Example:

included_apis = [
   "Vector.product", // Vector also exposed
   "HiddenV"
]

excluded_apis    (optional)

An array of strings each containing the name of a public Cangjie entity defined in the given package that has to be not mirrored. Either included_apis or excluded_apis may be present in a given [[packages]] array entry, but not both.

Example:

excluded_apis = [
   "Vector.product", // Even if Vector is exposed, product is not
   "HiddenV"
]

GenericTypeStrategy    (optional)

A string defining whether generic public entities defined in this package shall be mirrored by default or not. Valid values:

  • "Partial" - instantiations explicitly listed in generic_object_configuration shall be mirrored
  • "None" - no generic public entities shall be mirrored by default

If this property is absent, the default value is used.

generic_object_configuration    (optional)

A list of tables defining the specific instantiations of generic entities to be mirrored. See Generics Instantiations for details.

Generics Instantiations

The generic_object_configuration property of a [[packages]] array entry defines which specific instantiations of generic Cangjie entities will be mirrored.

The value of the property is a (possibly empty) array of tables. Each entry of the array has the following two mandatory properties:

name

A string containing the name of a public generic Cangjie type or global function defined in the current package.

type_arguments

An array of strings, each containing valid type argument(s) for the generic type or global function the name of which is specified in the name property.

Simply put, "valid" means that the type/function can be instantiated with the given string between the angle brackets < >.

Consider the following Cangjie class:

public class G<T> {
    public func f(t: T): Unit {}
    public func f(b: Bool): Unit {}
}

G<Bool> cannot be instantiated, so

generic_object_configuration  = [
    { name = "G", type_arguments = ["Bool"] }
]

would trigger a compiler error.

NOTE: The current version only supports mirroring of generic types instantiated with primitive types used as type arguments.

Example:

Given the following Cangjie definitions

public class G<T> { . . . }
public func f<T>(): Unit { . . . }
public struct S<T,U> { . . . }

the property

generic_object_configuration  = [
    { name = "G", type_arguments = ["Int32"] },
    { name = "f", type_arguments = ["Float32", "Float64"] }
    { name = "S", type_arguments = ["Int, Bool"] }
]

instructs the cjc compiler to generate the following generics instantiations and mirrors for them:

G<Int32>
f<Float32>
f<Float64>
S<Int, Bool>

naming the mirrors GInt32, fFloat32, fFloat64, and SIntBool respectively.

Using Objective-C in Cangjie

In the first interoperation scenario, described in Using Cangjie in Objective-C, user-defined Cangjie types are mapped to Objective-C types that behave just like any conventional Objective-C classes and protocols: classes can be extended, protocols implemented, instances created and passed around freely, and so on.

The Cangjie SDK for iOS also supports similar features for the reverse scenario, in which Objective-C classes and protocols are mapped to Cangjie classes and interfaces and can be extended, implemented, instantiated. However, the exact set of features and limitations is different, as well as tooling: mirror type and function declarations for Objective-C entities are generated by a standalone tool, not the cjc compiler.

The process of enabling this second scenario is therefore more involved. Here are the steps you need to take:

  1. Design the interoperability layer in terms of Objective-C classes and methods.

    developer → interop layer design (Objective-C pseudo-code)

  2. Generate mirror declarations for any Objective-C classes and protocols used in the design of the interoperability layer.

    .h → mirror generator → .cj (mirrors)

  3. Write the classes constituting the interoperability layer in Cangjie. Use the mirrored Objective-C types as needed: create instances, call member functions, etc.

    interop layer design + .cj (mirrors) → developer → .cj (interop layer)

  4. Compile the interoperability layer classes (interop classes for short) and mirror types together with cjc. cjc will generate:

    • the necessary glue code for all uses of the mirror types.
    • the actual Objective-C source for the Objective-C part of the interoperability layer (so called Objective-C wrappers for interop classes)

    .cj (mirrors + interop layer) → cjc.dylib + .h/.m (interop layer)

  5. Add to your iOS project:

    • .h and .m files generated by cjc
    • .dylib file generated by cjc
    • runtime libraries (.dylib) from the Cangjie SDK

    iOS project + .h, .m, .dylib → iOS toolchain → iOS application

  6. Now you may start using the interop class wrappers in the Objective-C code of your application, effectively calling Cangjie code from Objective-C.

IMPORTANT: The current version of the Cangjie SDK for iOS also requires downloading and integrating into the XCode project the Cangjie.h header file from the Cangjie open source repository:

https://gitcode.com/Cangjie/cangjie_runtime/blob/dev/runtime/src/Cangjie.h

Initial Interop Class Creation Workflow

Setup

  1. Install the llvm@16 Homebrew formula:

    brew install llvm@16
    
  2. Add the llvm@16/lib/ subdirectory to the DYLD_LIBRARY_PATH environment variable:

    export DYLD_LIBRARY_PATH=/opt/homebrew/opt/llvm@16/lib:$DYLD_LIBRARY_PATH
    
  3. Run the envsetup.sh script from the Cangjie SDK.

  4. Verify the installation by running the following command in the terminal:

    ObjCInteropGen
    

    If mirror generator usage instructions start to appear, you now have everything you need to begin using the iOS interoperability features of the Cangjie SDK.

Step 1: Design the Interoperability Layer {#step-1}

On this step, you design the API of one or more interop classes from the Objective-C code perspective, i.e. you need to decide, for each interop class:

  • What Objective-C class it will extend, e.g. NSObject;
  • Which Objective-C protocols it will implement, if any; and
  • What public/protected methods and/or constructors it will have. You only need to know the types of parameters and, for methods, their names and return value types; the implementations you will write in Cangjie.

See also Features and Limitations of Interop Classes.

For the Objective-C part of the application, the interoperability layer is indistinguishable from a set of regular Objective-C classes. So you can design it in the form of Objective-C pseudo-code. Write down the specifications and @interface definitions of one or more Objective-C classes. Note: You do not need to write the respective @implementation parts as you would re-write them in Cangjie at a later step anyway.

Supported parameter types: any mapped Objective-C types.

Supported return types: any mapped Objective-C type or void.

Supported inheritance: interop classes may only extend mirrored Objective-C classes (not other interop classes!), and may only implement interfaces that mirror Objective-C protocols.

Limitations:

  • Variable arity (varargs) methods and constructors are not supported.

  • Neither an interop class nor its individual non-private member functions may be generic.

  • Generic Objective-C types are erased before mirror generation; any type variables are replaced with the respective upper bounds.

End-to-end Example:

For the sake of simplicity, suppose that in your iOS application there is already a class M with a single parameterless void instance method foo():

// M.h
#import <Foundation/Foundation.h>

@interface M : NSObject
- (void)foo;
@end
// M.m
#import "M.h"

@implementation M
- (void) foo {
    printf("Hello from ObjC M.foo()\n");
}
@end

and you want to override that method foo() with a Cangjie implementation in a subclass of M called A.

Your interop class design would then look like this:

#import "M.h"
@interface A : M
- (void)foo;
@end

Step 2: Generate Mirror Type Declarations {#step-2}

Now, you need mirror type declarations for all Objective-C types on which your interop classes depend: their supertypes, types of member properties and local variables, types of method parameters/return values, and, if any of the foregoing are array types, their element types.

Before you proceed further, build your iOS application normally without changing anything in it, to ensure that the mirror generator takes a complete and consistent set of Objective-C headers as input.

Then write an appropriate mirror generator configuration file and run the mirror generator:

ObjCInteropGen --mode=normal <config-file>

where <config-file> is the pathname of the configuration file.

End-to-end Example (continued):

The only immediate dependency of your class A is its superclass M, so the configuration file is pretty simple:

# A.toml
# Place the mirror of M and any dependencies it may have in the 'cjworld' package:
[[packages]]
filters = { include = ["M", "NS.+"] }
package-name = "cjworld"

# Write the output files with mirror type definitions to the current directory:
[output-roots.default]
path = "."

# Specify the pathname of the input header:
[sources.all]
paths = ["M.h"]

[sources-mixins.default]
sources = [".*"]
arguments-append = [
    # Uncomment the following line if you get "unknown type name" errors
    # "-DTARGET_OS_IPHONE=1",

    # Edit the pathnames below to match the locations of Objective-C headers on your system:
    "-F", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks",
    "-isystem", "/Library/Developer/CommandLineTools/usr/lib/clang/17/include",
    "-isystem", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include"
]

Mirror generator command line:

ObjCInteropGen --mode=normal A.toml

The above command will generate files cjworld/M.cj with a mirror type declaration for the class M and a bunch of cjworld/NS*.cj files with mirror type declarations for the Foundation framework classes and protocols on which M depends.

Common Misconfiguration Issues

The mirror generator is unable to find the standard header files, such as stdarg.h or stdbool.h.

Example error messsage:

..../CoreFoundation.h:19:10: error: 'stdarg.h' file not found

This usially means that the pathnames in arguments-append array of the [sources-mixins] table are not correct. Double check that they match the locations of the header files on your system.

The mirror generator reports "Unknown type name 'NSUInteger'" errors or similar.

Example error messsage:

.../NSObjCRuntime.h:626:74: error: unknown type name 'NSUInteger'

On some systems, you need to pass an additional argument to Clang, either "-DTARGET_OS_IPHONE=1" or "-DTARGET_OS_OSX=1".

Add it to the arguments-append array of the [sources-mixins] table. In the above example, it is present but commented out:

   .  .  .
arguments-append = [
    # Uncomment the following line if you get "unknown type name" errors
    # "-DTARGET_OS_IPHONE=1",
   .  .  .

Step 3: Write the Interop Classes {#step-3}

For each Objective-C class skeleton that you included in your interop layer design on Step 1, write a matching Cangjie class as follows:

  • Use the appropriate package and class names (the Objective-C wrapper class that cjc will generate for you will have the same fully qualified name).

  • Import interoplib.objc.*.

  • Import the mirror types, if you've generated any on Step 2. Do not import all generated dependencies, only the types you actually need.

  • Annotate the interop class with @ObjCImpl.

  • Make the interop class inherit the respective open mirror class, or NSObject if it has no explicit supertype in your design.

  • Annotate public class members and constructors with @ForeignName["foreign-name"], where foreign-name is the desired Objective-C name for that member or constructor, according to the following rules:

    1. Do not annotate member functions that override member functions of mirror supertypes. The presence of a @ForeignName annotation on such a member function of an interop class triggers a compile-time error.

      The Objective-C names of such overriding functions must match the names of the overridden Objective-C methods. The latter are propagated from Objective-C to Cangjie automatically via @ForeignName annotations in mirror type declarations (see Classes and Protocols for details). The cjc compiler picks those annotations up.

    2. Do annotate constructors and non-overriding member functions that have two or more parameters. The absence of a @ForeignName annotation on such a member function or constructor triggers a compile-time error.

      The compiler is unable to morph the original Cangjie name of a member function or constructor with two or more parameters into a valid Objective-C name automatically. Such names essentially consist of the respective number of fragments separated with the colon character :.

    3. Overloaded member functions and constructors must be given distinctive Objective-C names using @ForeignName, as there is no method overloading in Objective-C. Exacly one of such overloaded entities can retain its original Cangjie name, provided it has at most one parameter.

    4. Annotating the remaining members and constructors with @ForeignName is optional. In the absence of a @ForeignName annotation, the original Cangjie name of such a member is used as the Objective-C name of the corresponding entity, with colon : appended to names of single-parameter methods, whereas the init-method matching the sole public constructor with one or zero parameters is called respectively init: or init.

    NOTE: The current version of the cjc compiler does not fully validate foreign-name. In particular, it does not check that the number of colon : separators mathes the number of method or constructor parameters.

  • Use the following Objective-C-to-Cangjie type mapping (T' is either the matching value type or the respective mirror type):

    Objective-C Cangjie Remark
    void Unit
    BOOL Bool
    signed char Int8
    short Int16
    int Int32
    long Int64
    long long Int64
    unsigned char UInt8
    unsigned short UInt16
    unsigned int UInt32
    unsigned long UInt64
    unsigned long long UInt64
    float Float32
    double Float64
    struct @C struct' (*)
    enum w/base type T T'
    id ObjCId (†)
    Objective-C Cangjie If T is... Remark
    T* CPointer<T'> ... a primitive or structure type
    T* CPointer<U'> ... an enum type and U is its base type
    T* CFunc<T> ... a pure C function type
    T* T' ... a class type (†)

    (*) The Objective-C structure must not contain fields of types that are not CType-compatible. See Structs for details.

    (†) Use ?<T'> (Option<T'>) types for parameters, return values, and local variables of mirror types and interop classe types that may receive/hold the Objective-C nil value.

    See Objective-C to Cangjie Mapping for details.

Supported features:

Member functions of interop classes:

  • Can override/redefine open instance/static member functions of their mirrored superclasses, implement member functions of their mirrored superinterfaces, and be newly introduced member functions.

Constructors and member functions of interop classes:

  • Can use any Cangjie language features in the body when working solely with (values of) regular Cangjie types.

  • Can use conventional Cangjie syntax to:

    • instantiate objects of interop classes and mirrored Objective-C classes
    • call static and instance methods of interop classes and mirrored Objective-C types (that includes using super to call Objective-C superconstructors and superclass methods)
    • access their own member properties and non-private member properties of other interop classes and mirrored Objective-C types

Limitations:

  • Interop classes may implement interfaces that are mirrored Objective-C protocols, but not regular Cangjie interfaces.

  • Interop classes may not be declared as open or abstract and may not be extended using extend.

  • Non-private member functions and constructors of interop classes:

    • May only use the Objective-C-mapped types listed above as parameter and return value types. (As a consequence, variable-length arguments are not supported as that requires using the Cangjie Array<T> type, which has no Objective-C mapping.)

    • May not have named parameters.

    • May not have type parameters, i.e. be generic.

  • Non-private member properties may only have the Objective-C-mapped types listed above.

  • Generic Objective-C types are erased; mirror types have upper bounds in place of the respective type parameters.

  • IMPORTANT: Instances of mirror types and interop classes (in other words, values of Objective-C reference types) must not escape to global or static Cangjie variables or any data structures that persist between calls.

End-to-end Example (continued):

To continue the above example, the interop class A would look like this:

package cjworld // Same package name

import interoplib.objc.* // Always required

@ObjCImpl
public class A <: M {
    public init() {
        super()
    }

    @ForeignName["foo"]
    public open override func foo(): Unit {
        println("Hello from overridden A.foo()")
    }
}

Step 4: Compile the Interop Classes {#step-4}

Command line:

cjc --target=arm64-apple-ios-simulator \
    --sysroot=$(xcrun --show-sdk-path --sdk iphonesimulator) \
    --output-type=dylib \
    --int-overflow=wrapping \
    <source-files> \
    -o <target-file> \
    --link-options "-undefined dynamic_lookup"

where

<source-files> are the source code files of the interop classes and mirror type declarations.

<target-file> is the desired name of the output .dylib file with compiled Cangjie code for the interop classes, such as libcjworld.dylib.

The compiler will also generate Objective-C source files (.h and .m) for the Objective-C wrappers or the interop classes, placing them in the ./objc-gen/ subdirectory.

The .dylib file has to be signed:

xcrun codesign --sign - <dylib-file>

End-to-end example (continued):

Compile the interop class:

cd cjworld

cjc --target=arm64-apple-ios-simulator \
    --sysroot=$(xcrun --show-sdk-path --sdk iphonesimulator) \
    --output-type=dylib \
    --int-overflow=wrapping \
    *.cj \
    -o libcjworld.dylib \
    --link-options "-undefined dynamic_lookup"

The compiler will produce three files: ./libcjworld.dylib, ./objc-gen/A.h and ./objc-gen/A.m.

Sign the generated dynamic library:

xcrun codesign --sign - libcjworld.dylib

Step 5: Put It All Together {#step-5}

  • Create another subdirectory in your project and copy all dynamic libraries from the $CANGJIE_HOME/runtime/lib/ios_simulator_aarch64_cjnative/ directory into it.

  • Add those all dynamic libraries and the .dylib file generated on the previous step to your Xcode project as dependencies (BuildPhases - in both “Copy Files” and “Link Binary With Libraries” lists).

  • Move the .h and .m files generated on the previous step to project root.

Then re-build the project.

End-to-end Example (continued):

Create a new subdirectory in the root directory of your project and copy the dynamic libraries constituting the Cangjie Runtime for iOS into it:

cd ..
mkdir -p CJRuntimeDylibs
cp $CANGJIE_HOME/runtime/lib/ios_simulator_aarch64_cjnative/*.dylib CJRuntimeDylibs/

Add those dynamic libraries and ./cjworld/libcjworld.dylib to your Xcode project as dependencies (BuildPhases - in both “Copy Files” and “Link Binary With Libraries” lists).

Move the .h and .m files generated by cjc to project root:

mv cjworld/objc-gen/*.h ./
mv cjworld/objc-gen/*.m ./

Rebuild the Xcode project.

Calling Objective-C from Cangjie

Once you have designed, built and integrated the interoperability layer as described in the previous section, you can add code that uses Objective-C types to the member functions of your interop classes. The type mapping is the same:

Add the code invoking a Cangjie function from Objective-C via the respective method of an interop class wrapper and re-build your iOS project again.

End-to-end Example (continued):

Insert an instantiation of A and a call of the method foo() of that instance into your Objective-C code:

       .  .  .
#import "M.h"
#import "A.h"
       .  .  .
    M* a = [[A alloc] init];
    [a foo];
       .  .  .

Rebuild the Xcode project and run your app on the iOS simulator.

Subsequent Enhancements

Following the above process should have helped you develop a good understanding of how to add more classes to the interoperability layer and/or enable the use of more Objective-C classes in its Cangjie code. Below please find a brief summary with links to the respective subsections:

Step 1: Add more interop classes to your design and/or enhance the existing ones with new methods and/or properties as you wish.

Step 2: If any additional, not yet mirrored Objective-C reference types will now get involved, build the current version of the application to ensure consistency, edit the mirror generator configuration file appropriately, and then (re-)generate all mirror type declarations.

Step 3: Implement the newly added interop classes, if any, and/or change the code of existing ones, using the mirrored Objective-C types as necessary.

Step 4: Compile all interop classes together.

Step 5: Copy the .h, .m and .dylib files that cjc has generated on the previous step to their respective locations in the project and re-build it.

Now you can start using the newly added interop classes and/or methods in the Objective-C part of your application.

Features and Limitations of Interop Classes

  1. An interop class must be a direct subclass of a mirror class.

  2. An interop class may implement one or more mirror interfaces, but never a conventional Cangjie interface. Conversely, a conventional Cangjie type may not implement or inherit an interface mirroring an Objective-C protocol.

  3. An interop class may not be declared as open or abstract and may not be extended using extend.

  4. An interop class may introduce new instance fields of any Cangjie type and override the member functions of its mirrored superclass.

  5. The constructors of an interop class may call superclass constructors using super(), but have the same limitation on instance member function calls as the conventional Cangjie constructors, as well as the requirement to initialize all newly introduced member variables.

  6. The instance member functions of an interop class may call instance member functions which that interop class has inherited from its mirrored superclass, and/or use super. to call the member functions of that superclass that the interop class overrides.

  7. The signatures of constructors and member functions of all mirror types and interop classes can only use types that are (a) mirror types or interop classes themselves, or (b) are 100% analogous to Objective-C primitive types. See the Type Mapping table in Step 3 above and the Chapter Objective-C to Cangjie Mapping.

Objective-C to Cangjie Mapping

The current version of the mirror generator does the following Objective-C → Cangjie conversions.

General Considerations

The Objective-C mirror generator relies on Clang for parsing Objective-C sources, invoking it with the -fobjc-arc option.

Declarations marked with the unavailable attribute are ignored.

Global, file-level function and variable declarations are also ignored.

The order of declarations in the output Cangjie source code is the same as the order of the respective definitions in the input Objective-C source code, with the exception of nested type definitions.

Names

The original Objective-C identifiers are preserved, with the following exceptions:

  • Objective-C identifiers that clash with Cangjie keywords, such as catch, false, or UInt32, are enclosed in backticks ``.

  • Name conflicts between Objective-C classes and protocols are resolved by adding the suffix Protocol to the name of the protocol.

  • Name conflicts between instance and static methods are resolved, if possible, by adding either Instance or Static suffix to the highest (closest to the root) class/protocol where the conflict is encountered. If the conflicting instance/static methods are first introduced in the same class/protocol, the static method is renamed. For example:

    @interface A
    +(void)foo;
    @end
    
    @interface B : A
    -(void)foo;
    +(void)bar;
    -(void)bar;
    @end
    

    will be converted to

    @ObjCMirror
    public open class A <: ObjCId {
        public static func foo()
    }
    
    @ObjCMirror
    public open class B <: A {
        public open func fooInstance()
        public static func barStatic()
        public open func bar()
    }
    
  • Conflicts between init methods having different names, but the same number and types of parameters are not resolved. See Classes for details.

Type Aliases

C typedef declarations are converted to public Cangjie type aliases.

Primitive Types

Objective-C primitive types map to the respective Cangjie value types. C types with platform-specific sizes map to Cangjie in accordance with their actual sizes on the host platform, i.e. the platform on which the mirror generator runs. For example, on macOS it can be the following:

Objective-C Cangjie
void Unit
BOOL Bool
signed char Int8
short Int16
int Int32
long Int64
long long Int64
unsigned char UInt8
unsigned short UInt16
unsigned int UInt32
unsigned long UInt64
unsigned long long UInt64
float Float32
double Float64

Structs

A C struct is converted to a public Cangjie struct marked as @C if it is CType-compatible, that is, contains fields of CType-compatible types only. Other scenarios, e.g. the original Objective-C structure containing pointers to objects, are not currently supported.

Nested structures are converted to top-level Cangjie structs.

Incomplete structure declarations are converted to empty structs (without any members).

Fields of Objective-C structures are converted to public member variables of the respective Cangjie structs. The static modifier is maintained.

Bit fields are not supported by the Cangjie language, so their widths are ignored and a warning is issued.

The Cangjie language, unlike Objective-C, requires all member variables of structs (even @C ones) to be initialized. Therefore, mirrored structures contain explicit zero initializers for all fields. For example:

struct A {
    int x;
    double y;
    BOOL z;
    struct A *w;
};

is mirrored to

@C
public struct A {
    public var x: Int32 = 0
    public var y: Float64 = 0.0
    public var z: Bool = false
    public var w: CPointer<A> = CPointer<A>()
}

Enumerations

Named C enumeration declarations are converted to abstract sealed Cangjie classes (effectively, namespaces). Enumerators are converted to public static constants initialized with their values. Their type corresponds to the explicitly specified underlying type for the enumeration, if any, otherwise it is Int32.

Unions

The Cangjie language does not support C union types. They are converted to structs with sequential fields and a warning is issued.

Classes and Protocols

Objective-C classes and protocols are mirrored respectively to Cangjie classes and interfaces. All such mirror classes and interfaces explicitly implement the built-in ObjCId mirror interface (see Built-in Types).

In methods with a variable number of parameters, , ... is ignored.

Full Objective-C method names (selectors) decorated with : are not valid Cangjie identifiers. Such names are mangled as follows:

  • Each letter that immediately follows a : character, if any, is capitalized.
  • All : characters are removed.

The original selector is specified in the @ForeignName attribute on the Cangjie side.

Example:

@interface A
- (void)foo;
- (void)foo:(int)i;
- (void)foo:(int)i bar:(int) j;
- (void)foo:(int)i bar:(int) j baz:(int) k;
@end

is mirrored to

@ObjCMirror
public open class A {
    public open func foo(): Unit
    @ForeignName["foo:"] public open func foo(i: Int32): Unit
    @ForeignName["foo:bar:"] public open func fooBar(i: Int32, j: Int32): Unit
    @ForeignName["foo:bar:baz:"] public open func fooBarBaz(i: Int32, j: Int32, k: Int32): Unit
}

In Cangjie, a property that overrides another property must have the same type. Also, the getter/setter functions of Cangjie properties may not be named arbitrarily. Hence any properties declared in Objective-C classes are only mirrored into Cangjie properties when possible. In all other cases, their getter/setter methods are mirrored as ordinary instance member functions. This includes properties declared within @protocol directives.

Instance variables are mirrored into instance member variables.

instancetype is converted to the name of the current declaration.

Classes

Objective-C @interface class declarations are converted to Cangjie classes marked as @ObjCMirror public open.

Objective-C @interface category and extension declarations are merged with the respective Cangjie class declarations.

Objective-C @implementation declarations are ignored.

Forward class declarations (@class directives) are mirrored into empty classes (without any members).

Methods identified as init methods as described in the Method families section of the Clang documentation on Objective-C ARC are mirrored into Cangjie constructors, with two limitations:

  • If two or more init methods of a class differ only by name, i.e. have the same number and types of parameters, the mirror generator comments out the respective Cangjie constructor declarations and issues a conflict warning.

  • init methods are inherited like any other methods, whereas constructors in Cangjie are not inherited. The constructors that mirror the init methods of superclasses are therefore unavailable at the point of a mirror class or interop class instantiation.

Other methods of Objective-C classes are mirrored into public open member functions of the respective Cangjie mirror types. Mirrors of class methods (those with the "+" prefix) are modified with static.

Protocols

Objective-C @protocol directives are mirrored into Cangjie interfaces marked as @ObjCMirror public. However, the cjc compiler does not support such interfaces yet.

Forward protocol declarations are mirrored into empty interfaces (without any members).

Methods of Objective-C protocols are mirrored into public open member functions of the respective Cangjie interfaces. The static modifier is maintained.

The @optional directive is ignored.

Pointers

The modifiers const, volatile, and restrict are ignored.

Pointers to C primitives and their type aliases are converted to CPointer with the respective type parameters.

A pointer to a C enumeration is converted to a CPointer to its underlying type, with the actual enumeration name specified in a comment.

Pointers to Structures

A pointer to a C structure T is converted to CPointer<T'>, where T' is the mirror type for T, if the structure is CType-compatible. Otherwise, they are converted to ObjCPointer<T'> (provisional name), which is a type that should be implemented in the interop library.

Pointers to Functions

Pointers to C functions are converted to either CFunc (if the function parameter types and return value are all CType-compatible) or to ObjCFunc<F> (provisional name) otherwise. The latter is a type implemented in the interop library.

Pointers to Blocks

Pointers to Objective-C blocks are converted to ObjCBlock<F> (provisional name), which is a type implemented in the interop library. The type argument F of ObjCBlock<F> must be the respective Cangjie function type.

Pointers to Class Instances

Pointers to Objective-C class instances are mirrored as follows:

  • Pointers to class instances (SomeClass*) are mirrored to the respective mirror classes.

  • The mirror of the Objective-C type id narrowed with a single protocol (for example, id<NSCopying>), is the mirror interface for that protocol.

  • id narrowed with several protocols, e.g. id<NSCopying, NSSecureCoding>, is mirrored to a pure ObjCId (the interface that all @ObjCMirror classes and interfaces respectively implement and inherit), with the actual protocol list specified in a comment.

  • If a generic type parameter is used inside a generic template with a narrowing protocol specified, it is converted to a reference to that protocol, with the name of the type parameter specified in a comment.

Generics

Parameterized Objective-C classes are mirrored as regular, non-generic Cangjie class declarations. The original lightweight generics syntax such as "<T>" or "<Foo>" is retained in comments; type parameter usages are replaced with ObjCId.

Example:

@interface G<T> : NSObject
- (void)f:(T)t;
@end

is mirrored to

@ObjCMirror
public open class G/*<T>*/ <: NSObject {
    @ForeignName["f:"] public open func f(t: ?ObjCId /*T*/): Unit
}

However. type constraints are lost as of the current version, i.e.

@interface G<T: SomeType*> : NSObject
- (void)f:(T)t;
@end

is now mirrored exactly as the first sample above.

Built-in Types

The mirror generator assumes that the following user-available Cangjie types are implemented in the interop library. As of the current version, however, they are not yet implemented, and their names may change.

Objective-C Cangjie (*) Description
id ObjCId Interface that should be implemented by all @ObjCMirror classes and interfaces. Bound to Objective-C id.
SEL SEL? Class bound to Objective-C SEL.
Class Class? Class bound to Objective-C Class.
Protocol Protocol? Class bound to Objective-C Protocol.
pointer type ObjCPointer<T>? Structure bound to Objective-C pointers of two kinds: with arity more than one and when T is a not CType-compatible structure.
non-C function type ObjCFunc<F>? Class implementing CFunc for functions that are not CType-compatible. F is a Cangjie function type.
block type ObjCBlock<F>? Structure implementing an Objective-C block. F is a Cangjie function type.
__builtin_va_list Helper type alias for CPointer<Unit>, technically needed in the current generator implementation. May and should be dropped in the future.

(*) Names of Cangjie types are provisional.

Not (Yet) Implemented Features

Objective-C interoperability support in the Cangjie SDK is still in development. Certain features are not implemented yet, others may change in the first production release, and some cannot be (fully) implemented due to principal differences between the two languages.

  • The C language features that alter the default size, packing, padding, and/or alignment of data are very ABI-specific. The Cangjie language does not support such low-level features, and bit fields in particular. The mirror generator therefore ignores bit field width specifiers and issues warnings.

  • The Cangjie language does not provide support for C union types, so they are mirrored as structs and warnings are issued.

  • Structures with fields of types that are not CType-compatible are not supported.

  • Anonymous C enumeration declarations are ignored. Named ones are mirrored to abstract sealed Cangjie classes. See Enumerations for details.

  • In methods with a variable number of parameters, ... is ignored.

  • The @optional directive is ignored.

  • Properties are mirrored as properties if possible, otherwise their getter and setter methods are mirrored as if they were regular instance methods. See Classes and Protocols for details.

  • The modifiers const, volatile, and restrict are ignored.

  • init methods are mirrored into Cangjie constructors, which imposes two limitations:

    • Two constructors of a Cangjie class may not have the same number and types of parameters, whereas init method may differ by name, and

    • Constructors in Cangjie are not inherited.

      See Classes for details.

  • Parameterized Objective-C classes are mirrored as regular, non-generic Cangjie class declarations. See Generics for details.

  • Mirroring of @protocol declarations into Cangjie interfaces is not fully supported yet.

  • Pointers to functions with parameter/return types that are not CType-compatible are not supported.

  • Objective-C blocks are not supported.

  • The intrinsic helper functions for converting values of the Objective-C type NSString to/from values of the Cangjie type String are not implemented yet.

nil Handling

Cangjie has no concept of null references and hence no equivalent for the Objective-C nil value. If pointers to Objective-C classes and protocols were mirrored to Cangjie classes and interfaces, any such value passed over from Objective-C to Cangjie could lead to a segmentation fault. Conversely, there would be no way to pass a nil value from Cangjie to Objective-C either.

The Option<T> enum is therefore generally used to represent the values of those Objective-C types, with None standing for the nil value, and Some(r) representing a (non-null) reference value r. The cjc compiler recognizes Option<T> as an Objective-C compatible type if T is a mirror type or interop class and wraps/unwraps the values of T accordingly.

For instance, the following Objective-C @interface directive:

@interface MyContainer: NSObject
   .  .  .
- (void)addItem:(MyItem *)item withUuid:(NSString *)uuid;
- (MyItem *)itemWithUuid:(NSString *)uuid;
- (NSString *)uuidForItem:(MyItem *)item;
@property (copy) NSArray<MyItem *> *allItems;
@end

will be mirrored to (@ForeignName annotations omitted for brevity):

@ObjCMirror
open class MyContainer <: NSObject {
       .  .  .
    public open func addItemWithUuid(item: ?MyItem, uuid: ?NSString): Unit
    public open func itemWithUuid(uuid: ?NSString): ?MyItem
    public open func uuidForItem:(item: ?MyItem): ?NSString
    public mut prop allItems ?NSArray/*<MyItem>*/
}

The Option<T> wrapping ensures that the code won't break if a nil value sneaks into the Cangjie world from the Objective-C one, but it does that at the cost of performance and memory footprint. The other disadvantage of this approach is loss of variance. That being said, support for nullability annotations considerably reduces the impact of reference wrapping, at least when it comes to the use of iOS APIs in Cangjie code.

NOTE: This problem does not exist for the low-level C types that get mirrored to the conventional CPointer<T> type, as the latter provides explicit null check functions.

Loss of Variance

One limitation imposed by the Option<T> wrapping of Objective-C mirror types and interop classes is that such wrapped types follow the semantics of Cangjie in all other respects. In particular, Option<T> is invariant by its type parameter T: Option<U> is not a subtype of Option<T> if U is a subtype of T unless T and U are the same type. For mirror types that means that any overriding Objective-C method that relies on return type covariance may not be mirrored that way with Option<T> wrapping. The return value type of the mirror of such a method has to be propagated from the method that it overrides.

Example:

Suppose an Objective-C class Foo is a direct superclass of the class Bar:

@interface Foo : NSObject
@end

@interface Bar : Foo
@end

and the class C declares a method get that returns an instance of Foo:

@interface C : NSObject
- (Foo*) get;
@end

A subclasss of C may then override get with a more precise return type, Bar:

@interface D : C
- (Bar*) get;
@end

Without Option<T> wrapping, all those classes could be mirrored to:

@ObjCMirror
open class Foo <: NSObject {}

@ObjCMirror
open class Bar <: Foo {}

@ObjCMirror
open class C <: NSObject {
    open func get(): Foo
}

@ObjCMirror
open class D <: C {
    open func get(): Bar // Return type covariance in action
}

but if get() can possibly return nil, an application crash is inevitable.

Option<T> wrapping makes it safe, but the return types of all overriding methods have to be lowered to the return type of the original method:

@ObjCMirror
open class Foo <: NSObject {}

@ObjCMirror
open class Bar <: Foo {}

@ObjCMirror
open class C <: NSObject {
    open func get(): Option<Foo>
}

@ObjCMirror
open class D <: C {
    // open func get(): Option<Bar>  // Error, `Option<T>` is not covariant by T
    open func get(): Option<Foo> // OK, but the return type is lowered
}

Nullability annotations alleviate the problem partially.

Nullability Annotations

Nullability keywords were introduced to Objective-C with the release of XCode 6.3 back in the day, for better integration with the new iOS/OS X development language, Swift. The definitions of all iOS APIs were enhanced with nullability annotations to reduce the use of optionals in Swift code utilizing those APIs.

Objective-C Nullability Annotations

The keywords nullable and nonnull can be used for Objective-C properties, method result types and method parameter types. They mean that the given entity respectively may or may not hold or accept the nil value. The (very rare) null_unspecified annotation denotes the entities about which it is unknown whether their value may be nil.

In addition, any pointer type may be annotated with __nullable, __nonnull or __null_unspecified with the same semantics.

Finally, a property may also ne annotated with null_resettable, meaning that its getter will never return nil, but passing nil to its setter would reset the property to some default value.

For details, see Designating Nullability in Objective-C APIs

All uses of Objective-C reference types annotated as non-nullable are therefore exempt from Option<T> wrapping. In other words, the Objective-C mirror generator emits wrapped types only for properties, method result types and method parameter types that are not annotated with either nonull or _Nonnull in the original Objective-C code.

Suppose the example from the introduction to this section was enhanced with nullability annotations as follows:

@interface MyContainer: NSObject
   .  .  .
- (void)addItem:(nonnull MyItem *)item withUuid:(nonnull NSString *)uuid;
- (nullable MyItem *)itemWithUuid:(nonnull NSString *)uuid;
- (nullable NSString *)uuidForItem:(nonnull MyItem *)item;
@property (copy, nonnull) NSArray<MyItem *> *allItems;
@end

The mirror generator would then bypass Option<T> wrapping for the nonnull entities (@ForeignName annotations omitted for brevity):

@ObjCMirror
open class MyContainer <: NSObject {
       .  .  .
    public open fund addItemWithUuid(item: MyItem, uuid: NSString): Unit
    public open func itemWithUuid(uuid: NSString): ?MyItem
    public open func uuidForItem:(item: MyItem): ?NSString
    public open prop allItems: NSArray
}

NOTE: It is currently not possible to mirror null_resettable properties into Cangjie properties while propagating the semantics of that annotation, hence the latter is treated exactly as nullable.

If your Objective-C code that you want to access from Cangjie is not taking advantage of the nullability annotations, you may want to add at least the nonnull annotations as appropriate before you begin using the Cangjie SDK interoperability features described here. That may considerably reduce the use of Option<T> wrapping in the respective mirror types, making the code of interop classes cleaner and easier to read.

Objective-C Mirror Generator Reference

Prerequisites

Make sure to run the envsetup.sh script from the Cangjie SDK before using the mirror generator.

You need to know the pathnames of the header files of the frameworks and libraries in which all dependencies of the classes you are going to mirror are defined. This includes the locations of the iOS standard library headers and generally all directories in which the Objective-C compiler looks up header files when it builds your project.

Command-line Syntax

ObjCInteropGen [-v] [--mode=normal config-file]

-v

Produce verbose output.

--mode=normal

Enforces the normal operation mode. Other modes are only used for the development and testing of the mirror generator itself. The use of --mode=normal is mandatory in the current version if config-file is specified, but will be optional in the future.

config-file

The pathname of the configuration file.

Configuration File Syntax

An Objective-C mirror generator configuration file is a plain text file in the TOML syntax. It specifies:

  • Output directories
  • Names of the source Objective-C headers (.h-files)
  • Names of the output Cangjie packages and the distribution of mirror types across them
  • Mappings for types that you want to be handled in a special way

String values that are interpreted as regular expressions must follow the ECMAScript regular expression syntax.

Output Directories Roots

Each entry in the [output-roots] table of tables defines a symbolic name for the pathname of a directory in the local file system. The mirror generator will use that pathname as the common root of one or more package-specific output directories, set using the output-root property of the respective [[packages]] array elements.

Example:

[output-roots.lib]
path = "./lib/src"

[output-roots.app]
path = "./main/src"

[[packages]]
package-name = "com.vendor1.lib1"
output-root = "lib"  # Output to "./lib/src/com/vendor1/lib1"
filters = ...

[[packages]]
package-name = "com.vendor2.lib2"
output-root = "lib"  # Output to "./lib/src/com/vendor2/lib2"
filters = ...

[[packages]]
package-name = "com.mycompany.app"
output-root = "app"  # Output to "./main/src/com/mycompany/app"
filters = ...

Source Files

Each entry in the [sources] table of tables specifies a set of individual header files that the mirror generator must take as input.

Properties:

paths (mandatory)

An array of strings that contain pathnames of individual header files that the mirror generator must read.

arguments (optional)

Array of strings containing Clang options that the mirror generator will pass over when processing the source files listed in paths.

Example:

[sources.all]
paths = ["original-objc/M.h"]

Additional Clang Arguments

Each entry in the [sources-mixins] table of tables specifies a regular expression matching one or more keys of the [sources] table and additional arguments that must be passed to Clang when processing the header files from its respective entries.

Properties:

sources (mandatory)

A string containing a regular expression.

arguments-prepend
arguments-append (optional)

Arrays of strings that the mirror generator will pass over to Clang as options when processing the source files listed in any [sources] table entry which key matches the regular expression specified in the sources property.

The options listed in arguments-prepend and arguments-append will be passed respectively before and after the options specified in the arguments property of each matching [sources] entry, if any.

Example:

[sources.UIWidgets]
paths = ["objc/UIWidgets.h"]
arguments = [ "-I", "/usr/local/include/share/Widgets" ]

[sources.UIPanels]
paths = ["objc/UIPanels.h"]
arguments = [ "-I", "/usr/local/include/share/Panels" ]

[sources-mixins.UI]
# Add these Clang arguments for both UIWidgets and UIPanels
sources = ["UI.+"]
arguments-append = [
    "-I", "/usr/local/include/Frameworks/AcmeUI"
]

Packages

Each entry in the [[packages]] array specifies a target Cangjie package name, a set of name filters that define which Objective-C entities will be mirrored to that package, and, optionally, the output directory specific for that package.

Properties:

package-name (mandatory)

A string containing the target Cangjie package name.

output-path (optional)

A string containing the exact pathname of the directory into which the mirror generator shall place the output files for the given package. If any directories in that pathname do not exist, the mirror generator will attempt to create them.

output-root (optional)

A string containing a key from the output-roots table The name of the target Cangjie package will be appended to the value of the respective output-roots table entry, dots . replaced with the file separator /, and the resulting pathname will be used as if it was the value of output-path.

If output-root is not set, and if there is only one element in [output-roots], the pathname from that entry is used. Otherwise, an error is shown.

Example:

[output-roots.main]
path="./cj-mirrors"

[[packages]]
package-name = "objc.foundation"
output-root = "main"

The output files will be placed in ./cj-mirrors/objc/foundation.

filters (mandatory)

A table defining a set of name filters that select only those Objective-C entity declarations from the source files that must be mirrored to the given Cangjie package. See Name Filters for details.

Example:

# The Foundation framework
[[packages]]
package-name = "objc.foundation"
filters = { include = "NS.+" }

Name Filters

Each name filter is, in turn, a TOML table that contains:

  • Exactly one of the properties include, exclude, union, intersect and not, and
  • Optionally, the filter property and/or the filter-not property.

Descriptions of the properties follow.

include

The value can be a regular expression or an array thereof. If the value is a single regex, only names that match it pass the filter. If the value is an array, a name only needs to match any single regex from that array to pass.

Examples:

# Only include entities that names of which start with "NS":
filters = { include = "NS.+" }

# Only include entities with names that either start with "Foo"
# or end with "Bar":
filters = { include = ["Foo.*", ".*Bar"] }

exclude

The opposite of include (see above). A name that would pass an include filter with the given value does not pass an exclude filter with the same value and vice versa.

Example:

# Include everything but entities the names of which start with "INTERNAL_":
filters = { exclude = "INTERNAL_.+" }

union

An operator combining two or more filters. Its value must be an array of filters. A name that passes any single one of those filters passes the entire union filter.

Example:

# An equivalent of the second `include` example above:
filters = { union = [ { include = "Foo.*" }, { include = ".*Bar" } ] }

intersect

An operator combining two or more filters. The value must be an array of filters. A name must pass all of them to pass the the entire intersect filter.

Example:

// Adding a negative filter:
filters = { intersect = [ { include = "NS.+" },
                          { exclude = "NSAccidentalClash" } ] }

not

An operator that reverses the meaning of a filter. The value must be a single filter:

Example:

// Another way to add a negative filter:
filters = { intersect = [ { include = "NS.+" },
                          { not = { include = "NSAccidentalClash" } } ] }

filter
filter-not (optional)

These properties must be mixed with other properties, i.e. they cannot be the only properties of a filters table. Their values can be regular expressions or arrays thereof, just as those of the include and exclude properties.

filter reduces the set that passed the main filter to include only names that match the given single regular expression or any regex from the array.

filter-not reduces the set that passed the main filter to include only names that do not match the given single regular expression or any regex from the array.

filter and filter-not are actually shorthands for intersect operations with an include and exclude filter respectively. They can be specified together.

Examples:

# Without filter-not:
filters = { intersect = [ { include = "NS.+" },
                          { exclude = "NSAccidentalClash" } ] }
# With filter-not:
filters = { include = "NS.+", filter-not = "NSAccidentalClash" }

# 'filter' and 'filter-not' can be used together:
filters = { include    = ".*Fizz.+",
            filter     = ".+Buzz.*",
            filter-not = ".*FizzBuzz.*" }

Type Substitutions

[[mappings]] is an array of tables, each adding to the list of substitutions of one Objective-C type for another. All mentions of almost any type (except for C primitive types such as int) can be replaced with mentions of another type.

Example:

# Replace the type id, the root of the Objective-C type hierarchy,
# with NSObjectProtocol everywhere.
[[mappings]]
id = "NSObjectProtocol"

Configuration File Import

imports is an array of strings each containing the pathname of another configuration file, the settings from which will be added to the current configuration. The entries of packages and mappings arrays found in the imported file are appended to those present in the importing file.

Nested imports are supported, circular import is detected and results in an error.

Example:

import = "../common.toml"