New trait library.

This commit is contained in:
H. Utku Maden 2024-03-24 12:03:10 +03:00
parent 41476ed89b
commit 1bb9a9cff7
3 changed files with 430 additions and 227 deletions

@ -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

219
include/mx/trait.h Normal file

@ -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 <code>Hello World!</code> 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

@ -4,214 +4,172 @@
#include <stdlib.h>
#include <string.h>
static mx_stream_flags mx_stdin_flags(mx_stream_t str)
typedef struct mx_file_t {
FILE *f;
} mx_file_t;
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);
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);
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
};
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)
{
return MX_STREAM_OPEN | (feof(stdin) ? MX_STREAM_EOF : 0);
return sizeof(self);
}
static mx_len_t mx_stdin_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)
{
return (mx_len_t)fread(dst, 1, (size_t)size, stdin);
const char in[] = "stdin";
const char out[] = "stdout";
const char err[] = "stderr";
if (self->f == stdin)
{
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 snprintf(buffer, max, "FILE* %p", self->f);
}
}
MX_IMPL mx_stream_t mx_get_stdin(void)
static void mx_file_IObject_destruct(mx_file_t *self)
{
const static mx_stream_base_t str = {
.flags = mx_stdin_flags,
.read = mx_stdin_read,
.write = NULL,
.seek = NULL,
.close = NULL
};
return &str;
fatptr_vcall(fat_new(self, fat_vtable(mx_file_t, IStream), IStream), close);
free(self);
}
static mx_stream_flags mx_stdxxx_flags(mx_stream_t str)
static mx_stream_flags mx_file_IStream_get_flags(mx_file_t *self)
{
return MX_STREAM_OPEN;
}
if (self->f == NULL)
return MX_STREAM_EOF;
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);
}
mx_stream_flags flags = MX_STREAM_OPEN;
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;
flags |= !!feof(self->f) * MX_STREAM_EOF;
return flags;
}
static mx_len_t mx_stdc_read(mx_stream_t str, void *dst, mx_len_t size)
static mx_len_t mx_file_IStream_read(mx_file_t *self, char *buffer, mx_len_t max)
{
FILE *f = THIS_FILE(str);
return (mx_len_t)fread(dst, 1, (size_t)size, f);
return fread(buffer, 1, max, self->f);
}
static mx_len_t mx_stdc_write(mx_stream_t str, const void *src, mx_len_t size)
static mx_len_t mx_file_IStream_seek(mx_file_t *self, mx_len_t offset, IStream_SeekOrigin origin)
{
FILE *f = THIS_FILE(str);
return (mx_len_t)fwrite(src, 1, (size_t)size, f);
return fseek(self->f, offset, origin);
}
static mx_len_t mx_stdc_seek(mx_stream_t str, mx_len_t offset, mx_seek_origin origin)
static mx_len_t mx_file_IStream_write(mx_file_t *self, const char *buffer, mx_len_t max)
{
FILE *f = THIS_FILE(str);
int stdc_origin;
return fwrite(buffer, 1, max, self->f);
}
switch (origin)
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)
{
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;
fd.f = stdin;
init = true;
}
fseek(f, offset, stdc_origin);
return (mx_len_t)ftell(f);
return fat_cast(fd, mx_file_t, IStream);
}
static void mx_stdc_close(mx_stream_t str)
MX_API fatptr_t(IStream) mx_get_stdout(void)
{
FILE *f = THIS_FILE(str);
static bool init = false;
static mx_file_t fd;
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
};
MX_API mx_stream_t mx_open(const char *file, mx_open_flags flags)
{
MX_ASSERT_PTR(file, "File path cannot be null.");
char mode[16] = "b";
if (flags & MX_OPEN_READ)
if (!init)
{
strcat(mode, "r");
fd.f = stdout;
init = true;
}
if (flags & MX_OPEN_WRITE)
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)
{
strcat(mode, "w");
fd.f = stderr;
init = true;
}
if (flags & MX_OPEN_APPEND)
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)
{
strcat(mode, "a");
return fat_new(NULL, *NULL, IStream);
}
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;
}
MX_IMPL mx_len_t mx_read(mx_stream_t str, void *dst, mx_len_t size)
{
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);
}
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)
{
return str->flags(str);
}
else
{
return 0;
}
}
MX_IMPL void mx_close(mx_stream_t str)
{
if (!str) return;
if (!str->close) return;
str->close(str);
self->f = fopen(file, options);
return fat_new(self, fat_vtable(mx_file_t, IStream), IStream);
}