diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-02-10 00:06:34 -0700 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2025-02-10 00:06:34 -0700 |
commit | 6cd125e1ffd44fdf62c44d22c519561a8c9d7268 (patch) | |
tree | bf2b4a8225fe0d6f00084a1577c70bb564a20600 /libobj | |
parent | f466cff948ec638f26a9a77a391904ebe03c4dfb (diff) | |
parent | a51875001eb672d73c9d84d44bb32abce327b931 (diff) |
Merge branch 'lukeshu/libobj-simple'
Diffstat (limited to 'libobj')
-rw-r--r-- | libobj/CMakeLists.txt | 14 | ||||
-rw-r--r-- | libobj/include/libobj/obj.h | 154 | ||||
l--------- | libobj/tests/test.h | 1 | ||||
-rw-r--r-- | libobj/tests/test_nest.c | 73 | ||||
-rw-r--r-- | libobj/tests/test_obj.c | 61 |
5 files changed, 303 insertions, 0 deletions
diff --git a/libobj/CMakeLists.txt b/libobj/CMakeLists.txt new file mode 100644 index 0000000..1cc552c --- /dev/null +++ b/libobj/CMakeLists.txt @@ -0,0 +1,14 @@ +# libobj/CMakeLists.txt - A simple Go-ish object system built on GCC -fplan9-extensions +# +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +add_library(libobj INTERFACE) +target_include_directories(libobj SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries(libobj INTERFACE + libmisc +) +target_compile_options(libobj INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") + +add_lib_test(libobj test_obj) +add_lib_test(libobj test_nest) diff --git a/libobj/include/libobj/obj.h b/libobj/include/libobj/obj.h new file mode 100644 index 0000000..d8a528a --- /dev/null +++ b/libobj/include/libobj/obj.h @@ -0,0 +1,154 @@ +/* libobj/obj.h - A simple Go-ish object system + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBOBJ_OBJ_H_ +#define _LIBOBJ_OBJ_H_ + +#include <libmisc/macro.h> + +/** + * Use `lo_interface` similarly to how you would use + * `struct`/`enum`/`union` when writing the type of an interface + * value. + */ +#define lo_interface struct + +/** + * Use `LO_INTERFACE` in a .h file to define an interface. + * + * First define a macro named `{iface_name}_LO_IFACE` consisting of a + * series of calls to LO_NEST and/or LO_FUNC, then call + * `LO_INTERFACE({iface_name})`: + * + * #define myiface_LO_IFACE \ + * LO_NEST(wrapped_iface_name) \ + * LO_FUNC(ret_type, func_name, args...) + * LO_INTERFACE(myiface) + * + * Use `lo_interface {iface_name}` as the type of this interface; it + * should not be a pointer type. + * + * If there are any LO_NEST interfaces, this will define a + * `lo_box_{iface_name}_as_{wrapped_iface_name}(obj)` function for + * each. + */ +#define LO_NEST(_ARG_child_iface_name) \ + (lo_nest, _ARG_child_iface_name) +#define LO_FUNC(_ARG_ret_type, _ARG_func_name, ...) \ + (lo_func, _ARG_ret_type, _ARG_func_name __VA_OPT__(,) __VA_ARGS__) +#define LO_INTERFACE(_ARG_iface_name) \ + typedef struct { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_VTABLE) \ + } _lo_##_ARG_iface_name##_vtable; \ + struct _ARG_iface_name { \ + void *self; \ + const _lo_##_ARG_iface_name##_vtable *vtable; \ + }; \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IFACE_PROTO, _ARG_iface_name) +#define _LO_IFACE_VTABLE(_tuple_typ, ...) \ + _LO_IFACE_VTABLE_##_tuple_typ(__VA_ARGS__) +#define _LO_IFACE_VTABLE_lo_nest(_ARG_child_iface_name) \ + _lo_##_ARG_child_iface_name##_vtable; +#define _LO_IFACE_VTABLE_lo_func(_ARG_ret_type, _ARG_func_name, ...) \ + _ARG_ret_type (*_ARG_func_name)(void * __VA_OPT__(,) __VA_ARGS__); +#define _LO_IFACE_PROTO(_ARG_iface_name, _tuple_typ, ...) \ + _LO_IFACE_PROTO_##_tuple_typ(_ARG_iface_name, __VA_ARGS__) +#define _LO_IFACE_PROTO_lo_nest(_ARG_iface_name, _ARG_child_iface_name) \ + LM_ALWAYS_INLINE static lo_interface _ARG_child_iface_name \ + box_##_ARG_iface_name##_as_##_ARG_child_iface_name(lo_interface _ARG_iface_name obj) { \ + return (lo_interface _ARG_child_iface_name){ \ + .self = obj.self, \ + .vtable = &obj.vtable->_lo_##_ARG_child_iface_name##_vtable, \ + }; \ + } +#define _LO_IFACE_PROTO_lo_func(_ARG_iface_name, _ARG_ret_type, _ARG_func_name, ...) \ + /* empty */ + +/** + * `LO_NULL(iface_name)` is the null/nil/zero value for `lo_interface {iface_name}`. + */ +#define LO_NULL(_ARG_iface_name) ((lo_interface _ARG_iface_name){0}) + +/** + * `LO_IS_NULL(iface_val)` returns whether `iface_val` is LO_NULL. + */ +#define LO_IS_NULL(_ARG_iface_val) ((_ARG_iface_val).vtable == NULL) + +/** + * `LO_IFACE_EQ(a, b)` returns whether the interface values `a` and + * `b` are the same object. + */ +#define LO_EQ(_ARG_iface_val_a, _ARG_iface_val_b) \ + ((_ARG_iface_val_a).self == (_ARG_iface_val_b).self) + +/** + * Use LO_CALL(obj, method_name, args...) to call a method on an `lo_interface`. + */ +#define LO_CALL(_ARG_obj, _ARG_meth, ...) \ + (_ARG_obj).vtable->_ARG_meth((_ARG_obj).self __VA_OPT__(,) __VA_ARGS__) + +/** + * Use `LO_IMPLEMENTATION_H(iface_name, impl_type, impl_name)` in a .h + * file to declare that `{impl_type}` implements the `{iface_name}` + * interface with functions named `{impl_name}_{method_name}`. + * + * This will also define a `lo_box_{impl_name}_as_{iface_name}(obj)` + * function. + * + * You must also call the LO_IMPLEMENTATION_C in a single .c file. + */ +#define LO_IMPLEMENTATION_H(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name) \ + /* Vtable. */ \ + extern const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable; \ + /* Boxing. */ \ + LM_ALWAYS_INLINE static lo_interface _ARG_iface_name \ + lo_box_##_ARG_impl_name##_as_##_ARG_iface_name(_ARG_impl_type *self) { \ + return (lo_interface _ARG_iface_name){ \ + .self = self, \ + .vtable = &_lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable, \ + }; \ + } + +/** + * Use `LO_IMPLEMENTATION_C(iface_name, impl_type, impl_name[, static])` in a .c + * file to declare that `{impl_type}` implements the `{iface_name}` interface + * with functions named `{impl_name}_{method_name}`. + * + * You must also call the LO_IMPLEMENTATION_H in the corresponding .h file. + * + * If `iface_name` contains a nested interface, then the + * implementation of the nested interfaces must be declared with + * `LO_IMPLEMENTATION_C` first. + */ +#define LO_IMPLEMENTATION_C(_ARG_iface_name, _ARG_impl_type, _ARG_impl_name, ...) \ + /* Method prototypes. */ \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_PROTO, _ARG_impl_type, _ARG_impl_name, __VA_ARGS__) \ + /* Vtable. */ \ + const _lo_##_ARG_iface_name##_vtable \ + _lo_##_ARG_impl_name##_##_ARG_iface_name##_vtable = { \ + LM_FOREACH_TUPLE(_ARG_iface_name##_LO_IFACE, \ + _LO_IMPL_VTABLE, _ARG_impl_name) \ + }; \ + +#define _LO_IMPL_PROTO(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _tuple_typ, ...) \ + _LO_IMPL_PROTO_##_tuple_typ(_ARG_impl_type, _ARG_impl_name, _ARG_quals, __VA_ARGS__) +#define _LO_IMPL_PROTO_lo_nest(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_child_iface_name) \ + /* empty */ +#define _LO_IMPL_PROTO_lo_func(_ARG_impl_type, _ARG_impl_name, _ARG_quals, _ARG_ret_type, _ARG_func_name, ...) \ + _ARG_quals _ARG_ret_type _ARG_impl_name##_##_ARG_func_name(_ARG_impl_type * __VA_OPT__(,) __VA_ARGS__); + +#define _LO_IMPL_VTABLE(_ARG_impl_name, _tuple_typ, ...) \ + _LO_IMPL_VTABLE_##_tuple_typ(_ARG_impl_name, __VA_ARGS__) +#define _LO_IMPL_VTABLE_lo_nest(_ARG_impl_name, _ARG_child_iface_name) \ + ._lo_##_ARG_child_iface_name##_vtable = _lo_##_ARG_impl_name##_##_ARG_child_iface_name##_vtable, +#define _LO_IMPL_VTABLE_lo_func(_ARG_impl_name, _ARG_ret_type, _ARG_func_name, ...) \ + ._ARG_func_name = (void*)_ARG_impl_name##_##_ARG_func_name, + +#endif /* _LIBOBJ_OBJ_H_ */ diff --git a/libobj/tests/test.h b/libobj/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libobj/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libobj/tests/test_nest.c b/libobj/tests/test_nest.c new file mode 100644 index 0000000..c9f9eba --- /dev/null +++ b/libobj/tests/test_nest.c @@ -0,0 +1,73 @@ +/* libobj/tests/test_nest.c - Tests for <libobj/obj.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libobj/obj.h> + +#include "test.h" + +/* interfaces *****************************************************************/ + +#define reader_LO_IFACE \ + LO_FUNC(ssize_t, read, void *, size_t) +LO_INTERFACE(reader) + +#define writer_LO_IFACE \ + LO_FUNC(ssize_t, write, void *, size_t) +LO_INTERFACE(writer) + +#define read_writer_LO_IFACE \ + LO_NEST(reader) \ + LO_NEST(writer) +LO_INTERFACE(read_writer) + +/* implementation header ******************************************************/ + +struct myclass { + size_t len; + char buf[512]; +}; +LO_IMPLEMENTATION_H(reader, struct myclass, myclass) +LO_IMPLEMENTATION_H(writer, struct myclass, myclass) +LO_IMPLEMENTATION_H(read_writer, struct myclass, myclass) + +/* implementation main ********************************************************/ + +LO_IMPLEMENTATION_C(reader, struct myclass, myclass, static) +LO_IMPLEMENTATION_C(writer, struct myclass, myclass, static) +LO_IMPLEMENTATION_C(read_writer, struct myclass, myclass, static) + +static ssize_t myclass_read(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (count > self->len) + count = self->len; + memcpy(buf, self->buf, count); + return count; +} + +static ssize_t myclass_write(struct myclass *self, void *buf, size_t count) { + test_assert(self); + if (self->len) + return -1; + if (count > sizeof(self->buf)) + count = sizeof(self->buf); + memcpy(self->buf, buf, count); + self->len = count; + return count; +} + +/* main test body *************************************************************/ + +int main() { + struct myclass _obj = {0}; + lo_interface read_writer obj = lo_box_myclass_as_read_writer(&_obj); + test_assert(LO_CALL(obj, write, "Hello", 6) == 6); + char buf[6] = {0}; + test_assert(LO_CALL(obj, read, buf, 3) == 3); + test_assert(memcmp(buf, "Hel\0\0\0", 6) == 0); + return 0; +} diff --git a/libobj/tests/test_obj.c b/libobj/tests/test_obj.c new file mode 100644 index 0000000..89fff68 --- /dev/null +++ b/libobj/tests/test_obj.c @@ -0,0 +1,61 @@ +/* libobj/tests/test_obj.c - Tests for <libobj/obj.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libobj/obj.h> + +#include "test.h" + +/* `lo_inteface frobber` header ***********************************************/ + +#define frobber_LO_IFACE \ + /** Basic function. */ \ + LO_FUNC(int, frob) \ + /** Function that takes 1 argument. */ \ + LO_FUNC(int, frob1, int) \ + /** Function that returns nothing. */ \ + LO_FUNC(void, frob0) +LO_INTERFACE(frobber) + +/* `struct myclass` header ****************************************************/ + +struct myclass { + int a; +}; +LO_IMPLEMENTATION_H(frobber, struct myclass, myclass) + +/* `struct myclass` implementation ********************************************/ + +LO_IMPLEMENTATION_C(frobber, struct myclass, myclass, static) + +static int myclass_frob(struct myclass *self) { + test_assert(self); + return self->a; +} + +static int myclass_frob1(struct myclass *self, int arg) { + test_assert(self); + return arg; +} + +static void myclass_frob0(struct myclass *self) { + test_assert(self); +} + +/* main test body *************************************************************/ + +#define MAGIC1 909837 +#define MAGIC2 657441 + +int main() { + struct myclass obj = { + .a = MAGIC1, + }; + lo_interface frobber iface = lo_box_myclass_as_frobber(&obj); + test_assert(LO_CALL(iface, frob) == MAGIC1); + test_assert(LO_CALL(iface, frob1, MAGIC2) == MAGIC2); + LO_CALL(iface, frob0); + return 0; +} |