Compare commits

...
3 Commits
Author SHA1 Message Date
dcrubro ee3aa1e488 fix crash on macos for dev 2026-08-28 23:31:23 +02:00
dcrubro ecc792bcb6 command 2026-08-28 23:01:03 +02:00
dcrubro e6eba80150 commands 2026-08-28 22:57:07 +02:00
16 changed files with 1872 additions and 10527 deletions
+3 -1
View File
@@ -1,4 +1,6 @@
.env
build/
.DS_Store
config.json
config.json
.vscode
.vscode/
-17
View File
@@ -1,17 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/include",
"${workspaceFolder}/third_party"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"intelliSenseMode": "linux-clang-x64"
}
],
"version": 4
}
+61 -5
View File
@@ -337,7 +337,14 @@ if(HAMMY_ENABLE_LTO)
endif()
# ---------------------------------------------------------
# Concord (Discord API wrapper), fetched from the dev branch
# Concord (Discord API wrapper), pinned to the v3.0.1 release
#
# Pinned rather than tracking dev: dev reverted the notifier's portable fcntl
# setup back to ioctl(FIONBIO), cast to int. macOS FIONBIO is 0x8004667E, so the
# cast goes negative, sign-extends into ioctl's unsigned long parameter and the
# call fails -- ccord_global_init() then dies before the client is ever built.
# The release tags still carry the fcntl version. Check that the regression is
# gone before moving this back to a branch.
#
# Concord ships a hand-written Makefile, not a CMake build, so this is a
# two-stage arrangement: FetchContent clones it at configure time (recursively,
@@ -358,7 +365,7 @@ include(ExternalProject)
FetchContent_Declare(
concord
GIT_REPOSITORY https://github.com/Cogmasters/concord.git
GIT_TAG dev
GIT_TAG v3.0.1
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
@@ -395,7 +402,11 @@ else()
# absolute paths and name the two failure modes that actually occur.
#
# The globs run at install time rather than configure time because
# gencodecs/discord_codecs.h does not exist until the build has generated it.
# generated/discord_codecs.h does not exist until the build has emitted it.
#
# Keep the directory list below in step with the `install:` target in
# upstream's Makefile; v3.0.1 renamed gencodecs/ to generated/ and split
# reflect-c.h out into its own directory.
set(CONCORD_INSTALL_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/concord_install.cmake")
file(CONFIGURE
OUTPUT "${CONCORD_INSTALL_SCRIPT}"
@@ -407,7 +418,7 @@ file(MAKE_DIRECTORY "@CONCORD_INCLUDE_DIR@/concord" "@CONCORD_PREFIX@/lib")
# them one at a time: a single empty directory is the interesting failure, and a
# combined glob would hide it behind whichever siblings still matched.
set(_hdrs "")
foreach(_dir include core gencodecs)
foreach(_dir include core generated)
file(GLOB _found "@concord_SOURCE_DIR@/${_dir}/*.h")
if(NOT _found)
message(FATAL_ERROR
@@ -416,8 +427,53 @@ foreach(_dir include core gencodecs)
endif()
list(APPEND _hdrs ${_found})
endforeach()
# Not part of any of the three directories, but generated/discord_codecs.h
# includes it, so discord.h does not parse without it.
if(NOT EXISTS "@concord_SOURCE_DIR@/reflect-c/reflect-c.h")
message(FATAL_ERROR
"Concord: missing @concord_SOURCE_DIR@/reflect-c/reflect-c.h -- the "
"reflect-c submodule was not cloned; remove build/_deps and reconfigure")
endif()
list(APPEND _hdrs "@concord_SOURCE_DIR@/reflect-c/reflect-c.h")
file(COPY ${_hdrs} DESTINATION "@CONCORD_INCLUDE_DIR@/concord")
# third_party/concord is a checked-in mirror of the headers just installed. It
# exists so an editor has something to resolve <concord/*.h> against in a fresh
# clone, before anything has been built -- build/ is gitignored, so the copy
# above is not there yet.
#
# It is refreshed from the same file list on every build rather than left to be
# updated by hand, because it also sits FIRST on hammy's include path
# (-isystem third_party precedes the installed headers). A stale mirror would
# not merely confuse the editor, it would be what the compiler actually reads --
# hammy would build against one version of Concord and link another. Keeping the
# two byte-identical makes the ordering irrelevant. Moving GIT_TAG therefore
# shows up as a diff under third_party/concord; commit it along with the bump.
#
# Prune first: a version bump can retire a header, and one left behind in the
# mirror would still be found by the compiler. Only files that vanished upstream
# are removed -- file(COPY) preserves timestamps and skips files already
# matching, so mirroring costs one stat per header on a build that changed
# nothing. The exception is discord_codecs.h, which Concord's own build
# regenerates every time (BUILD_ALWAYS re-runs make); its mtime moves, so it is
# recopied. That does not add a rebuild -- the regenerated original already
# forces one -- and its contents are deterministic, so git stays clean.
set(_want "")
foreach(_hdr IN LISTS _hdrs)
get_filename_component(_name "${_hdr}" NAME)
list(APPEND _want "${_name}")
endforeach()
file(GLOB _mirrored "@CMAKE_CURRENT_SOURCE_DIR@/third_party/concord/*.h")
foreach(_old IN LISTS _mirrored)
get_filename_component(_name "${_old}" NAME)
if(NOT "${_name}" IN_LIST _want)
file(REMOVE "${_old}")
endif()
endforeach()
file(COPY ${_hdrs} DESTINATION "@CMAKE_CURRENT_SOURCE_DIR@/third_party/concord")
file(GLOB _libs "@concord_SOURCE_DIR@/lib/libdiscord.*")
if(NOT _libs)
message(FATAL_ERROR "Concord: build produced no library in @concord_SOURCE_DIR@/lib")
@@ -559,7 +615,7 @@ message(STATUS "hammy: sanitizers [Debug/Strict] ${HAMMY_SANITIZER_FLAGS}")
message(STATUS "hammy: static analyzer [Analyzer] ${HAMMY_ANALYZER_FLAGS}")
message(STATUS "hammy: hardening ${HAMMY_ENABLE_HARDENING}")
message(STATUS "hammy: LTO ${HAMMY_ENABLE_LTO}")
message(STATUS "hammy: concord ${concord_SOURCE_DIR} (branch dev)")
message(STATUS "hammy: concord ${concord_SOURCE_DIR} (v3.0.1)")
if(CMAKE_BUILD_TYPE STREQUAL "Analyzer" AND NOT HAMMY_ANALYZER_FLAGS)
message(STATUS "hammy: NOTE - Analyzer config has no static analyzer on "
"${CMAKE_C_COMPILER_ID}; use scan-build over this build tree")
+56 -9
View File
@@ -1,18 +1,65 @@
#ifndef HAMMY_COMMAND_H
#define HAMMY_COMMAND_H
#include <stdlib.h>
#include <concord/discord.h>
#include <concord/log.h>
#include <stdbool.h>
#include <hammy/types.h>
// A command handler. Runs either on the gateway thread (instant commands) or on
// a worker thread (deferred ones), so it must not assume either. client is
// whichever client is correct for the calling thread; use it and nothing else.
//
// Instant handlers send a fresh interaction response.
// Deferred handlers EDIT the already-deferred response, via hammy_job_reply()
// or discord_edit_original_interaction_response().
//
// The handler does NOT own the job and must not destroy it.
typedef void (*hammy_command_fn)(const hammy_job_t* job, struct discord* client);
// Plain old data. Every field points at static storage, so this struct owns
// nothing, copies freely, and needs no destructor - the vector can be created
// with a NULL destructor hook.
struct hammy_command_t {
// TODO: add shit here
char empty;
const char* name; // Discord command name. Not owning; expected to be a literal.
const char* description; // Not owning.
// Registration options, or NULL for none. Not owning; point at a static
// struct discord_application_command_options, e.g.
//
// static struct discord_application_command_option grid_opt_array[] = {
// { .type = DISCORD_APPLICATION_OPTION_STRING, .name = "locator",
// .description = "Maidenhead grid", .required = true },
// };
// static struct discord_application_command_options grid_opts = {
// .size = 1, .array = grid_opt_array
// };
struct discord_application_command_options* options;
hammy_command_fn handler;
// true -> pure computation; answered inline on the gateway thread, no
// defer, no queue. Must complete in microseconds.
// false -> may block (HTTP, DB, file IO); deferred and queued to a worker.
//
// Marking a blocking command instant stalls the gateway and risks missed
// heartbeats. When unsure, leave it false.
bool instant;
};
// Creates the command on the heap, returns NULL if failed.
hammy_command_t* hammy_command_create();
// Destroys a command.
void hammy_command_destroy(void* cmd);
// The built-in command table. Returns a pointer to static storage; outCount
// receives the number of entries. Never NULL.
const hammy_command_t* hammy_command_builtins(size_t* outCount);
// Linear lookup by name over a plain array. The table is small enough that
// anything cleverer is not worth the code.
// Returns NULL if not found.
const hammy_command_t* hammy_command_find(const hammy_command_t* commands, size_t count,
const char* name);
// True if the command is safe to run inline on the gateway thread.
// Tolerates NULL so callers can skip a null check.
bool hammy_command_is_instant(const hammy_command_t* command);
#endif
-6
View File
@@ -17,12 +17,6 @@ hammy_bot_t* hammy_bot_create() {
return NULL;
}
if (vector_set_destructor(bot->commands, &hammy_command_destroy) < 0) {
vector_destroy(&bot->commands);
free(bot);
return NULL;
}
// Init the discord client - even though this is an owning reference, the main() and others may call functions on it (if we don't stay singlethread anymore then FIX THIS)
bot->client = discord_from_json("config.json");
if (!bot->client) {
+48 -17
View File
@@ -1,24 +1,55 @@
#include <stddef.h>
#include <string.h>
#include <hammy/command.h>
hammy_command_t* hammy_command_create() {
hammy_command_t* cmd = (hammy_command_t*)malloc(sizeof(hammy_command_t));
if (!cmd) {
return NULL;
}
// TODO: set defaults
return cmd;
// Handlers live in src/hammy/commands/. Declared here rather than in a header
// so adding a command touches exactly two places: its own .c file and this
// table.
void hammy_cmd_ping(const hammy_job_t* job, struct discord* client) {
(void)job;
(void)client;
log_info("[command] ping");
}
void hammy_command_destroy(void* cmd) {
if (!cmd) {
return;
// The command table. Pure data - no allocation, no lifetime, no destructor.
// Copying an entry into the bot's vector is a plain struct copy, and the vector
// may be created with a NULL destructor hook.
//
// Field order: name, description, options, handler, instant.
static const hammy_command_t hammy_builtin_commands[] = {
{ "ping", "Check whether Hammy is alive and how fast it is responding",
NULL, &hammy_cmd_ping, true },
// Tier 0 commands go here as they land. All pure computation, so instant:
// { "grid", "Maidenhead locator conversions", &grid_opts, &hammy_cmd_grid, true },
// { "morse", "Text to and from Morse code", &morse_opts, &hammy_cmd_morse, true },
// { "band", "What band is a frequency in", &band_opts, &hammy_cmd_band, true },
//
// Anything touching the backend API, a database or the network is NOT
// instant:
// { "call", "Look up a callsign", &call_opts, &hammy_cmd_call, false },
};
const hammy_command_t* hammy_command_builtins(size_t* outCount) {
if (outCount)
*outCount = sizeof(hammy_builtin_commands) / sizeof(hammy_builtin_commands[0]);
return hammy_builtin_commands;
}
const hammy_command_t* hammy_command_find(const hammy_command_t* commands, size_t count,
const char* name) {
if (!commands || !name) return NULL;
for (size_t i = 0; i < count; i++) {
if (commands[i].name && strcmp(commands[i].name, name) == 0)
return &commands[i];
}
cmd = (hammy_command_t*)cmd;
// TODO: Other things here eventually
free(cmd);
return NULL;
}
bool hammy_command_is_instant(const hammy_command_t* command) {
return command && command->instant;
}
-26
View File
@@ -1,26 +0,0 @@
#include "custom.PRE.h"
#include "application.PRE.h"
#include "audit_log.PRE.h"
#include "auto_moderation.PRE.h"
#include "invite.PRE.h"
#include "channel.PRE.h"
#include "emoji.PRE.h"
#include "guild.PRE.h"
#include "guild_scheduled_event.PRE.h"
#include "guild_template.PRE.h"
#include "stage_instance.PRE.h"
#include "sticker.PRE.h"
#include "user.PRE.h"
#include "voice.PRE.h"
#include "webhook.PRE.h"
#include "gateway.PRE.h"
#include "oauth2.PRE.h"
#include "permissions.PRE.h"
#include "teams.PRE.h"
#include "voice_connections.PRE.h"
#include "application_commands.PRE.h"
#include "message_components.PRE.h"
#include "interactions.PRE.h"
+53 -33
View File
@@ -31,59 +31,75 @@ extern "C" {
#include "priority_queue.h"
#include "attributes.h"
/** @brief Return 1 if string isn't considered empty */
#define NOT_EMPTY_STR(str) ((str) && *(str))
/**
* @brief Get container `type` from a field `ptr`
* @brief return 1 if string isn't empty
*
* @param[in] ptr the field contained in `type`
* @param[in] type the container datatype
* @param[in] path the path to the field from the container POV
* @param[in] _str the string to check
*/
#define CONTAINEROF(ptr, type, path) \
((type *)((char *)(ptr) - offsetof(type, path)))
#define NOT_EMPTY_STR(_str) ((_str) && *(_str))
/**
* @brief get container `type` from a field `ptr`
*
* @param[in] _ptr the field contained in `type`
* @param[in] _type the container datatype
* @param[in] _path the path to the field from the container POV
*/
#define CONTAINEROF(_ptr, _type, _path) \
((_type *)((char *)(_ptr) - offsetof(_type, _path)))
/** @defgroup DiscordInternal Internal implementation details
* @brief Documentation useful when developing or debugging Concord itself
* @{ */
/** @brief cet client from its nested field */
#define CLIENT(ptr, path) CONTAINEROF(ptr, struct discord, path)
/**
* @brief get client from its nested field
*
* @param[in] _ptr the nested field pointer
* @param[in] _path the path to the discord client from the nested field POV
*/
#define CLIENT(_ptr, _path) CONTAINEROF(_ptr, struct discord, _path)
/**
* @brief log and return `code` if `expect` condition is false
*
* @param[in] client the Discord client
* @param[in] expect the expected outcome
* @param[in] code return CCORDcode error code
* @param[in] reason for return
* @param[in] _client the Discord client
* @param[in] _expect the expected outcome
* @param[in] _code return CCORDcode error code
* @param[in] _reason for return
* @return the provided @ref CCORDcode `code` parameter
*/
#define CCORD_EXPECT(client, expect, code, reason) \
#define CCORD_EXPECT(_client, _expect, _code, _reason) \
do { \
if (!(expect)) { \
logmod_log(ERROR, (client)->logger, \
"Expected: " #expect " | %s (%s)", \
discord_strerror(code, client), \
discord_code_as_string(code)); \
return code; \
if (!(_expect)) { \
logmod_log(ERROR, (_client)->logger, \
"Expected: " #_expect " | %s (%s)", \
discord_strerror(_code, _client), \
discord_code_as_string(_code)); \
return _code; \
} \
} while (0)
/**
* @brief log and return `code` if function call doesn't returns @ref CCORD_OK
*
* @param[in] client the Discord client
* @param[in] fn the function call that returns CCORDcode
* @param[in] _client the Discord client
* @param[in] _type the datatype to be converted to JSON
* @param[out] _body the JSON body buffer
* @param[in] _params the parameters to be converted to JSON
* @return the returned @ref CCORDcode `code` parameter
*/
#define CCORD_EXPECT_OK(client, fn) \
#define CCORD_DATA_TO_JSON(_client, _type, _body, _params) \
do { \
const CCORDcode code = (fn); \
struct reflectc_wrap *w_params = \
reflectc_from_##_type((_client)->registry, _params, NULL); \
CCORDcode code = discord_data_wrap_to_json(w_params, &(_body)->start, \
&(_body)->size); \
reflectc_cleanup((_client)->registry, w_params); \
if (code != CCORD_OK) { \
logmod_log(ERROR, (client)->logger, \
"Expected: CCORD_OK == " #fn " | %s (%s)", \
discord_strerror(code, client), \
logmod_log(ERROR, (_client)->logger, \
"Expected: CCORD_OK == discord_data_wrap_to_json | " \
"%s (%s)", \
discord_strerror(code, _client), \
discord_code_as_string(code)); \
return code; \
} \
@@ -421,11 +437,9 @@ struct discord_ret_response {
/** size of datatype in bytes */
size_t size;
/** initializer function for datatype fields */
void (*init)(void *data);
/** populate datatype with JSON values */
size_t (*from_json)(const char *json, size_t len, void *data);
/** cleanup function for datatype */
void (*cleanup)(void *data);
struct reflectc_wrap *(*init)(struct reflectc *registry,
void *data,
struct reflectc_wrap *root);
};
/**
@@ -1191,6 +1205,12 @@ struct discord_cache {
* @see discord_init(), discord_config_init(), discord_cleanup()
*/
struct discord {
/**
* the registry for all wrapped discord data types
* @note keep as first member for casting to `struct reflectc`
* @see DiscordDataWrap
*/
struct reflectc *registry;
/** `DISCORD` logging module */
struct logmod_logger *logger;
/** LogMod loggers table */
+30 -49
View File
@@ -12,78 +12,59 @@
typedef void (*cast_done_typed)(struct discord *,
struct discord_response *,
const void *);
typedef void (*cast_init)(void *);
typedef void (*cast_cleanup)(void *);
typedef size_t (*cast_from_json)(const char *, size_t, void *);
typedef struct reflectc_wrap *(*cast_init)(struct reflectc *,
void *,
struct reflectc_wrap *);
/* helper typedef for getting sizeof of `struct discord_ret` common fields */
typedef struct {
DISCORD_RET_DEFAULT_FIELDS;
} discord_ret_default_fields;
#define _RET_COPY_TYPED(dest, src) \
#define _RET_COPY_TYPED(_dest, _src) \
do { \
memcpy(&(dest), &(src), sizeof(discord_ret_default_fields)); \
(dest).has_type = true; \
(dest).done.typed = (cast_done_typed)(src).done; \
(dest).sync = (src).sync; \
memcpy(&(_dest), &(_src), sizeof(discord_ret_default_fields)); \
(_dest).has_type = true; \
(_dest).done.typed = (cast_done_typed)(_src).done; \
(_dest).sync = (_src).sync; \
} while (0)
#define _RET_COPY_TYPELESS(dest, src) \
#define _RET_COPY_TYPELESS(_dest, _src) \
do { \
memcpy(&(dest), &(src), sizeof(discord_ret_default_fields)); \
(dest).has_type = false; \
(dest).done.typeless = (src).done; \
(dest).sync = (void *)(src).sync; \
memcpy(&(_dest), &(_src), sizeof(discord_ret_default_fields)); \
(_dest).has_type = false; \
(_dest).done.typeless = (_src).done; \
(_dest).sync = (void *)(_src).sync; \
} while (0)
/**
* @brief Helper for setting attributes for a specs-generated return struct
*
* @param[out] attr @ref discord_attributes handler to be initialized
* @param[in] type datatype of the struct
* @param[in] ret dispatch attributes
* @param[out] _attr @ref discord_attributes handler to be initialized
* @param[in] _type datatype of the struct
* @param[in] _ret dispatch attributes
* @param[in] _reason reason for request (if available)
*/
#define DISCORD_ATTR_INIT(attr, type, ret, _reason) \
#define DISCORD_ATTR_INIT(_attr, _type, _ret, _reason) \
do { \
(attr).response.size = sizeof(struct type); \
(attr).response.init = (cast_init)type##_init; \
(attr).response.from_json = (cast_from_json)type##_from_json; \
(attr).response.cleanup = (cast_cleanup)type##_cleanup; \
(attr).reason = _reason; \
if (ret) _RET_COPY_TYPED(attr.dispatch, *ret); \
(_attr).response.size = sizeof(struct _type); \
(_attr).response.init = (cast_init)reflectc_from_##_type; \
(_attr).reason = _reason; \
if (_ret) _RET_COPY_TYPED((_attr).dispatch, *(_ret)); \
} while (0)
/**
* @brief Helper for setting attributes for a specs-generated list
*
* @param[out] attr @ref discord_attributes handler to be initialized
* @param[in] type datatype of the list
* @param[in] ret dispatch attributes
* @param[in] _reason reason for request (if available)
*/
#define DISCORD_ATTR_LIST_INIT(attr, type, ret, _reason) \
do { \
(attr).response.size = sizeof(struct type); \
(attr).response.from_json = (cast_from_json)type##_from_json; \
(attr).response.cleanup = (cast_cleanup)type##_cleanup; \
(attr).reason = _reason; \
if (ret) _RET_COPY_TYPED(attr.dispatch, *ret); \
} while (0)
/**
* @brief Helper for setting attributes for attruests that doensn't expect a
* @brief Helper for setting attributes for attributes that don't have a
* response object
*
* @param[out] attr @ref discord_attributes handler to be initialized
* @param[in] ret dispatch attributes
* @param[out] _attr @ref discord_attributes handler to be initialized
* @param[in] _ret dispatch attributes
* @param[in] _reason reason for request (if available)
*/
#define DISCORD_ATTR_BLANK_INIT(attr, ret, _reason) \
#define DISCORD_ATTR_BLANK_INIT(_attr, _ret, _reason) \
do { \
(attr).reason = _reason; \
if (ret) _RET_COPY_TYPELESS(attr.dispatch, *ret); \
(_attr).reason = _reason; \
if (_ret) _RET_COPY_TYPELESS((_attr).dispatch, *(_ret)); \
} while (0)
/**
@@ -91,10 +72,10 @@ typedef struct {
*
* @param[in,out] attchs a @ref discord_attachments to have its IDs initialized
*/
#define DISCORD_ATTACHMENTS_IDS_INIT(attchs) \
#define DISCORD_ATTACHMENTS_IDS_INIT(_attchs) \
do { \
for (int i = 0; i < attchs->size; ++i) { \
attchs->array[i].id = (u64snowflake)i; \
for (int i = 0; i < (_attchs)->size; ++i) { \
(_attchs)->array[i].id = (u64snowflake)i; \
} \
} while (0)
+215
View File
@@ -37,8 +37,17 @@ extern "C" {
/* forward declaration */
struct discord;
struct reflectc;
/**/
/**
* @brief Get the reflectc registry from a Discord client
*
* @param client the client created with discord_from_json() or discord_from_token()
* @return the reflectc registry used by the client
*/
struct reflectc *discord_get_registry(struct discord *client);
#include "discord_codecs.h"
#include "discord-response.h"
@@ -209,6 +218,12 @@ void discord_shutdown_all(void);
*/
bool discord_shutdown_all_ongoing(void);
/**
* @brief Backwards compatible alias for discord_shutdown_all()
* @deprecated since v3.0.0
*/
#define ccord_shutdown_async() discord_shutdown_all()
/**
* @brief Creates a Discord Client handle from a token
* @see discord_get_logmod() to configure logging behavior
@@ -235,6 +250,15 @@ struct discord *discord_from_json(const char config_file[]);
struct discord_config {
/** the bot token */
char *token;
/**
* optional override for the REST API base URL
* (e.g. `"http://127.0.0.1:8080"` for a local test server)
* @note when `NULL` (the default) @ref DISCORD_API_BASE_URL is used
* @note only read during client initialization and copied by the
* User-Agent layer; the client does not take ownership and will
* never free this pointer
*/
char *base_url;
struct {
/** minimum logging level */
enum logmod_levels level;
@@ -427,6 +451,197 @@ struct logmod *discord_get_logmod(struct discord *client);
*/
struct io_poller *discord_get_io_poller(struct discord *client);
/** @addtogroup DiscordDataWrap Data Wrap
* @brief Helpers for wrapping Discord data types for easier management
* @{ */
#define __CAT(_a, _b) _a##_b
#define _CAT(_a, _b) __CAT(_a, _b)
#define _EXPECT_CONTAINER__struct
#define _EXPECT_CONTAINER__union
#define _DISCORD_SYMBOL_WITHOUT_CONTAINER(_symbol) \
_CAT(_EXPECT_CONTAINER__, _symbol)
/**
* @brief The Discord data wrap structure
*
* This struct is used to wrap Discord data types for easier management
*/
#define discord_data_wrap reflectc_wrap
/**
* @brief Wrap a Discord data type into a reflectc_wrap structure
*
* @param _symbol the Discord data type symbol (with struct or union)
* @param _client the client created with discord_from_token()
* @param _data the Discord data type to be wrapped
* @return the @ref discord_data_wrap Discord data type
*/
#define discord_data_wrap_from(_symbol, _client, _data) \
_CAT(reflectc_from_, _DISCORD_SYMBOL_WITHOUT_CONTAINER(_symbol))( \
discord_get_registry((_client)), _data, NULL)
/**
* @brief Cleanup a Discord data type wrapped into a reflectc_wrap structure
*
* Releases everything discord_data_from_json() allocated for `_data` —
* strings, nested objects, list arrays — plus the reflect-c wrap and its
* registry entry. Every non-NULL pointer member is treated as owned: do
* not call this on hand-built structs pointing at literals or stack
* objects.
*
* @param _client the client created with discord_from_token()
* @param _data the Discord data type to be cleaned up
*/
#define discord_data_cleanup(_client, _data) \
discord_data_free(discord_get_registry((_client)), (_data))
/** Function backing @ref discord_data_cleanup; see its contract */
void discord_data_free(struct reflectc *registry, void *data);
/**
* @brief Drop the wrap cached for `_data` without touching its contents
*
* discord_data_to_json() caches a reflect-c wrap keyed by `_data`'s
* address; release it with this once done encoding a caller-owned
* struct (e.g. stack-built params). Without it the stale registry entry
* would be wrongly reused by a future object at the same address.
* Decoded structs don't need this — discord_data_cleanup() releases
* both the data and the wrap.
*
* @param _client the client created with discord_from_token()
* @param _data the encoded Discord data type to drop the wrap for
*/
#define discord_data_unwrap(_client, _data) \
discord_data_release(discord_get_registry((_client)), (_data))
/** Function backing @ref discord_data_unwrap */
void discord_data_release(struct reflectc *registry, void *data);
/** @addtogroup DiscordDataWrapJSON JSON Conversion
* @brief Helpers for converting Discord data types to/from JSON
* @{ */
/* forward declaration */
struct jsmnf_pair;
struct jsonb;
/**
* @brief Transform a wrapped Discord data type into a JSON string
*
* @param member the wrapped Discord data type
* @param p_buf pointer to the JSON buffer
* @param p_bufsize pointer to the JSON buffer size
* @CCORD_return
*/
CCORDcode discord_data_wrap_to_json(const struct discord_data_wrap *member,
char *p_buf[],
size_t *p_bufsize);
/**
* @brief Transform a Discord data type into a JSON string
*
* @param _symbol the Discord data type symbol (with struct or union)
* @param _client the client created with discord_from_token()
* @param _data the Discord data type to be transformed
* @param _p_buf pointer to the JSON buffer
* @param _p_bufsize pointer to the JSON buffer size
* @CCORD_return
*/
#define discord_data_to_json(_symbol, _client, _data, _p_buf, _p_bufsize) \
discord_data_wrap_to_json( \
discord_data_wrap_from(_symbol, _client, _data), _p_buf, _p_bufsize)
/**
* @brief Transform a wrapped Discord data type into a jsonb handle
*
* @param jb the jsonb handle
* @param member the wrapped Discord data type
* @param p_buf pointer to the JSON buffer
* @param p_bufsize pointer to the JSON buffer size
* @CCORD_return
*/
CCORDcode discord_data_wrap_to_jsonb(struct jsonb *jb,
const struct discord_data_wrap *member,
char *p_buf[],
size_t *p_bufsize);
/**
* @brief Transform a Discord data type into a jsonb handle
*
* @param _symbol the Discord data type symbol (with struct or union)
* @param _client the client created with discord_from_token()
* @param _jb the jsonb handle
* @param _data the Discord data type to be transformed
* @param _p_buf pointer to the JSON buffer
* @param _p_bufsize pointer to the JSON buffer size
* @CCORD_return
*/
#define discord_data_to_jsonb(_symbol, _client, _jb, _data, _p_buf, \
_p_bufsize) \
discord_data_wrap_to_jsonb( \
_jb, discord_data_wrap_from(_symbol, _client, _data), _p_buf, \
_p_bufsize)
/**
* @brief Parse a JSON string and fill a wrapped Discord data type
*
* @param json the JSON string
* @param len length of @ref json
* @param root the root wrapped Discord data type
* @CCORD_return
*/
CCORDcode discord_data_wrap_from_json(const char *json,
size_t len,
struct discord_data_wrap *root);
/**
* @brief Parse a JSON string and fill a Discord data type
*
* @param _symbol the Discord data type symbol (with struct or union)
* @param _client the client created with discord_from_token()
* @param _json the JSON string
* @param _len length of @ref json
* @param _data the Discord data type to be filled
* @CCORD_return
*/
#define discord_data_from_json(_symbol, _client, _json, _len, _data) \
discord_data_wrap_from_json( \
_json, _len, discord_data_wrap_from(_symbol, _client, _data))
/**
* @brief Parse a jsmnf_pair and fill a wrapped Discord data type
*
* @param p the jsmnf_pair
* @param json the JSON string
* @param length length of @ref json
* @param member the wrapped Discord data type
* @CCORD_return
*/
CCORDcode discord_data_wrap_from_jsmnf(const struct jsmnf_pair *p,
const char *json,
size_t length,
const struct discord_data_wrap *member);
/**
* @brief Parse a jsmnf_pair and fill a Discord data type
*
* @param _client the client created with discord_from_token()
* @param _p the jsmnf_pair
* @param _json the JSON string
* @param _length length of @ref json
* @param _type the Discord data type symbol (without struct/union)
* @param _data the Discord data type to be filled
* @CCORD_return
*/
#define discord_data_from_jsmnf(_client, _p, _json, _length, _type, _data) \
discord_data_wrap_from_jsmnf( \
_p, _json, _length, discord_data_wrap_from(_type, _client, _data))
/** @} DiscordDataWrapJSON */
/** @} DiscordDataWrap */
/** @addtogroup DiscordTimer Timer
* @brief Schedule callbacks to be called in the future
* @{ */
-74
View File
@@ -1,74 +0,0 @@
#include "gencodecs.h"
#ifdef GENCODECS_HEADER
PP_INCLUDE(<inttypes.h>)
PP_INCLUDE("carray.h")
PP_INCLUDE("cog-utils.h")
PP_INCLUDE("types.h")
PP_INCLUDE("concord-error.h")
#endif
/* Custom JSON encoding macros */
#define GENCODECS_JSON_ENCODER_PTR_json_char(b, buf, size, _var, _type) \
if (0 > (code = jsonb_token_auto(b, buf, size, _var, \
_var ? strlen(_var) : 0))) \
return code
#define GENCODECS_JSON_ENCODER_size_t(b, buf, size, _var, _type) \
{ \
char tok[64]; \
int toklen; \
toklen = sprintf(tok, "%zu", _var); \
if (0 > (code = jsonb_token_auto(b, buf, size, tok, toklen))) \
return code; \
}
#define GENCODECS_JSON_ENCODER_uint64_t(b, buf, size, _var, _type) \
{ \
char tok[64]; \
int toklen; \
toklen = sprintf(tok, "%" PRIu64, _var); \
if (0 > (code = jsonb_string_auto(b, buf, size, tok, toklen))) \
return code; \
}
#define GENCODECS_JSON_ENCODER_u64snowflake GENCODECS_JSON_ENCODER_uint64_t
#define GENCODECS_JSON_ENCODER_u64bitmask GENCODECS_JSON_ENCODER_uint64_t
#define GENCODECS_JSON_ENCODER_u64unix_ms(b, buf, size, _var, _type) \
{ \
char tok[64]; \
int toklen = cog_unix_ms_to_iso8601(tok, sizeof(tok), _var); \
if (0 > (code = jsonb_string_auto(b, buf, size, tok, toklen))) \
return code; \
}
/* Custom JSON decoding macros */
#define GENCODECS_JSON_DECODER_PTR_json_char(_f, _js, _var, _type) \
if (_f) { \
_var = _gc_strndup(js + _f->v->start, _f->v->end - _f->v->start); \
ret += _f->v->end - _f->v->start; \
}
#define GENCODECS_JSON_DECODER_size_t(_f, _js, _var, _type) \
if (_f && _f->v->type == JSMN_PRIMITIVE) \
_var = (size_t)strtoull(_js + _f->v->start, NULL, 10)
#define GENCODECS_JSON_DECODER_uint64_t(_f, _js, _var, _type) \
if (_f) sscanf(_js + _f->v->start, "%" SCNu64, &_var)
#define GENCODECS_JSON_DECODER_u64snowflake GENCODECS_JSON_DECODER_uint64_t
#define GENCODECS_JSON_DECODER_u64bitmask GENCODECS_JSON_DECODER_uint64_t
#define GENCODECS_JSON_DECODER_u64unix_ms(_f, _js, _var, _type) \
if (_f && _f->v->type == JSMN_STRING) \
cog_iso8601_to_unix_ms(_js + _f->v->start, _f->v->end - _f->v->start, &_var)
/* Custom field macros */
#define FIELD_SNOWFLAKE(_name) \
FIELD_PRINTF(_name, u64snowflake, "\"%" PRIu64 "\"", "%" SCNu64)
#define FIELD_BITMASK(_name) \
FIELD_PRINTF(_name, u64bitmask, "\"%" PRIu64 "\"", "%" SCNu64)
#define FIELD_TIMESTAMP(_name) \
FIELD_CUSTOM(_name, #_name, u64unix_ms, DECOR_BLANK, INIT_BLANK, \
CLEANUP_BLANK, GENCODECS_JSON_ENCODER_u64unix_ms, \
GENCODECS_JSON_DECODER_u64unix_ms, (u64unix_ms)0)
/* if GENCODECS_READ is not specified then generate for all files */
#ifndef GENCODECS_READ
#define GENCODECS_READ "all.PRE.h"
#endif
#include "gencodecs-process.PRE.h"
+1239 -10187
View File
File diff suppressed because it is too large Load Diff
-27
View File
@@ -1,27 +0,0 @@
#ifndef GENCODECS_READ
#error "Missing GENCODECS_READ definition"
#else
#define DATA (1 << 1)
#define JSON_DECODER (1 << 2)
#define JSON_ENCODER (1 << 3)
#define JSON (JSON_DECODER | JSON_ENCODER)
#define GENCODECS_RECIPE DATA
#include "recipes/struct.h"
#undef GENCODECS_RECIPE
#define GENCODECS_RECIPE JSON_DECODER
#include "recipes/json-decoder.h"
#undef GENCODECS_RECIPE
#define GENCODECS_RECIPE JSON_ENCODER
#include "recipes/json-encoder.h"
#undef GENCODECS_RECIPE
#undef DATA
#undef JSON_DECODER
#undef JSON_ENCODER
#undef JSON
#endif /* GENCODECS_READ */
-74
View File
@@ -1,74 +0,0 @@
#ifndef GENCODECS_H
#define GENCODECS_H
/* Allow symbols usage without GENCODECS_ prefix */
#ifndef GENCODECS_USE_PREFIX
# define PP_INCLUDE GENCODECS_PP_INCLUDE
# define PP_DEFINE GENCODECS_PP_DEFINE
# define PP GENCODECS_PP
# define COND_WRITE GENCODECS_COND_WRITE
# define COND_END GENCODECS_COND_END
# define PUB_STRUCT GENCODECS_PUB_STRUCT
# define STRUCT GENCODECS_STRUCT
# define FIELD_CUSTOM GENCODECS_FIELD_CUSTOM
# define FIELD_PRINTF GENCODECS_FIELD_PRINTF
# define FIELD GENCODECS_FIELD
# define FIELD_STRUCT_PTR GENCODECS_FIELD_STRUCT_PTR
# define FIELD_PTR GENCODECS_FIELD_PTR
# define FIELD_ENUM GENCODECS_FIELD_ENUM
# define STRUCT_END GENCODECS_STRUCT_END
# define PUB_LIST GENCODECS_PUB_LIST
# define LIST GENCODECS_LIST
# define LISTTYPE GENCODECS_LISTTYPE
# define LISTTYPE_STRUCT GENCODECS_LISTTYPE_STRUCT
# define LISTTYPE_PTR GENCODECS_LISTTYPE_PTR
# define LIST_END GENCODECS_LIST_END
# define ENUM GENCODECS_ENUM
# define ENUM_END GENCODECS_ENUM_END
# define ENUMERATOR GENCODECS_ENUMERATOR
# define ENUMERATOR_LAST GENCODECS_ENUMERATOR_LAST
# define ENUMERATOR_END GENCODECS_ENUMERATOR_END
#endif /* GENCODECS_USE_PREFIX */
#ifndef GENCODECS_HEADER
# ifdef GENCODECS_DATA
GENCODECS_PP_INCLUDE(<stdio.h>)
GENCODECS_PP_INCLUDE(<stdlib.h>)
GENCODECS_PP_INCLUDE(<string.h>)
# ifdef GENCODECS_INIT
GENCODECS_PP_INCLUDE("carray.h")
# endif
# if defined(GENCODECS_JSON_DECODER) && defined(GENCODECS_FORWARD)
static char *
_gc_strndup(const char *src, size_t len)
{
char *dest = malloc(len + 1);
memcpy(dest, src, len);
dest[len] = '\0';
return dest;
}
# endif /* GENCODECS_JSON_DECODER && GENCODECS_FORWARD */
# endif /* GENCODECS_DATA */
#else
GENCODECS_PP_INCLUDE(<stddef.h>)
GENCODECS_PP_INCLUDE(<stdbool.h>)
# ifdef GENCODECS_JSON_DECODER
GENCODECS_PP_DEFINE(JSMN_STRICT)
GENCODECS_PP_DEFINE(JSMN_HEADER)
GENCODECS_PP_INCLUDE("jsmn.h")
GENCODECS_PP_INCLUDE("jsmn-find.h")
# endif
# ifdef GENCODECS_JSON_ENCODER
GENCODECS_PP_DEFINE(JSONB_HEADER)
GENCODECS_PP_INCLUDE("json-build.h")
# endif
#endif /* GENCODECS_HEADER */
#define GENCODECS_PP(_description)
#define GENCODECS_PP_DEFINE(_description)
#endif /* GENCODECS_H */
+4 -2
View File
@@ -689,8 +689,10 @@ logmod_init(struct logmod *logmod,
LOGMOD_API logmod_err
logmod_cleanup(struct logmod *logmod)
{
memset((void *)logmod->loggers, 0,
logmod->real_length * sizeof *logmod->loggers);
LOGMOD_EXPECT(logmod != NULL, LOGMOD_BAD_PARAMETER);
if (logmod->loggers)
memset((void *)logmod->loggers, 0,
logmod->real_length * sizeof *logmod->loggers);
memset(logmod, 0, sizeof *logmod);
return LOGMOD_OK;
}
+163
View File
@@ -0,0 +1,163 @@
#ifndef REFLECTC_H
#define REFLECTC_H
#include <stddef.h>
/* Allow consumers to rename the runtime namespace (`reflectc_*` /
* `REFLECTC_*`). */
#ifndef REFLECTC_PREFIX
#define REFLECTC_PREFIX reflectc
#endif /* REFLECTC_PREFIX */
#ifndef REFLECTC_PREFIX_UPPER
#define REFLECTC_PREFIX_UPPER REFLECTC
#endif /* REFLECTC_PREFIX_UPPER */
#define __CAT(_a, _b) _a##_b
#define _CAT(_a, _b) __CAT(_a, _b)
#define REFLECTC_NS(_sym) _CAT(REFLECTC_PREFIX, _sym)
#define REFLECTC_NS_UPPER(_sym) _CAT(REFLECTC_PREFIX_UPPER, _sym)
/* Forward declarations */
struct REFLECTC_PREFIX;
struct REFLECTC_NS(_template);
struct REFLECTC_NS(_wrap);
struct REFLECTC_NS(_wrap_mut);
#define REFLECTC_TYPES(_sym) REFLECTC_NS_UPPER(_TYPES__##_sym)
enum REFLECTC_NS(_types) {
REFLECTC_TYPES(void),
REFLECTC_TYPES(bool),
REFLECTC_TYPES(char),
REFLECTC_TYPES(short),
REFLECTC_TYPES(int),
REFLECTC_TYPES(long),
REFLECTC_TYPES(float),
REFLECTC_TYPES(double),
REFLECTC_TYPES(struct),
REFLECTC_TYPES(union),
REFLECTC_TYPES(enum),
REFLECTC_TYPES(EXTEND)
};
typedef struct REFLECTC_NS(_wrap)
* (*REFLECTC_NS(_from_cb))(struct REFLECTC_PREFIX *registry,
void *self,
struct REFLECTC_NS(_wrap) * root);
struct REFLECTC_NS(_template) {
const size_t size;
const struct {
const char *const buf;
const size_t length;
} qualifier, decorator, name, alias, dimensions;
const enum REFLECTC_NS(_types) type;
const unsigned long attrs;
const struct {
const size_t length;
const struct REFLECTC_NS(_template) * array;
} members;
const REFLECTC_NS(_from_cb) from_cb;
};
#define _REFLECTC_PICKER_const struct REFLECTC_NS(_wrap)
#define _REFLECTC_PICKER___BLANK__ struct REFLECTC_NS(_wrap_mut)
#define REFLECTC_INSTANCE_FIELDS(_qualifier) \
_qualifier size_t length; \
struct REFLECTC_PREFIX *_qualifier registry; \
_qualifier void *_qualifier ptr_value; \
_qualifier struct { \
_qualifier size_t length; \
_qualifier _REFLECTC_PICKER_##_qualifier *_qualifier array; \
} members
struct REFLECTC_NS(_wrap) {
const struct REFLECTC_NS(_template) * tmpl;
REFLECTC_INSTANCE_FIELDS(const);
};
#define __BLANK__
struct REFLECTC_NS(_wrap_mut) {
const struct REFLECTC_NS(_template) * tmpl;
REFLECTC_INSTANCE_FIELDS(__BLANK__);
};
#undef __BLANK__
#undef REFLECTC_INSTANCE_FIELDS
#undef _REFLECTC_PICKER_const
#undef _REFLECTC_PICKER___BLANK__
struct REFLECTC_PREFIX *REFLECTC_NS(_init)(void);
void REFLECTC_NS(_dispose)(struct REFLECTC_PREFIX *table);
struct REFLECTC_NS(_wrap)
* REFLECTC_NS(_find)(const struct REFLECTC_PREFIX *table,
const void *value);
int REFLECTC_NS(_put)(struct REFLECTC_PREFIX *table,
void *value,
struct REFLECTC_NS(_wrap) * wrapper);
void REFLECTC_NS(_erase)(struct REFLECTC_PREFIX *table, const void *value);
size_t REFLECTC_NS(_length)(const struct REFLECTC_NS(_wrap) * member);
size_t REFLECTC_NS(_get_pos)(const struct REFLECTC_NS(_wrap) * root,
const char *const name,
const size_t len);
/* Serialized name of a member: its alias when one is declared in the
* recipe (8th tuple slot), otherwise its C member name. Use this when
* mapping members to an external format (e.g. JSON keys). */
const char *REFLECTC_NS(_alias)(const struct REFLECTC_NS(_wrap) * member,
size_t *length);
void REFLECTC_NS(_array)(const struct REFLECTC_NS(_wrap) * root,
const size_t length);
unsigned REFLECTC_NS(_get_pointer_depth)(const struct REFLECTC_NS(_wrap)
* member);
const void *REFLECTC_NS(_deref)(const struct REFLECTC_NS(_wrap) * field);
const void *REFLECTC_NS(_memcpy)(const struct REFLECTC_NS(_wrap) * field,
const void *map,
const size_t size);
const char *REFLECTC_NS(_string)(const struct REFLECTC_NS(_wrap) * dest,
const char src[],
const size_t size);
void *REFLECTC_NS(_resolve)(const struct REFLECTC_NS(_wrap) * member);
int REFLECTC_NS(_expand)(struct REFLECTC_PREFIX *registry,
const struct REFLECTC_NS(_wrap) * member);
typedef int (*REFLECTC_NS(_visit_cb))(const struct REFLECTC_NS(_wrap) * member,
void *ctx);
void REFLECTC_NS(_cleanup)(struct REFLECTC_PREFIX *registry,
struct REFLECTC_NS(_wrap) * member);
void REFLECTC_NS(_cleanup_members)(struct REFLECTC_PREFIX *registry,
struct REFLECTC_NS(_wrap) * member);
int REFLECTC_NS(_for_each)(const struct REFLECTC_NS(_wrap) * root,
REFLECTC_NS(_visit_cb) cb,
void *ctx);
const struct REFLECTC_NS(_wrap)
* REFLECTC_NS(_require_member)(const struct REFLECTC_NS(_wrap) * root,
const char *name,
size_t len);
int REFLECTC_NS(_is_pointer_type)(const struct REFLECTC_NS(_wrap) * member);
int REFLECTC_NS(_is_null)(const struct REFLECTC_NS(_wrap) * member);
int REFLECTC_NS(_expand_all)(struct REFLECTC_PREFIX *registry,
struct REFLECTC_NS(_wrap) * root);
#define REFLECTC_LOOKUP(_container, _namespace, _member_name, _root) \
(size_t)(REFLECTC_NS_UPPER(_LOOKUP__##_namespace##__##_member_name))
const void *REFLECTC_NS(_get)(const struct REFLECTC_NS(_wrap) * member);
const void *REFLECTC_NS(_set)(const struct REFLECTC_NS(_wrap) * member,
const void *value,
size_t size);
const void *REFLECTC_NS(_get_member)(const struct REFLECTC_NS(_wrap) * root,
size_t pos);
const void *REFLECTC_NS(_set_member)(const struct REFLECTC_NS(_wrap) * root,
size_t pos,
const void *value,
size_t size);
#endif /* REFLECTC_H */