RainwayApp / node-clangffi

Generate Typescript FFI bindings for C/C++ libraries using libclang and ffi-napi.
MIT License
7 stars 1 forks source link

feat: TypeScript codegen for union{} and struct{union{}} #50

Closed lynn closed 2 years ago

lynn commented 2 years ago

The codegen uses ref-union-di. A top-level union is the easy case, but this PR also supports the valid C11 case where a struct contains an anonymous union, such as cbindgen generates for its tagged unions. In this case I generate a fictitious field in the struct called union whose type is a ref-union-di Union. This should be ABI-compatible with the anonymous union, I think. Fingers crossed!

Example input:

// Regular union:
typedef union JustUnion { int a; char b; } JustUnion;

// Tagged union:
typedef enum RainwayInput_Tag { IDK, GAMEPAD, MOUSE } RainwayInput_Tag;
typedef struct GamepadReport_Body { const char *text; } GamepadReport_Body;
typedef struct MouseAbsolute_Body { int x; int y; } MouseAbsolute_Body;
typedef struct RainwayInput
{
    RainwayInput_Tag tag;
    union
    {
        GamepadReport_Body GAMEPAD_REPORT;
        MouseAbsolute_Body MOUSE_ABSOLUTE;
    };
} RainwayInput;

This generates:

import ffi from "ffi-napi";
import ref, { Pointer as TypedPointer, UnderlyingType } from "ref-napi";
import refStructDi, { StructObject } from "ref-struct-di";
import refArrayDi, { TypedArray } from "ref-array-di";
import refUnionDi from "ref-union-di";
const Struct = refStructDi(ref);
const Union = refUnionDi(ref);
const ArrayType = refArrayDi(ref);
const Pointer = ref.refType;
export type JustUnionType = UnderlyingType<typeof JustUnionDef>;
export interface JustUnion {
  a: number;
  b: number;
}
export enum RainwayInput_Tag {
  Idk = 0,
  Gamepad = 1,
  Mouse = 2,
}
export type GamepadReport_BodyType = UnderlyingType<
  typeof GamepadReport_BodyDef
>;
export interface GamepadReport_Body {
  text: string;
}
export type MouseAbsolute_BodyType = UnderlyingType<
  typeof MouseAbsolute_BodyDef
>;
export interface MouseAbsolute_Body {
  x: number;
  y: number;
}
export type RainwayInput_UnionType = UnderlyingType<
  typeof RainwayInput_UnionDef
>;
export interface RainwayInput_Union {
  GAMEPAD_REPORT: GamepadReport_Body;
  MOUSE_ABSOLUTE: MouseAbsolute_Body;
}
export type RainwayInputType = UnderlyingType<typeof RainwayInputDef>;
export interface RainwayInput {
  tag: RainwayInput_Tag;
  union: RainwayInput_Union;
}
export const JustUnionDef = Union({
  a: ref.types.int,
  b: ref.types.char,
});
export const RainwayInput_TagDef = ref.types.int;
export const GamepadReport_BodyDef = Struct({
  text: ref.types.CString,
});
export const MouseAbsolute_BodyDef = Struct({
  x: ref.types.int,
  y: ref.types.int,
});
export const RainwayInput_UnionDef = Union({
  GAMEPAD_REPORT: GamepadReport_BodyDef,
  MOUSE_ABSOLUTE: MouseAbsolute_BodyDef,
});
export const RainwayInputDef = Struct({
  tag: RainwayInput_TagDef,
  union: RainwayInput_UnionDef,
});
export function dlopen(libPath: string) {
  return ffi.Library(libPath, {});
}