diff --git a/.gitignore b/.gitignore index 920b1724..b7b3e1b5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ cmake-build-release/ .idea/ node_modules/ *.dSYM -.vs/ \ No newline at end of file +.vs/ +bindings/odin/clay-odin/tmp/ + +generator/__pycache__/ + +generator/generators/__pycache__/ diff --git a/generator/__init__.py b/generator/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/generator/array_add.template.c b/generator/clay_header_generator/array_add.template.c similarity index 100% rename from generator/array_add.template.c rename to generator/clay_header_generator/array_add.template.c diff --git a/generator/array_add_value.template.c b/generator/clay_header_generator/array_add_value.template.c similarity index 100% rename from generator/array_add_value.template.c rename to generator/clay_header_generator/array_add_value.template.c diff --git a/generator/array_allocate.template.c b/generator/clay_header_generator/array_allocate.template.c similarity index 100% rename from generator/array_allocate.template.c rename to generator/clay_header_generator/array_allocate.template.c diff --git a/generator/array_allocate_pointer.template.c b/generator/clay_header_generator/array_allocate_pointer.template.c similarity index 100% rename from generator/array_allocate_pointer.template.c rename to generator/clay_header_generator/array_allocate_pointer.template.c diff --git a/generator/array_define.template.c b/generator/clay_header_generator/array_define.template.c similarity index 100% rename from generator/array_define.template.c rename to generator/clay_header_generator/array_define.template.c diff --git a/generator/array_define_slice.template.c b/generator/clay_header_generator/array_define_slice.template.c similarity index 100% rename from generator/array_define_slice.template.c rename to generator/clay_header_generator/array_define_slice.template.c diff --git a/generator/array_get.template.c b/generator/clay_header_generator/array_get.template.c similarity index 100% rename from generator/array_get.template.c rename to generator/clay_header_generator/array_get.template.c diff --git a/generator/array_get_slice.template.c b/generator/clay_header_generator/array_get_slice.template.c similarity index 100% rename from generator/array_get_slice.template.c rename to generator/clay_header_generator/array_get_slice.template.c diff --git a/generator/array_get_value.template.c b/generator/clay_header_generator/array_get_value.template.c similarity index 100% rename from generator/array_get_value.template.c rename to generator/clay_header_generator/array_get_value.template.c diff --git a/generator/array_remove_swapback.template.c b/generator/clay_header_generator/array_remove_swapback.template.c similarity index 100% rename from generator/array_remove_swapback.template.c rename to generator/clay_header_generator/array_remove_swapback.template.c diff --git a/generator/array_set.template.c b/generator/clay_header_generator/array_set.template.c similarity index 100% rename from generator/array_set.template.c rename to generator/clay_header_generator/array_set.template.c diff --git a/generator/generate_templates.js b/generator/clay_header_generator/generate_templates.js similarity index 100% rename from generator/generate_templates.js rename to generator/clay_header_generator/generate_templates.js diff --git a/generator/cli.py b/generator/cli.py new file mode 100644 index 00000000..de7415f7 --- /dev/null +++ b/generator/cli.py @@ -0,0 +1,83 @@ +import argparse +import logging +import json + +from pathlib import Path + +from generators.base_generator import BaseGenerator +from generators.odin_generator import OdinGenerator +from parser import parse_headers + +logger = logging.getLogger(__name__) + +GeneratorMap = dict[str, type[BaseGenerator]] +GENERATORS = { + 'odin': OdinGenerator, +} + +def main() -> None: + arg_parser = argparse.ArgumentParser(description='Generate clay bindings') + + # Directories + arg_parser.add_argument('input_files', nargs='+', type=str, help='Input header files') + arg_parser.add_argument('--output-dir', type=str, help='Output directory', required=True) + arg_parser.add_argument('--tmp-dir', type=str, help='Temporary directory') + + # Generators + arg_parser.add_argument('--generator', type=str, choices=list(GENERATORS.keys()), help='Generators to run', required=True) + + # Logging + arg_parser.add_argument('--verbose', action='store_true', help='Verbose logging') + + args = arg_parser.parse_args() + + log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + log_handler = logging.StreamHandler() + log_handler.setFormatter(log_formatter) + if args.verbose: + logging.basicConfig(level=logging.DEBUG, handlers=[log_handler]) + else: + logging.basicConfig(level=logging.INFO, handlers=[log_handler]) + + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + if args.tmp_dir: + tmp_dir = Path(args.tmp_dir) + else: + tmp_dir = output_dir / 'tmp' + tmp_dir.mkdir(parents=True, exist_ok=True) + + fake_libc_include_path = Path(__file__).parent / 'fake_libc_include' + input_files = list(fake_libc_include_path.glob('*.h')) + [Path(f) for f in args.input_files] + + logger.info(f'Input files: {input_files}') + logger.info(f'Output directory: {output_dir}') + logger.info(f'Temporary directory: {tmp_dir}') + logger.info(f'Generator: {args.generator}') + + logger.info('Parsing headers') + extracted_symbols = parse_headers(input_files, output_dir, tmp_dir) + with open(tmp_dir / 'extracted_symbols.json', 'w') as f: + f.write(json.dumps({ + 'structs': extracted_symbols.structs, + 'enums': extracted_symbols.enums, + 'functions': extracted_symbols.functions, + }, indent=2)) + + logger.info('Generating bindings') + generator = GENERATORS[args.generator](extracted_symbols) + generator.generate() + logger.debug(f'Generated bindings:') + # for file_name, content in generator.get_outputs().items(): + # logger.debug(f'{file_name}:') + # logger.debug(content) + # logger.debug('\n') + + tmp_outputs_dir = tmp_dir / 'generated' + tmp_outputs_dir.mkdir(parents=True, exist_ok=True) + generator.write_outputs(tmp_outputs_dir) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/generator/fake_libc_include/_fake_defines.h b/generator/fake_libc_include/_fake_defines.h new file mode 100644 index 00000000..852744a6 --- /dev/null +++ b/generator/fake_libc_include/_fake_defines.h @@ -0,0 +1,262 @@ +#ifndef _FAKE_DEFINES_H +#define _FAKE_DEFINES_H + +#define NULL 0 +#define BUFSIZ 1024 +#define FOPEN_MAX 20 +#define FILENAME_MAX 1024 + +#ifndef SEEK_SET +#define SEEK_SET 0 /* set file offset to offset */ +#endif +#ifndef SEEK_CUR +#define SEEK_CUR 1 /* set file offset to current plus offset */ +#endif +#ifndef SEEK_END +#define SEEK_END 2 /* set file offset to EOF plus offset */ +#endif + +#define __LITTLE_ENDIAN 1234 +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#define __BIG_ENDIAN 4321 +#define BIG_ENDIAN __BIG_ENDIAN +#define __BYTE_ORDER __LITTLE_ENDIAN +#define BYTE_ORDER __BYTE_ORDER + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +#define SCHAR_MIN -128 +#define SCHAR_MAX 127 +#define CHAR_MIN -128 +#define CHAR_MAX 127 +#define UCHAR_MAX 255 +#define SHRT_MIN -32768 +#define SHRT_MAX 32767 +#define USHRT_MAX 65535 +#define INT_MIN -2147483648 +#define INT_MAX 2147483647 +#define UINT_MAX 4294967295U +#define LONG_MIN -9223372036854775808L +#define LONG_MAX 9223372036854775807L +#define ULONG_MAX 18446744073709551615UL +#define RAND_MAX 32767 + +/* C99 inttypes.h defines */ +#define PRId8 "d" +#define PRIi8 "i" +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRId16 "d" +#define PRIi16 "i" +#define PRIo16 "o" +#define PRIu16 "u" +#define PRIx16 "x" +#define PRIX16 "X" +#define PRId32 "d" +#define PRIi32 "i" +#define PRIo32 "o" +#define PRIu32 "u" +#define PRIx32 "x" +#define PRIX32 "X" +#define PRId64 "d" +#define PRIi64 "i" +#define PRIo64 "o" +#define PRIu64 "u" +#define PRIx64 "x" +#define PRIX64 "X" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIdLEAST16 "d" +#define PRIiLEAST16 "i" +#define PRIoLEAST16 "o" +#define PRIuLEAST16 "u" +#define PRIxLEAST16 "x" +#define PRIXLEAST16 "X" +#define PRIdLEAST32 "d" +#define PRIiLEAST32 "i" +#define PRIoLEAST32 "o" +#define PRIuLEAST32 "u" +#define PRIxLEAST32 "x" +#define PRIXLEAST32 "X" +#define PRIdLEAST64 "d" +#define PRIiLEAST64 "i" +#define PRIoLEAST64 "o" +#define PRIuLEAST64 "u" +#define PRIxLEAST64 "x" +#define PRIXLEAST64 "X" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" +#define PRIdFAST16 "d" +#define PRIiFAST16 "i" +#define PRIoFAST16 "o" +#define PRIuFAST16 "u" +#define PRIxFAST16 "x" +#define PRIXFAST16 "X" +#define PRIdFAST32 "d" +#define PRIiFAST32 "i" +#define PRIoFAST32 "o" +#define PRIuFAST32 "u" +#define PRIxFAST32 "x" +#define PRIXFAST32 "X" +#define PRIdFAST64 "d" +#define PRIiFAST64 "i" +#define PRIoFAST64 "o" +#define PRIuFAST64 "u" +#define PRIxFAST64 "x" +#define PRIXFAST64 "X" +#define PRIdPTR "d" +#define PRIiPTR "i" +#define PRIoPTR "o" +#define PRIuPTR "u" +#define PRIxPTR "x" +#define PRIXPTR "X" +#define PRIdMAX "d" +#define PRIiMAX "i" +#define PRIoMAX "o" +#define PRIuMAX "u" +#define PRIxMAX "x" +#define PRIXMAX "X" +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNd16 "d" +#define SCNi16 "i" +#define SCNo16 "o" +#define SCNu16 "u" +#define SCNx16 "x" +#define SCNd32 "d" +#define SCNi32 "i" +#define SCNo32 "o" +#define SCNu32 "u" +#define SCNx32 "x" +#define SCNd64 "d" +#define SCNi64 "i" +#define SCNo64 "o" +#define SCNu64 "u" +#define SCNx64 "x" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNdLEAST16 "d" +#define SCNiLEAST16 "i" +#define SCNoLEAST16 "o" +#define SCNuLEAST16 "u" +#define SCNxLEAST16 "x" +#define SCNdLEAST32 "d" +#define SCNiLEAST32 "i" +#define SCNoLEAST32 "o" +#define SCNuLEAST32 "u" +#define SCNxLEAST32 "x" +#define SCNdLEAST64 "d" +#define SCNiLEAST64 "i" +#define SCNoLEAST64 "o" +#define SCNuLEAST64 "u" +#define SCNxLEAST64 "x" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNdFAST16 "d" +#define SCNiFAST16 "i" +#define SCNoFAST16 "o" +#define SCNuFAST16 "u" +#define SCNxFAST16 "x" +#define SCNdFAST32 "d" +#define SCNiFAST32 "i" +#define SCNoFAST32 "o" +#define SCNuFAST32 "u" +#define SCNxFAST32 "x" +#define SCNdFAST64 "d" +#define SCNiFAST64 "i" +#define SCNoFAST64 "o" +#define SCNuFAST64 "u" +#define SCNxFAST64 "x" +#define SCNdPTR "d" +#define SCNiPTR "i" +#define SCNoPTR "o" +#define SCNuPTR "u" +#define SCNxPTR "x" +#define SCNdMAX "d" +#define SCNiMAX "i" +#define SCNoMAX "o" +#define SCNuMAX "u" +#define SCNxMAX "x" + +/* C99 stdbool.h defines */ +#define __bool_true_false_are_defined 1 +#define false 0 +#define true 1 + +/* va_arg macros and type*/ +#define va_start(_ap, _type) __builtin_va_start((_ap)) +#define va_arg(_ap, _type) __builtin_va_arg((_ap)) +#define va_end(_list) + +/* Vectors */ +#define __m128 int +#define __m128_u int +#define __m128d int +#define __m128d_u int +#define __m128i int +#define __m128i_u int +#define __m256 int +#define __m256_u int +#define __m256d int +#define __m256d_u int +#define __m256i int +#define __m256i_u int +#define __m512 int +#define __m512_u int +#define __m512d int +#define __m512d_u int +#define __m512i int +#define __m512i_u int + +/* C11 stdnoreturn.h defines */ +#define __noreturn_is_defined 1 +#define noreturn _Noreturn + +/* C11 threads.h defines */ +#define thread_local _Thread_local + +/* C11 assert.h defines */ +#define static_assert _Static_assert + +/* C11 stdatomic.h defines */ +#define ATOMIC_BOOL_LOCK_FREE 0 +#define ATOMIC_CHAR_LOCK_FREE 0 +#define ATOMIC_CHAR16_T_LOCK_FREE 0 +#define ATOMIC_CHAR32_T_LOCK_FREE 0 +#define ATOMIC_WCHAR_T_LOCK_FREE 0 +#define ATOMIC_SHORT_LOCK_FREE 0 +#define ATOMIC_INT_LOCK_FREE 0 +#define ATOMIC_LONG_LOCK_FREE 0 +#define ATOMIC_LLONG_LOCK_FREE 0 +#define ATOMIC_POINTER_LOCK_FREE 0 +#define ATOMIC_VAR_INIT(value) (value) +#define ATOMIC_FLAG_INIT { 0 } +#define kill_dependency(y) (y) + +/* C11 stdalign.h defines */ +#define alignas _Alignas +#define alignof _Alignof +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +#endif diff --git a/generator/fake_libc_include/_fake_typedefs.h b/generator/fake_libc_include/_fake_typedefs.h new file mode 100644 index 00000000..3be1462d --- /dev/null +++ b/generator/fake_libc_include/_fake_typedefs.h @@ -0,0 +1,222 @@ +#ifndef _FAKE_TYPEDEFS_H +#define _FAKE_TYPEDEFS_H + +typedef int size_t; +typedef int __builtin_va_list; +typedef int __gnuc_va_list; +typedef int va_list; +typedef int __int8_t; +typedef int __uint8_t; +typedef int __int16_t; +typedef int __uint16_t; +typedef int __int_least16_t; +typedef int __uint_least16_t; +typedef int __int32_t; +typedef int __uint32_t; +typedef int __int64_t; +typedef int __uint64_t; +typedef int __int_least32_t; +typedef int __uint_least32_t; +typedef int __s8; +typedef int __u8; +typedef int __s16; +typedef int __u16; +typedef int __s32; +typedef int __u32; +typedef int __s64; +typedef int __u64; +typedef int _LOCK_T; +typedef int _LOCK_RECURSIVE_T; +typedef int _off_t; +typedef int __dev_t; +typedef int __uid_t; +typedef int __gid_t; +typedef int _off64_t; +typedef int _fpos_t; +typedef int _ssize_t; +typedef int wint_t; +typedef int _mbstate_t; +typedef int _flock_t; +typedef int _iconv_t; +typedef int __ULong; +typedef int __FILE; +typedef int ptrdiff_t; +typedef int wchar_t; +typedef int char16_t; +typedef int char32_t; +typedef int __off_t; +typedef int __pid_t; +typedef int __loff_t; +typedef int u_char; +typedef int u_short; +typedef int u_int; +typedef int u_long; +typedef int ushort; +typedef int uint; +typedef int clock_t; +typedef int time_t; +typedef int daddr_t; +typedef int caddr_t; +typedef int ino_t; +typedef int off_t; +typedef int dev_t; +typedef int uid_t; +typedef int gid_t; +typedef int pid_t; +typedef int key_t; +typedef int ssize_t; +typedef int mode_t; +typedef int nlink_t; +typedef int fd_mask; +typedef int _types_fd_set; +typedef int clockid_t; +typedef int timer_t; +typedef int useconds_t; +typedef int suseconds_t; +typedef int FILE; +typedef int fpos_t; +typedef int cookie_read_function_t; +typedef int cookie_write_function_t; +typedef int cookie_seek_function_t; +typedef int cookie_close_function_t; +typedef int cookie_io_functions_t; +typedef int div_t; +typedef int ldiv_t; +typedef int lldiv_t; +typedef int sigset_t; +typedef int __sigset_t; +typedef int _sig_func_ptr; +typedef int sig_atomic_t; +typedef int __tzrule_type; +typedef int __tzinfo_type; +typedef int mbstate_t; +typedef int sem_t; +typedef int pthread_t; +typedef int pthread_attr_t; +typedef int pthread_mutex_t; +typedef int pthread_mutexattr_t; +typedef int pthread_cond_t; +typedef int pthread_condattr_t; +typedef int pthread_key_t; +typedef int pthread_once_t; +typedef int pthread_rwlock_t; +typedef int pthread_rwlockattr_t; +typedef int pthread_spinlock_t; +typedef int pthread_barrier_t; +typedef int pthread_barrierattr_t; +typedef int jmp_buf; +typedef int rlim_t; +typedef int sa_family_t; +typedef int sigjmp_buf; +typedef int stack_t; +typedef int siginfo_t; +typedef int z_stream; + +/* C99 exact-width integer types */ +typedef int int8_t; +typedef int uint8_t; +typedef int int16_t; +typedef int uint16_t; +typedef int int32_t; +typedef int uint32_t; +typedef int int64_t; +typedef int uint64_t; + +/* C99 minimum-width integer types */ +typedef int int_least8_t; +typedef int uint_least8_t; +typedef int int_least16_t; +typedef int uint_least16_t; +typedef int int_least32_t; +typedef int uint_least32_t; +typedef int int_least64_t; +typedef int uint_least64_t; + +/* C99 fastest minimum-width integer types */ +typedef int int_fast8_t; +typedef int uint_fast8_t; +typedef int int_fast16_t; +typedef int uint_fast16_t; +typedef int int_fast32_t; +typedef int uint_fast32_t; +typedef int int_fast64_t; +typedef int uint_fast64_t; + +/* C99 integer types capable of holding object pointers */ +typedef int intptr_t; +typedef int uintptr_t; + +/* C99 greatest-width integer types */ +typedef int intmax_t; +typedef int uintmax_t; + +/* C99 stdbool.h bool type. _Bool is built-in in C99 */ +typedef _Bool bool; + +/* Mir typedefs */ +typedef void* MirEGLNativeWindowType; +typedef void* MirEGLNativeDisplayType; +typedef struct MirConnection MirConnection; +typedef struct MirSurface MirSurface; +typedef struct MirSurfaceSpec MirSurfaceSpec; +typedef struct MirScreencast MirScreencast; +typedef struct MirPromptSession MirPromptSession; +typedef struct MirBufferStream MirBufferStream; +typedef struct MirPersistentId MirPersistentId; +typedef struct MirBlob MirBlob; +typedef struct MirDisplayConfig MirDisplayConfig; + +/* xcb typedefs */ +typedef struct xcb_connection_t xcb_connection_t; +typedef uint32_t xcb_window_t; +typedef uint32_t xcb_visualid_t; + +/* C11 stdatomic.h types */ +typedef _Atomic(_Bool) atomic_bool; +typedef _Atomic(char) atomic_char; +typedef _Atomic(signed char) atomic_schar; +typedef _Atomic(unsigned char) atomic_uchar; +typedef _Atomic(short) atomic_short; +typedef _Atomic(unsigned short) atomic_ushort; +typedef _Atomic(int) atomic_int; +typedef _Atomic(unsigned int) atomic_uint; +typedef _Atomic(long) atomic_long; +typedef _Atomic(unsigned long) atomic_ulong; +typedef _Atomic(long long) atomic_llong; +typedef _Atomic(unsigned long long) atomic_ullong; +typedef _Atomic(uint_least16_t) atomic_char16_t; +typedef _Atomic(uint_least32_t) atomic_char32_t; +typedef _Atomic(wchar_t) atomic_wchar_t; +typedef _Atomic(int_least8_t) atomic_int_least8_t; +typedef _Atomic(uint_least8_t) atomic_uint_least8_t; +typedef _Atomic(int_least16_t) atomic_int_least16_t; +typedef _Atomic(uint_least16_t) atomic_uint_least16_t; +typedef _Atomic(int_least32_t) atomic_int_least32_t; +typedef _Atomic(uint_least32_t) atomic_uint_least32_t; +typedef _Atomic(int_least64_t) atomic_int_least64_t; +typedef _Atomic(uint_least64_t) atomic_uint_least64_t; +typedef _Atomic(int_fast8_t) atomic_int_fast8_t; +typedef _Atomic(uint_fast8_t) atomic_uint_fast8_t; +typedef _Atomic(int_fast16_t) atomic_int_fast16_t; +typedef _Atomic(uint_fast16_t) atomic_uint_fast16_t; +typedef _Atomic(int_fast32_t) atomic_int_fast32_t; +typedef _Atomic(uint_fast32_t) atomic_uint_fast32_t; +typedef _Atomic(int_fast64_t) atomic_int_fast64_t; +typedef _Atomic(uint_fast64_t) atomic_uint_fast64_t; +typedef _Atomic(intptr_t) atomic_intptr_t; +typedef _Atomic(uintptr_t) atomic_uintptr_t; +typedef _Atomic(size_t) atomic_size_t; +typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t; +typedef _Atomic(intmax_t) atomic_intmax_t; +typedef _Atomic(uintmax_t) atomic_uintmax_t; +typedef struct atomic_flag { atomic_bool _Value; } atomic_flag; +typedef enum memory_order { + memory_order_relaxed, + memory_order_consume, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst +} memory_order; + +#endif diff --git a/generator/fake_libc_include/_syslist.h b/generator/fake_libc_include/_syslist.h new file mode 100644 index 00000000..f952c1d6 --- /dev/null +++ b/generator/fake_libc_include/_syslist.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/generator/fake_libc_include/stdbool.h b/generator/fake_libc_include/stdbool.h new file mode 100644 index 00000000..f952c1d6 --- /dev/null +++ b/generator/fake_libc_include/stdbool.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/generator/fake_libc_include/stddef.h b/generator/fake_libc_include/stddef.h new file mode 100644 index 00000000..f952c1d6 --- /dev/null +++ b/generator/fake_libc_include/stddef.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/generator/fake_libc_include/stdint.h b/generator/fake_libc_include/stdint.h new file mode 100644 index 00000000..f952c1d6 --- /dev/null +++ b/generator/fake_libc_include/stdint.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/generator/gen_repo_bindings.sh b/generator/gen_repo_bindings.sh new file mode 100644 index 00000000..067c753e --- /dev/null +++ b/generator/gen_repo_bindings.sh @@ -0,0 +1,5 @@ +#!/usr/bin/bash +REPO_ROOT=$(realpath $(dirname $(dirname $0))) + +# Generate odin bindings +python $REPO_ROOT/generator/cli.py $REPO_ROOT/clay.h --output-dir $REPO_ROOT/bindings/odin/clay-odin --generator odin --verbose \ No newline at end of file diff --git a/generator/generators/__init__.py b/generator/generators/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/generator/generators/base_generator.py b/generator/generators/base_generator.py new file mode 100644 index 00000000..c2c620f8 --- /dev/null +++ b/generator/generators/base_generator.py @@ -0,0 +1,46 @@ + +from parser import ExtractedSymbols, ExtractedEnum, ExtractedStruct, ExtractedFunction +from typing import Any, Callable, DefaultDict, Literal, NotRequired, Optional, TypedDict +from pathlib import Path +from dataclasses import dataclass + +SymbolType = Literal['enum', 'struct', 'function'] + +class BaseGenerator: + def __init__(self, extracted_symbols: ExtractedSymbols): + self.extracted_symbols = extracted_symbols + self.output_content: dict[str, list[str]] = dict() + + def generate(self) -> None: + pass + + def has_symbol(self, symbol: str) -> bool: + return ( + symbol in self.extracted_symbols.enums or + symbol in self.extracted_symbols.structs or + symbol in self.extracted_symbols.functions + ) + + def get_symbol_type(self, symbol: str) -> SymbolType: + if symbol in self.extracted_symbols.enums: + return 'enum' + elif symbol in self.extracted_symbols.structs: + return 'struct' + elif symbol in self.extracted_symbols.functions: + return 'function' + raise ValueError(f'Unknown symbol: {symbol}') + + def _write(self, file_name: str, content: str) -> None: + if file_name not in self.output_content: + self.output_content[file_name] = [] + self.output_content[file_name].append(content) + + def write_outputs(self, output_dir: Path) -> None: + for file_name, content in self.output_content.items(): + with open(output_dir / file_name, 'w') as f: + f.write("\n".join(content)) + + def get_outputs(self) -> dict[str, str]: + return {file_name: "\n".join(content) for file_name, content in self.output_content.items()} + + diff --git a/generator/generators/odin/clay.template.odin b/generator/generators/odin/clay.template.odin new file mode 100644 index 00000000..b75ce180 --- /dev/null +++ b/generator/generators/odin/clay.template.odin @@ -0,0 +1,158 @@ +package clay + +import "core:c" +import "core:strings" + +when ODIN_OS == .Windows { + foreign import Clay "windows/clay.lib" +} else when ODIN_OS == .Linux { + foreign import Clay "linux/clay.a" +} else when ODIN_OS == .Darwin { + when ODIN_ARCH == .arm64 { + foreign import Clay "macos-arm64/clay.a" + } else { + foreign import Clay "macos/clay.a" + } +} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { + foreign import Clay "wasm/clay.o" +} + +when ODIN_OS == .Windows { + EnumBackingType :: u32 +} else { + EnumBackingType :: u8 +} + +{{enums}} + +Context :: struct { + +} + +ClayArray :: struct($type: typeid) { + capacity: i32, + length: i32, + internalArray: [^]type, +} + +SizingConstraints :: struct #raw_union { + sizeMinMax: SizingConstraintsMinMax, + sizePercent: c.float, +} + +{{structs}} + +@(link_prefix = "Clay_", default_calling_convention = "c") +foreign Clay { +{{public_functions}} +} + +@(link_prefix = "Clay_", default_calling_convention = "c", private) +foreign Clay { +{{private_functions}} +} + +@(require_results, deferred_none = _CloseElement) +UI :: proc(configs: ..TypedConfig) -> bool { + _OpenElement() + for config in configs { + #partial switch (config.type) { + case ElementConfigType.Id: + _AttachId(config.id) + case ElementConfigType.Layout: + _AttachLayoutConfig(cast(^LayoutConfig)config.config) + case: + _AttachElementConfig(config.config, config.type) + } + } + _ElementPostConfiguration() + return true +} + +Layout :: proc(config: LayoutConfig) -> TypedConfig { + return {type = ElementConfigType.Layout, config = _StoreLayoutConfig(config) } +} + +PaddingAll :: proc (padding: u16) -> Padding { + return { padding, padding, padding, padding } +} + +Rectangle :: proc(config: RectangleElementConfig) -> TypedConfig { + return {type = ElementConfigType.Rectangle, config = _StoreRectangleElementConfig(config)} +} + +Text :: proc(text: string, config: ^TextElementConfig) { + _OpenTextElement(MakeString(text), config) +} + +TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig { + return _StoreTextElementConfig(config) +} + +Image :: proc(config: ImageElementConfig) -> TypedConfig { + return {type = ElementConfigType.Image, config = _StoreImageElementConfig(config)} +} + +Floating :: proc(config: FloatingElementConfig) -> TypedConfig { + return {type = ElementConfigType.Floating, config = _StoreFloatingElementConfig(config)} +} + +Custom :: proc(config: CustomElementConfig) -> TypedConfig { + return {type = ElementConfigType.Custom, config = _StoreCustomElementConfig(config)} +} + +Scroll :: proc(config: ScrollElementConfig) -> TypedConfig { + return {type = ElementConfigType.Scroll, config = _StoreScrollElementConfig(config)} +} + +Border :: proc(config: BorderElementConfig) -> TypedConfig { + return {type = ElementConfigType.Border, config = _StoreBorderElementConfig(config)} +} + +BorderOutside :: proc(outsideBorders: BorderData) -> TypedConfig { + return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}) } +} + +BorderOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> TypedConfig { + return { type = ElementConfigType.Border, config = _StoreBorderElementConfig( + (BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}}, + ) } +} + +BorderAll :: proc(allBorders: BorderData) -> TypedConfig { + return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}) } +} + +BorderAllRadius :: proc(allBorders: BorderData, radius: f32) -> TypedConfig { + return { type = ElementConfigType.Border, config = _StoreBorderElementConfig( + (BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}}, + ) } +} + +CornerRadiusAll :: proc(radius: f32) -> CornerRadius { + return CornerRadius{radius, radius, radius, radius} +} + +SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { + return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = sizeMinMax}} +} + +SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { + return SizingAxis{type = SizingType.GROW, constraints = {sizeMinMax = sizeMinMax}} +} + +SizingFixed :: proc(size: c.float) -> SizingAxis { + return SizingAxis{type = SizingType.FIXED, constraints = {sizeMinMax = {size, size}}} +} + +SizingPercent :: proc(sizePercent: c.float) -> SizingAxis { + return SizingAxis{type = SizingType.PERCENT, constraints = {sizePercent = sizePercent}} +} + +MakeString :: proc(label: string) -> String { + return String{chars = raw_data(label), length = cast(c.int)len(label)} +} + +ID :: proc(label: string, index: u32 = 0) -> TypedConfig { + return { type = ElementConfigType.Id, id = _HashString(MakeString(label), index, 0) } +} diff --git a/generator/generators/odin_generator.py b/generator/generators/odin_generator.py new file mode 100644 index 00000000..1aea01ba --- /dev/null +++ b/generator/generators/odin_generator.py @@ -0,0 +1,279 @@ +from pathlib import Path +import logging + +from generators.base_generator import BaseGenerator + +logger = logging.getLogger(__name__) + +def get_common_prefix(keys: list[str]) -> str: + # find a prefix that's shared between all keys + prefix = "" + for i in range(min(map(len, keys))): + if len(set(key[i] for key in keys)) > 1: + break + prefix += keys[0][i] + return prefix + +def snake_case_to_pascal_case(snake_case: str) -> str: + return ''.join(word.lower().capitalize() for word in snake_case.split('_')) + + +SYMBOL_NAME_OVERRIDES = { + 'Clay_TextElementConfigWrapMode': 'TextWrapMode', + 'Clay_Border': 'BorderData', + 'Clay_SizingMinMax': 'SizingConstraintsMinMax', +} +SYMBOL_COMPLETE_OVERRIDES = { + 'Clay_RenderCommandArray': 'ClayArray(RenderCommand)', + 'Clay_Context': 'Context', + 'Clay_ElementConfig': None, + # 'Clay_SetQueryScrollOffsetFunction': None, +} + +# These enums should have output binding members that are PascalCase instead of UPPER_SNAKE_CASE. +ENUM_MEMBER_PASCAL = { + 'Clay_RenderCommandType', + 'Clay_TextElementConfigWrapMode', + 'Clay__ElementConfigType', +} +ENUM_MEMBER_OVERRIDES = { + 'Clay__ElementConfigType': { + 'CLAY__ELEMENT_CONFIG_TYPE_BORDER_CONTAINER': 'Border', + 'CLAY__ELEMENT_CONFIG_TYPE_FLOATING_CONTAINER': 'Floating', + 'CLAY__ELEMENT_CONFIG_TYPE_SCROLL_CONTAINER': 'Scroll', + } +} +ENUM_ADDITIONAL_MEMBERS = { + 'Clay__ElementConfigType': { + 'Id': 65, + 'Layout': 66, + } +} + +TYPE_MAPPING = { + '*char': '[^]c.char', + 'const *char': '[^]c.char', + '*void': 'rawptr', + 'bool': 'bool', + 'float': 'c.float', + 'uint16_t': 'u16', + 'uint32_t': 'u32', + 'int32_t': 'c.int32_t', + 'uintptr_t': 'rawptr', + 'void': 'void', + + '*Clay_RectangleElementConfig': '^RectangleElementConfig', + '*Clay_TextElementConfig': '^TextElementConfig', + '*Clay_ImageElementConfig': '^ImageElementConfig', + '*Clay_FloatingElementConfig': '^FloatingElementConfig', + '*Clay_CustomElementConfig': '^CustomElementConfig', + '*Clay_ScrollElementConfig': '^ScrollElementConfig', + '*Clay_BorderElementConfig': '^BorderElementConfig', +} +STRUCT_TYPE_OVERRIDES = { + 'Clay_Arena': { + 'nextAllocation': 'uintptr', + 'capacity': 'uintptr', + }, + 'Clay_ErrorHandler': { + 'errorHandlerFunction': 'proc "c" (errorData: ErrorData)', + }, + 'Clay_SizingAxis': { + 'size': 'SizingConstraints', + }, +} +STRUCT_MEMBER_OVERRIDES = { + 'Clay_ErrorHandler': { + 'errorHandlerFunction': 'handler', + }, + 'Clay_SizingAxis': { + 'size': 'constraints', + }, +} +STRUCT_OVERRIDE_AS_FIXED_ARRAY = { + 'Clay_Color', + 'Clay_Vector2', +} + +FUNCTION_PARAM_OVERRIDES = { + 'Clay_SetCurrentContext': { + 'context': 'ctx', + }, +} +FUNCTION_TYPE_OVERRIDES = { + 'Clay_CreateArenaWithCapacityAndMemory': { + 'offset': '[^]u8', + }, + 'Clay_SetMeasureTextFunction': { + 'measureTextFunction': 'proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions', + }, +} + +class OdinGenerator(BaseGenerator): + + def generate(self) -> None: + self.generate_structs() + self.generate_enums() + self.generate_functions() + + odin_template_path = Path(__file__).parent / 'odin' / 'clay.template.odin' + with open(odin_template_path, 'r') as f: + template = f.read() + self.output_content['clay.odin'] = ( + template + .replace('{{structs}}', '\n'.join(self.output_content['struct'])) + .replace('{{enums}}', '\n'.join(self.output_content['enum'])) + .replace('{{public_functions}}', '\n'.join(self.output_content['public_function'])) + .replace('{{private_functions}}', '\n'.join(self.output_content['private_function'])) + .splitlines() + ) + del self.output_content['struct'] + del self.output_content['enum'] + del self.output_content['private_function'] + del self.output_content['public_function'] + + def get_symbol_name(self, symbol: str) -> str: + if symbol in SYMBOL_NAME_OVERRIDES: + return SYMBOL_NAME_OVERRIDES[symbol] + symbol_type = self.get_symbol_type(symbol) + base_name = symbol.removeprefix('Clay_') + if symbol_type == 'enum': + return base_name.removeprefix('_') # Clay_ and Clay__ are exported as public types. + elif symbol_type == 'struct': + return base_name + elif symbol_type == 'function': + return base_name + raise ValueError(f'Unknown symbol: {symbol}') + + def resolve_binding_type(self, symbol: str, member: str | None, member_type: str | None, type_overrides: dict[str, dict[str, str]]) -> str | None: + if member_type in SYMBOL_COMPLETE_OVERRIDES: + return SYMBOL_COMPLETE_OVERRIDES[member_type] + if symbol in type_overrides and member in type_overrides[symbol]: + return type_overrides[symbol][member] + if member_type in TYPE_MAPPING: + return TYPE_MAPPING[member_type] + if member_type and self.has_symbol(member_type): + return self.get_symbol_name(member_type) + if member_type and member_type.startswith('*'): + result = self.resolve_binding_type(symbol, member, member_type[1:], type_overrides) + if result: + return f"^{result}" + return None + + def generate_structs(self) -> None: + for struct, members in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]): + if not struct.startswith('Clay_'): + continue + if struct in SYMBOL_COMPLETE_OVERRIDES: + continue + + binding_name = self.get_symbol_name(struct) + if binding_name.startswith('_'): + continue + + if struct in STRUCT_OVERRIDE_AS_FIXED_ARRAY: + array_size = len(members) + array_type = list(members.values())[0]['type'] + + if array_type in TYPE_MAPPING: + array_binding_type = TYPE_MAPPING[array_type] + elif array_type and self.has_symbol(array_type): + array_binding_type = self.get_symbol_name(array_type) + else: + self._write('struct', f"// {struct} ({array_type}) - has no mapping") + continue + + self._write('struct', f"// {struct} (overridden as fixed array)") + self._write('struct', f"{binding_name} :: [{array_size}]{array_binding_type}") + self._write('struct', "") + continue + + self._write('struct', f"// {struct}") + self._write('struct', f"{binding_name} :: struct {{") + + for member, member_info in members.items(): + if struct in STRUCT_TYPE_OVERRIDES and member in STRUCT_TYPE_OVERRIDES[struct]: + member_type = 'unknown' + elif not 'type' in member_info: + self._write('struct', f" // {member} (unknown type)") + continue + else: + member_type = member_info['type'] + + binding_member_name = member + if struct in STRUCT_MEMBER_OVERRIDES and member in STRUCT_MEMBER_OVERRIDES[struct]: + binding_member_name = STRUCT_MEMBER_OVERRIDES[struct][member] + + member_binding_type = self.resolve_binding_type(struct, member, member_type, STRUCT_TYPE_OVERRIDES) + if member_binding_type is None: + self._write('struct', f" // {binding_member_name} ({member_type}) - has no mapping") + continue + self._write('struct', f" {binding_member_name}: {member_binding_type}, // {member} ({member_type})") + self._write('struct', "}") + self._write('struct', '') + + def generate_enums(self) -> None: + for enum, members in sorted(self.extracted_symbols.enums.items(), key=lambda x: x[0]): + if not enum.startswith('Clay_'): + continue + if enum in SYMBOL_COMPLETE_OVERRIDES: + continue + + binding_name = self.get_symbol_name(enum) + common_member_prefix = get_common_prefix(list(members.keys())) + self._write('enum', f"// {enum}") + self._write('enum', f"{binding_name} :: enum EnumBackingType {{") + for member in members: + if enum in ENUM_MEMBER_OVERRIDES and member in ENUM_MEMBER_OVERRIDES[enum]: + binding_member_name = ENUM_MEMBER_OVERRIDES[enum][member] + else: + binding_member_name = member.removeprefix(common_member_prefix) + if enum in ENUM_MEMBER_PASCAL: + binding_member_name = snake_case_to_pascal_case(binding_member_name) + + if members[member] is not None: + self._write('enum', f" {binding_member_name} = {members[member]}, // {member}") + else: + self._write('enum', f" {binding_member_name}, // {member}") + + if enum in ENUM_ADDITIONAL_MEMBERS: + self._write('enum', ' // Odin specific enum types') + for member, value in ENUM_ADDITIONAL_MEMBERS[enum].items(): + self._write('enum', f" {member} = {value},") + self._write('enum', "}") + self._write('enum', '') + + def generate_functions(self) -> None: + for function, function_info in sorted(self.extracted_symbols.functions.items(), key=lambda x: x[0]): + if not function.startswith('Clay_'): + continue + if function in SYMBOL_COMPLETE_OVERRIDES: + continue + is_private = function.startswith('Clay__') + write_to = 'private_function' if is_private else 'public_function' + + binding_name = self.get_symbol_name(function) + + return_type = function_info['return_type'] + binding_return_type = self.resolve_binding_type(function, None, return_type, {}) + if binding_return_type is None: + self._write(write_to, f" // {function} ({return_type}) - has no mapping") + continue + + skip = False + binding_params = [] + for param_name, param_type in function_info['params']: + binding_param_name = param_name + if function in FUNCTION_PARAM_OVERRIDES and param_name in FUNCTION_PARAM_OVERRIDES[function]: + binding_param_name = FUNCTION_PARAM_OVERRIDES[function][param_name] + binding_param_type = self.resolve_binding_type(function, param_name, param_type, FUNCTION_TYPE_OVERRIDES) + if binding_param_type is None: + skip = True + binding_params.append(f"{binding_param_name}: {binding_param_type}") + if skip: + self._write(write_to, f" // {function} - has no mapping") + continue + + binding_params_str = ', '.join(binding_params) + self._write(write_to, f" {binding_name} :: proc({binding_params_str}) -> {binding_return_type} --- // {function}") + diff --git a/generator/parser.py b/generator/parser.py new file mode 100644 index 00000000..c2f7c2bf --- /dev/null +++ b/generator/parser.py @@ -0,0 +1,152 @@ +from dataclasses import dataclass +from typing import Optional, TypedDict +from pycparser import c_ast, parse_file, preprocess_file +from pathlib import Path +import os +import json +import shutil +import logging + +logger = logging.getLogger(__name__) + +class ExtractedStructAttributeUnion(TypedDict): + type: Optional[str] + +class ExtractedStructAttribute(TypedDict): + type: Optional[str] + union: Optional[dict[str, Optional[str]]] + +ExtractedStruct = dict[str, ExtractedStructAttribute] +ExtractedEnum = dict[str, Optional[str]] +ExtractedFunctionParam = tuple[str, Optional[str]] + +class ExtractedFunction(TypedDict): + return_type: Optional[str] + params: list[ExtractedFunctionParam] + +@dataclass +class ExtractedSymbols: + structs: dict[str, ExtractedStruct] + enums: dict[str, ExtractedEnum] + functions: dict[str, ExtractedFunction] + +def get_type_names(node: c_ast.Node, prefix: str="") -> Optional[str]: + if isinstance(node, c_ast.TypeDecl) and hasattr(node, 'quals') and node.quals: + prefix = " ".join(node.quals) + " " + prefix + if isinstance(node, c_ast.PtrDecl): + prefix = "*" + prefix + + if hasattr(node, 'names'): + return prefix + node.names[0] # type: ignore + elif hasattr(node, 'type'): + return get_type_names(node.type, prefix) # type: ignore + return None + +class Visitor(c_ast.NodeVisitor): + def __init__(self): + self.structs: dict[str, ExtractedStruct] = {} + self.enums: dict[str, ExtractedEnum] = {} + self.functions: dict[str, ExtractedFunction] = {} + + def visit_FuncDecl(self, node: c_ast.FuncDecl): + # node.show() + logger.debug(node) + node_type = node.type + is_pointer = False + if isinstance(node.type, c_ast.PtrDecl): + node_type = node.type.type + is_pointer = True + + if hasattr(node_type, "declname"): + return_type = get_type_names(node_type.type) + if return_type is not None and is_pointer: + return_type = "*" + return_type + func: ExtractedFunction = { + 'return_type': return_type, + 'params': [], + } + for param in node.args.params: + if param.name is None: + continue + func['params'].append((param.name, get_type_names(param))) + self.functions[node_type.declname] = func + self.generic_visit(node) + + def visit_Struct(self, node: c_ast.Struct): + # node.show() + if node.name and node.decls: + struct = {} + for decl in node.decls: + struct[decl.name] = { + "type": get_type_names(decl), + } + self.structs[node.name] = struct + self.generic_visit(node) + + def visit_Typedef(self, node: c_ast.Typedef): + # node.show() + if hasattr(node.type, 'type') and hasattr(node.type.type, 'decls') and node.type.type.decls: + struct = {} + for decl in node.type.type.decls: + if hasattr(decl, 'type') and hasattr(decl.type, 'type') and isinstance(decl.type.type, c_ast.Union): + union = {} + for field in decl.type.type.decls: + union[field.name] = get_type_names(field) + struct[decl.name] = { + 'union': union + } + else: + struct[decl.name] = { + "type": get_type_names(decl), + } + self.structs[node.name] = struct + if hasattr(node.type, 'type') and isinstance(node.type.type, c_ast.Enum): + enum = {} + for enumerator in node.type.type.values.enumerators: + if enumerator.value is None: + enum[enumerator.name] = None + else: + enum[enumerator.name] = enumerator.value.value + self.enums[node.name] = enum + self.generic_visit(node) + + +def parse_headers(input_files: list[Path], output_dir: Path, tmp_dir: Path) -> ExtractedSymbols: + cpp_args = ["-nostdinc", "-D__attribute__(x)=", "-E"] + + # Make a new clay.h that combines the provided input files, so that we can add bindings for customized structs + with open(tmp_dir / 'merged_clay.h', 'w') as f: + for input_file in input_files: + with open(input_file, 'r') as f2: + for line in f2: + # Ignore includes, as they should be manually included in input_files. + if line.startswith("#include"): + continue + + # Ignore the CLAY_IMPLEMENTATION define, because we only want to parse the public api code. + # This is helpful so that the user can provide their implementation code, which will contain any custom extensions + if "#define CLAY_IMPLEMENTATION" in line: + continue + + f.write(line) + + # Preprocess the file + logger.info("Preprocessing file") + preprocessed = preprocess_file(tmp_dir / 'merged_clay.h', cpp_path="cpp", cpp_args=cpp_args) # type: ignore + with open(tmp_dir / 'clay.preprocessed.h', 'w') as f: + f.write(preprocessed) + + # Parse the file + logger.info("Parsing file") + ast = parse_file(tmp_dir / 'clay.preprocessed.h', use_cpp=False) # type: ignore + + # Extract symbols + visitor = Visitor() + visitor.visit(ast) + + result = ExtractedSymbols( + structs=visitor.structs, + enums=visitor.enums, + functions=visitor.functions + ) + return result \ No newline at end of file diff --git a/generator/requirements.txt b/generator/requirements.txt new file mode 100644 index 00000000..64dea8a9 --- /dev/null +++ b/generator/requirements.txt @@ -0,0 +1 @@ +pycparser==2.22 \ No newline at end of file