From 1bb9a9cff7bd89c6f3d4a2cb1ce60840aed3ca3e Mon Sep 17 00:00:00 2001 From: "H. Utku Maden" Date: Sun, 24 Mar 2024 12:03:10 +0300 Subject: [PATCH] New trait library. --- include/mx/io/stream.h | 94 +++++++---- include/mx/trait.h | 219 ++++++++++++++++++++++++++ src/stream.stdc.c | 344 ++++++++++++++++++----------------------- 3 files changed, 430 insertions(+), 227 deletions(-) create mode 100644 include/mx/trait.h diff --git a/include/mx/io/stream.h b/include/mx/io/stream.h index b22cd09..fe03371 100644 --- a/include/mx/io/stream.h +++ b/include/mx/io/stream.h @@ -2,6 +2,62 @@ #define _MX_IO_STREAM_H_ #include "mx/base.h" +#include "mx/trait.h" + +typedef enum IStream_SeekOrigin +{ + ISTREAM_SEEK_START, + ISTERAM_SEEK_CURRENT, + ISTREAM_SEEK_END, +} IStream_SeekOrigin; + +typedef enum mx_stream_flags +{ + MX_STREAM_OPEN = 1 << 0, + MX_STREAM_EOF = 1 << 1, +} mx_stream_flags; + +typedef struct IStream +{ + IObject Object; + + mx_stream_flags (*get_flags)(void *self); + mx_len_t (*read)(void *self, char *buffer, mx_len_t max); + mx_len_t (*seek)(void *self, mx_len_t offset, IStream_SeekOrigin origin); + mx_len_t (*write)(void* self, const void *src, mx_len_t size); + void (*close)(void *self); +} IStream; +fatptr_define(IStream); + +MX_INLINE fatptr_t(IObject) IStream_AsIObject(fatptr_t(IStream) str) +{ + return fat_new(str.ptr, str.traits->Object, IObject); +} + +MX_INLINE mx_stream_flags IStream_flags(fatptr_t(IStream) str) +{ + return fatptr_vcall(str, get_flags); +} + +MX_INLINE mx_len_t IStream_read(fatptr_t(IStream) str, char *buffer, mx_len_t max) +{ + return fatptr_vcall(str, read, buffer, max); +} + +MX_INLINE mx_len_t IStream_seek(fatptr_t(IStream) str, mx_len_t offset, IStream_SeekOrigin origin) +{ + return fatptr_vcall(str, seek, offset, origin); +} + +MX_INLINE mx_len_t IStream_write(fatptr_t(IStream) str, const void *buffer, mx_len_t size) +{ + return fatptr_vcall(str, write, buffer, size); +} + +MX_INLINE void IStream_close(fatptr_t(IStream) str) +{ + fatptr_vcall(str, close); +} typedef enum mx_open_flags { @@ -11,43 +67,13 @@ typedef enum mx_open_flags MX_OPEN_NEW = 1 << 3, } mx_open_flags; -typedef enum mx_seek_origin -{ - MX_SEEK_START, - MX_SEEK_CURRENT, - MX_SEEK_END, -} mx_seek_origin; - -typedef enum mx_stream_flags -{ - MX_STREAM_OPEN = 1 << 0, - MX_STREAM_EOF = 1 << 1, -} mx_stream_flags; - -typedef struct mx_stream_base_t -{ - mx_stream_flags (*flags)(mx_stream_t str); - mx_len_t (*read)(mx_stream_t str, void *dst, mx_len_t size); - mx_len_t (*write)(mx_stream_t str, const void *src, mx_len_t size); - mx_len_t (*seek)(mx_stream_t str, mx_len_t offset, mx_seek_origin origin); - void (*close)(mx_stream_t str); - -} mx_stream_base_t; -typedef const mx_stream_base_t* mx_stream_t; - -MX_API mx_stream_t mx_get_stdin(void); -MX_API mx_stream_t mx_get_stdout(void); -MX_API mx_stream_t mx_get_stderr(void); -MX_API mx_stream_t mx_open(const char *file, mx_open_flags flags); -MX_API mx_stream_flags mx_sflags(mx_stream_t str); -MX_API mx_len_t mx_read(mx_stream_t str, void *dst, mx_len_t size); -MX_API mx_len_t mx_write(mx_stream_t str, const void *dst, mx_len_t size); -MX_API mx_len_t mx_seek(mx_stream_t str, mx_len_t offset, mx_seek_origin origin); -MX_API void mx_close(mx_stream_t str); +MX_API fatptr_t(IStream) mx_get_stdin(void); +MX_API fatptr_t(IStream) mx_get_stdout(void); +MX_API fatptr_t(IStream) mx_get_stderr(void); +MX_API fatptr_t(IStream) mx_open(const char *file, mx_open_flags flags); #define mx_stdin (mx_get_stdin()) #define mx_stdout (mx_get_stdout()) #define mx_stderr (mx_get_stderr()) - #endif \ No newline at end of file diff --git a/include/mx/trait.h b/include/mx/trait.h new file mode 100644 index 0000000..5f58949 --- /dev/null +++ b/include/mx/trait.h @@ -0,0 +1,219 @@ +#ifndef _MX_TRAIT_H_ +#define _MX_TRAIT_H_ + +/** + * @file trait.h Interface Traits and Fat Pointers + * + * This is the MX interface library. Interfaces define common traits C and C++ + * objects can have. This functionality is exposed through a fat pointer, which + * is just two pointers: the object reference and its virtual table (vtable). + * + * @section new_interface Creating an Interface + * The convention in the library is to prefix an interface with an I, and use + * Pascal case naming conventions for the interface structure. + * + * Define the interface like any other C structure, except, only include function + * pointers and other interfaces in it. Then use `fatptr_define` to create a fat + * pointer for the given interface. + * + * @code + * typedef struct IFoo { + * IObject Object; // Base interface. + * void (*bar)(void *self); // Virtual method objects can implement. + * } IFoo; + * + * fatptr_define(IFoo); + * @endcode + * + * Now, in order to create a fat pointer with this interface, you can use the + * generic type fatptr_t(T). Notice the parenthesis, because this is a C + * library. + * + * A suggestion is to create inline static functions right after this interface + * declaration to aid calling the virtual methods of the trait defined. + * + * @code + * MX_INLINE void IFoo_bar(fatptr_t(IFoo) ptr) { + * fatptr_vcall(ptr, bar); + * } + * @endcode + * + * In order to implement these traits, create a source code file, and implement + * the trait functions as you like. These functions can be static. Make sure + * the virtual methods and the implementations have the same function signature. + * + * Then, create a vtable for the object using the fat_vtable(type, trait), and + * refrerence the implemented functions. + * + * @code + * typedef struct MyFoo + * { + * // Private implementation data. + * } MyFoo; + * + * static void MyFoo_IFoo_bar(void *self) + * { + * printf("Hello World!\n"); + * } + * + * IFoo fat_vtable(MyFoo, IFoo) = { MyFoo_IFoo_bar }; + * @endcode + * + * Now you have sucessfully implemented this trait. Now let's see an example + * of how this is used. + * + * @code + * void FooConsumer(fatptr_t(IFoo) foo) + * { + * fat_vcall(foo, bar); + * } + * + * int main(void) + * { + * MyFoo foo = {}; + * fatptr_t(IFoo) foo_trait = fat_cast(foo, MyFoo, IFoo); + * FooConsumer(foo_trait); + * } + * @endcode + * + * If everything went right, this should print Hello World! and exit. + * + * @section fat_functions "fat" Functions + * The structure fatptr_t(T) serves as the main type for this library, where + * T is the interface that defines the object traits. Ideally all objects should + * implement the IObject trait, but this isn't enforced at all. + * + * You can use fat_new(ptr, vtable, trait) in your code to create a fat pointer + * to the object "ptr", with a "vtable" (the implementation of the traits + * defined by the interface) for the interface "trait". + * + * You can use fat_cast(object, type, trait) to cast the value "object" of + * "type" to a fat pointer with traits "trait". This will look up the object + * vtable and create a fat pointer automatically. + * + * fat_reinterpret(value, trait) will reinterpret the fatptr "value" a + * fatptr_t(trait). This type of cast is only valid as long as the first member + * of the source fat pointer interface is also an interface. + * + * fat_vnill(fatptr, vmethod) checks if a virtual method "vmethod" of the fat + * pointer is nill. This is mostly useful for optional traits. + * + * fat_vcall(fatptr, vmethod, ...) will invoke the "vmethod" of "fatptr". This + * is a GNU-C macro so your mileage may vary using it on niche compilers. + */ + +#include "mx/base.h" + +/** + * Fat pointer to any object. + */ +#define fatptr_t(trait) struct __mx_fatptr_##trait##_t + +/** + * @brief Define the fat pointer structure. + * @param base Base pointer type + */ +#define fatptr_define(trait) fatptr_t(trait) { void *ptr; trait *traits; } + +/** + * Fat pointer vtable for object #type for traits #trait +*/ +#define fat_vtable(type, trait) __mx_fat_vtable_##type##_##trait + +/** + * Create a fat pointer to #ptr, with #vtable of #trait. + */ +#define fat_new(ptr, vtable, trait) ((fatptr_t(trait)){ (ptr), &(vtable)}) + +/** + * Cast a value #object of type #type into fat pointer of #trait. + */ +#define fat_cast(object, type, trait) fat_new(&(object), fat_vtable(type, trait), trait) + +/** + * Reinterpret a fatptr into another fatptr. +*/ +#define fat_reinterpret(value, trait) (*((fatptr_t(trait)*)&(value))) + +/** + * True if vmethod is null. + */ +#define fat_vnil(fatptr, vmethod) ((fatptr)->traits.vmethod == NULL) + +/** + * Base traits of all MX objects. + */ +typedef struct IObject +{ + /** + * Get the size of this object in bytes. + * @param self Instance object. + * @return The size of the object in bytes. + */ + size_t (*get_size)(void *self); + /** + * Get the type object for this. + * @param self Instance object. + */ + void * (*get_type)(void *self); + /** + * Convert this object to a string. + * @param self Instance object. + * @param[out] buffer The buffer to write the string into. + * @param[in] max The maximum number of bytes to write. + * @return The size of the written string. + */ + size_t (*to_string)(void *self, char *buffer, size_t max); + /** + * Destructor function for this object. + * @param self Instance object. + */ + void (*destruct)(void* self); +} IObject; +fatptr_define(IObject); + +// This is GNU C :/ + +#define fatptr_vcall(fatptr, vmethod, ...) ((fatptr).traits->vmethod(fatptr.ptr, ## __VA_ARGS__)) + +/** + * Get the size of this object in bytes. + * @param self Instance object. + * @return The size of the object in bytes. + */ +MX_INLINE size_t IObject_get_size(fatptr_t(IObject) obj) +{ + return fatptr_vcall(obj, get_size); +} + +/** + * Get the type object for this. + * @param self Instance object. + */ +MX_INLINE void* IObject_get_type(fatptr_t(IObject) obj) +{ + return fatptr_vcall(obj, get_type); +} + +/** + * Convert this object to a string. + * @param self Instance object. + * @param[out] buffer The buffer to write the string into. + * @param[in] max The maximum number of bytes to write. + * @return The size of the written string. + */ +MX_INLINE size_t IObject_to_string(fatptr_t(IObject) obj, char *buffer, size_t max) +{ + return fatptr_vcall(obj, to_string, buffer, max); +} + +/** + * Destructor function for this object. + * @param self Instance object. + */ +MX_INLINE void IObject_destruct(fatptr_t(IObject) obj) +{ + return fatptr_vcall(obj, destruct); +} + +#endif diff --git a/src/stream.stdc.c b/src/stream.stdc.c index dd3aa6b..644d632 100644 --- a/src/stream.stdc.c +++ b/src/stream.stdc.c @@ -4,214 +4,172 @@ #include #include -static mx_stream_flags mx_stdin_flags(mx_stream_t str) -{ - return MX_STREAM_OPEN | (feof(stdin) ? MX_STREAM_EOF : 0); -} +typedef struct mx_file_t { + FILE *f; +} mx_file_t; -static mx_len_t mx_stdin_read(mx_stream_t str, void *dst, mx_len_t size) -{ - return (mx_len_t)fread(dst, 1, (size_t)size, stdin); -} +static size_t mx_file_IObject_get_size(mx_file_t *self); +static size_t mx_file_IObject_to_string(mx_file_t *self, char *buffer, size_t max); +static void mx_file_IObject_destruct(mx_file_t *self); -MX_IMPL mx_stream_t mx_get_stdin(void) -{ - const static mx_stream_base_t str = { - .flags = mx_stdin_flags, - .read = mx_stdin_read, - .write = NULL, - .seek = NULL, - .close = NULL - }; +static mx_stream_flags mx_file_IStream_get_flags(mx_file_t *self); +static mx_len_t mx_file_IStream_read(mx_file_t *self, char *buffer, mx_len_t max); +static mx_len_t mx_file_IStream_seek(mx_file_t *self, mx_len_t offset, IStream_SeekOrigin origin); +static mx_len_t mx_file_IStream_write(mx_file_t *self, const char *buffer, mx_len_t max); +static void mx_file_IStream_close(mx_file_t *self); - return &str; -} - -static mx_stream_flags mx_stdxxx_flags(mx_stream_t str) -{ - return MX_STREAM_OPEN; -} - -static mx_len_t mx_stdout_write(mx_stream_t str, const void *src, mx_len_t size) -{ - return (mx_len_t)fwrite(src, 1, (size_t)size, stdout); -} - -static mx_len_t mx_stderr_write(mx_stream_t str, const void *src, mx_len_t size) -{ - return (mx_len_t)fwrite(src, 1, (size_t)size, stderr); -} - -MX_IMPL mx_stream_t mx_get_stdout(void) -{ - const static mx_stream_base_t str = { - .flags = mx_stdxxx_flags, - .write = mx_stdout_write, - .read = NULL, - .seek = NULL, - .close = NULL - }; - - return &str; -} - -MX_IMPL mx_stream_t mx_get_stderr(void) -{ - const static mx_stream_base_t str = { - .flags = mx_stdxxx_flags, - .write = mx_stderr_write, - .read = NULL, - .seek = NULL, - .close = NULL - }; - - return &str; -} - -typedef struct mx_stdc_stream_t -{ - mx_stream_base_t base; - FILE *file; -} mx_stdc_stream_t; - -#define THIS_FILE(x) (((mx_stdc_stream_t*)(str))->file) - -static mx_stream_flags mx_stdc_flags(mx_stream_t str) -{ - mx_stream_flags flags = 0; - FILE *f = THIS_FILE(str); - - if (fileno(f) != -1) - flags |= MX_STREAM_OPEN; - if (feoff(f)) - flags |= MX_STREAM_EOF; - - return flags; -} - -static mx_len_t mx_stdc_read(mx_stream_t str, void *dst, mx_len_t size) -{ - FILE *f = THIS_FILE(str); - return (mx_len_t)fread(dst, 1, (size_t)size, f); -} - -static mx_len_t mx_stdc_write(mx_stream_t str, const void *src, mx_len_t size) -{ - FILE *f = THIS_FILE(str); - return (mx_len_t)fwrite(src, 1, (size_t)size, f); -} - -static mx_len_t mx_stdc_seek(mx_stream_t str, mx_len_t offset, mx_seek_origin origin) -{ - FILE *f = THIS_FILE(str); - int stdc_origin; - - switch (origin) - { - case MX_SEEK_START: stdc_origin = SEEK_SET; break; - case MX_SEEK_CURRENT: stdc_origin = SEEK_CUR; break; - case MX_SEEK_END: stdc_origin = SEEK_END; break; - } - - fseek(f, offset, stdc_origin); - return (mx_len_t)ftell(f); -} - -static void mx_stdc_close(mx_stream_t str) -{ - FILE *f = THIS_FILE(str); - - fclose(f); - free((void*)str); -} - -const static mx_stream_base_t mx_stdc_stream_base = -{ - .flags = mx_stdc_flags, - .read = mx_stdc_read, - .write = mx_stdc_write, - .seek = mx_stdc_seek, - .close = mx_stdc_close +const IObject fat_vtable(mx_file_t, IObject) = { + .get_size = mx_file_IObject_get_size, + .get_type = NULL, + .to_string = mx_file_IObject_to_string, + .destruct = mx_file_IObject_destruct }; -MX_API mx_stream_t mx_open(const char *file, mx_open_flags flags) +const IStream fat_vtable(mx_file_t, IStream) = { + .Object = { + .get_size = mx_file_IObject_get_size, + .get_type = NULL, + .to_string = mx_file_IObject_to_string, + .destruct = mx_file_IObject_destruct + }, + .get_flags = mx_file_IStream_get_flags, + .read = mx_file_IStream_read, + .seek = mx_file_IStream_seek, + .write = mx_file_IStream_write, + .close = mx_file_IStream_close, +}; + +static size_t mx_file_IObject_get_size(mx_file_t *self) { - MX_ASSERT_PTR(file, "File path cannot be null."); - - char mode[16] = "b"; - - if (flags & MX_OPEN_READ) - { - strcat(mode, "r"); - } - - if (flags & MX_OPEN_WRITE) - { - strcat(mode, "w"); - } - - if (flags & MX_OPEN_APPEND) - { - strcat(mode, "a"); - } - - FILE *f = fopen(file, mode); - - if (f == NULL) - return NULL; - - mx_stdc_stream_t *str = malloc(sizeof(*str)); - memcpy(&str->base, &mx_stdc_stream_base, sizeof mx_stdc_stream_base); - - if (!(flags & (MX_OPEN_APPEND|MX_OPEN_WRITE))) - { - str->base.write = NULL; - } - - return str; + return sizeof(self); } -MX_IMPL mx_len_t mx_read(mx_stream_t str, void *dst, mx_len_t size) +static size_t mx_file_IObject_to_string(mx_file_t *self, char *buffer, size_t max) { - MX_ASSERT_SELFPTR(str); - MX_ASSERT_PTR(dst, "Destination cannot be null"); - MX_ASSERT(size >= 0, "Must be non-negative."); - MX_ASSERT(str->read, "Stream has no reader."); - return str->read(str, dst, size); -} + const char in[] = "stdin"; + const char out[] = "stdout"; + const char err[] = "stderr"; -MX_IMPL mx_len_t mx_write(mx_stream_t str, void *dst, mx_len_t size) -{ - MX_ASSERT_SELFPTR(str); - MX_ASSERT_PTR(dst, "Source cannot be null"); - MX_ASSERT(size >= 0, "Must be non-negative."); - MX_ASSERT(str->write, "Stream has no writer."); - return str->write(str, dst, size); -} - -MX_IMPL mx_len_t mx_seek(mx_stream_t str, mx_len_t offset, mx_seek_origin origin) -{ - MX_ASSERT_SELFPTR(str); - MX_ASSERT(str->seek, "Stream has no seeker."); - return str->seek(str, offset, origin); -} - -MX_IMPL mx_stream_flags mx_sflags(mx_stream_t str) -{ - MX_ASSERT_SELFPTR(str); - if (str->flags) + if (self->f == stdin) { - return str->flags(str); + strncpy(buffer, in, max); + return max > sizeof(in) ? sizeof(in) : max; + } + else if (self->f == stdout) + { + strncpy(buffer, out, max); + return max > sizeof(out) ? sizeof(out) : max; + } + else if (self->f == stderr) + { + strncpy(buffer, err, max); + return max > sizeof(err) ? sizeof(err) : max; } else { - return 0; + return snprintf(buffer, max, "FILE* %p", self->f); } } -MX_IMPL void mx_close(mx_stream_t str) +static void mx_file_IObject_destruct(mx_file_t *self) { - if (!str) return; - if (!str->close) return; - str->close(str); -} \ No newline at end of file + fatptr_vcall(fat_new(self, fat_vtable(mx_file_t, IStream), IStream), close); + free(self); +} + +static mx_stream_flags mx_file_IStream_get_flags(mx_file_t *self) +{ + if (self->f == NULL) + return MX_STREAM_EOF; + + mx_stream_flags flags = MX_STREAM_OPEN; + + flags |= !!feof(self->f) * MX_STREAM_EOF; + + return flags; +} + +static mx_len_t mx_file_IStream_read(mx_file_t *self, char *buffer, mx_len_t max) +{ + return fread(buffer, 1, max, self->f); +} + +static mx_len_t mx_file_IStream_seek(mx_file_t *self, mx_len_t offset, IStream_SeekOrigin origin) +{ + return fseek(self->f, offset, origin); +} + +static mx_len_t mx_file_IStream_write(mx_file_t *self, const char *buffer, mx_len_t max) +{ + return fwrite(buffer, 1, max, self->f); +} + +static void mx_file_IStream_close(mx_file_t *self) +{ + if (self->f == NULL) + return; + else if (self->f != stdin || self->f != stdout || self->f != stderr) + fclose(self->f); + + self->f = NULL; +} + +MX_API fatptr_t(IStream) mx_get_stdin(void) +{ + static bool init = false; + static mx_file_t fd; + + if (!init) + { + fd.f = stdin; + init = true; + } + + return fat_cast(fd, mx_file_t, IStream); +} + +MX_API fatptr_t(IStream) mx_get_stdout(void) +{ + static bool init = false; + static mx_file_t fd; + + if (!init) + { + fd.f = stdout; + init = true; + } + + return fat_cast(fd, mx_file_t, IStream); +} + +MX_API fatptr_t(IStream) mx_get_stderr(void) +{ + static bool init = false; + static mx_file_t fd; + + if (!init) + { + fd.f = stderr; + init = true; + } + + return fat_cast(fd, mx_file_t, IStream); +} + +MX_API fatptr_t(IStream) mx_open(const char *file, mx_open_flags flags) +{ + char options[6] = "b"; + if (flags & MX_OPEN_READ) strcat(options, "r"); + if (flags & MX_OPEN_WRITE) strcat(options, "w"); + if (flags & MX_OPEN_APPEND) strcat(options, "a"); + if (flags & MX_OPEN_NEW) strcat(options, "s"); + + mx_file_t *self = malloc(sizeof(mx_file_t)); + if (!self) + { + return fat_new(NULL, *NULL, IStream); + } + + self->f = fopen(file, options); + return fat_new(self, fat_vtable(mx_file_t, IStream), IStream); +}