#ifndef EPRINTF_HECTIC #define EPRINTF_HECTIC // NOTE(yukkop): definitions and features from the POSIX.1-2008 standard #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include // ------------- // -- Helpers -- // ------------- // Helper macros for argument counting // NOTE(yukkop): this ugly macroses for avoid all posible warnings #define PP_CAT(a, b) a##b // ------------ // -- Colors -- // ------------ // Color mode enumeration typedef enum { COLOR_MODE_AUTO, COLOR_MODE_FORCE, COLOR_MODE_DISABLE } ColorMode; // External color mode variable declaration extern ColorMode color_mode; const char* color_mode_to_string(ColorMode mode); // Function to set color mode void set_output_color_mode(ColorMode mode); // Macros for detecting terminal and color usage #define IS_TERMINAL() (isatty(fileno(stderr))) #define USE_COLOR() ((color_mode == COLOR_MODE_FORCE) || (color_mode == COLOR_MODE_AUTO && IS_TERMINAL())) #define COLOR_RED "\033[1;31m" #define COLOR_GREEN "\033[1;32m" #define COLOR_YELLOW "\033[1;33m" #define COLOR_BLUE "\033[1;34m" #define COLOR_MAGENTA "\033[1;35m" #define COLOR_CYAN "\033[1;36m" #define COLOR_WHITE "\033[1;37m" #define COLOR_RESET "\033[0m" #define OPTIONAL_COLOR(color) (USE_COLOR() ? color : "") // ------------ // -- Errors -- // ------------ // Define color macros based on output type //#define ERROR_PREFIX PP_CAT(COLOR_RED, "Error: ") //#define ERROR_SUFFIX PP_CAT(COLOR_RESET, "\n") #define ERROR_PREFIX (USE_COLOR() ? "\033[1;31mError: " : "Error: ") #define ERROR_SUFFIX (USE_COLOR() ? "\033[0m\n" : "\n") // eprintf handling 1 or more arguments #define eprintf(fmt, ...) "%s" fmt "%s", ERROR_PREFIX, ##__VA_ARGS__, ERROR_SUFFIX #define todo fprintf(stderr, "%sNot implimented yet%s", COLOR_RED, COLOR_RESET);exit(1) // ------------ // -- Logger -- // ------------ /** * Log levels following a consistent severity-based hierarchy. * Each level includes specific guidance on when it should be used. */ typedef enum { /** * TRACE: Most detailed information for in-depth debugging * Use for: Deep diagnostic details, function entry/exit, variable dumps * Visibility: Development environments only, rarely used in production */ LOG_LEVEL_TRACE, /** * DEBUG: Detailed information useful during development * Use for: Development-time debugging, showing variable states, internal flows * Visibility: Development and debugging environments, rarely in production */ LOG_LEVEL_DEBUG, /** * LOG: General operational events * Use for: Runtime events worth logging but not requiring attention * Visibility: Always written to logs, useful for auditing/diagnostics */ LOG_LEVEL_LOG, /** * INFO: Informational messages highlighting progress * Use for: Normal but noteworthy events, state changes, startup/shutdown events * Visibility: Visible to client applications if configured */ LOG_LEVEL_INFO, /** * NOTICE: More important events than INFO, but not warnings * Use for: Important state changes, significant operations, configuration changes * Visibility: Displayed to client by default, meant to be seen */ LOG_LEVEL_NOTICE, /** * WARN: Potential problems that don't prevent normal operation * Use for: Unexpected behaviors, deprecated feature usage, recoverable errors * Visibility: Alerts both client and server logs, needs attention */ LOG_LEVEL_WARN, /** * EXCEPTION: Serious errors requiring immediate attention * Use for: Critical failures, data loss risks, business rule violations * Visibility: Highest priority, often leads to operation termination */ LOG_LEVEL_EXCEPTION } LogLevel; /** * Structure for complex log level rule * Allows specifying log levels per file, function, and line range */ typedef struct LogRule { LogLevel level; // Log level for this rule char *file_pattern; // File pattern to match (can be NULL) char *function_pattern; // Function pattern to match (can be NULL) int line_start; // Start line number (-1 for any) int line_end; // End line number (-1 for any) struct LogRule *next; // Next rule in the chain } LogRule; void logger_level_reset(); void init_logger(void); void logger_level(LogLevel level); LogLevel log_level_from_string(const char *level_str); /** * Set complex logging rules from a string * Format: DEFAULT_LEVEL,@=LEVEL,@:=LEVEL,... * Example: "INFO,main.c@main=DEBUG,helper.c@10:50=TRACE" * * @param rules_str The rule string to parse * @return 1 on success, 0 on failure */ int logger_parse_rules(const char *rules_str); /** * Set complex logging rule programmatically * * @param level Log level for this rule * @param file_pattern File pattern to match (NULL for any file) * @param function_pattern Function pattern to match (NULL for any function) * @param line_start Start line number (-1 for any) * @param line_end End line number (-1 for any) * @return 1 on success, 0 on failure */ int logger_add_rule(LogLevel level, const char *file_pattern, const char *function_pattern, int line_start, int line_end); /** * Get the effective log level for a message based on complex rules * * @param file Source file where log was generated * @param func Function where log was generated * @param line Line number where log was generated * @return The effective log level for this context */ LogLevel logger_get_effective_level(const char *file, const char *func, int line); /** * Core logging function that formats and outputs log messages. * * @param level Severity level of the message * @param file Source file where log was generated * @param func Function where log was generated * @param line Line number where log was generated * @param format Printf-style format string * @param ... Variable arguments for format string * @return Timestamp string for the log message */ char* raise_message(LogLevel level, const char *file, const char *func, int line, const char *format, ...); #ifndef PRECOMPILED_LOG_LEVEL #define PRECOMPILED_LOG_LEVEL LOG_LEVEL_TRACE // default level #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_TRACE #define raise_trace(...) ((void)0) // log removed at compile time #else #define raise_trace(...) raise_message(LOG_LEVEL_TRACE, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_DEBUG #define raise_debug(...) ((void)0) #else #define raise_debug(...) raise_message(LOG_LEVEL_DEBUG, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_LOG #define raise_log(...) ((void)0) #else #define raise_log(...) raise_message(LOG_LEVEL_LOG, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_INFO #define raise_info(...) ((void)0) #else #define raise_info(...) raise_message(LOG_LEVEL_INFO, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_NOTICE #define raise_notice(...) ((void)0) #else #define raise_notice(...) raise_message(LOG_LEVEL_NOTICE, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_WARN #define raise_warn(...) ((void)0) #else #define raise_warn(...) raise_message(LOG_LEVEL_WARN, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif #if PRECOMPILED_LOG_LEVEL > LOG_LEVEL_EXCEPTION #define raise_exception(...) ((void)0) #else #define raise_exception(...) raise_message(LOG_LEVEL_EXCEPTION, __FILE__, __func__, __LINE__, ##__VA_ARGS__) #endif // ---------- // -- misc -- // ---------- #define MEM_b 1 #define MEM_KiB 1024 #define MEM_MiB (MEM_KiB * 1024) #define MEM_GiB (MEM_MiB * 1024) #define MEM_TiB (MEM_TiB * 1024) #define MEM_PiB (MEM_TiB * 1024) #define MEM_EiB (MEM_PiB * 1024) #define MEM_ZiB (MEM_EiB * 1024) #define MEM_YiB (MEM_ZiB * 1024) #define MEM_RiB (MEM_YiB * 1024) #define MEM_QiB (MEM_RiB * 1024) void substr_clone__(const char *file, const char *func, int line, const char * const src, char *dest, size_t from, size_t len); #define substr_clone(src, dest, from, len) substr_clone__(__FILE__, __func__, __LINE__, src, dest, from, len) // ----------- // -- arena -- // ----------- #define ARENA_DEFAULT_SIZE MEM_MiB typedef struct { void *begin; void *current; size_t capacity; } Arena; Arena arena_init__(const char *file, const char *func, int line, size_t size); void* arena_alloc_or_null__(const char *file, const char *func, int line, Arena *arena, size_t size); void* arena_alloc__(const char *file, const char *func, int line, Arena *arena, size_t size); void arena_reset__(const char *file, const char *func, int line, Arena *arena); void arena_free__(const char *file, const char *func, int line, Arena *arena); char* arena_strdup__(const char *file, const char *func, int line, Arena *arena, const char *s); char* arena_repstr__(const char *file, const char *func, int line, Arena *arena, const char *src, size_t start, size_t len, const char *rep); void* arena_realloc_copy__(const char *file, const char *func, int line, Arena *arena, void *old_ptr, size_t old_size, size_t new_size); // NOTE(yukkop): This macro is used to define procedures so that `__LINE__` and `__FILE__` // in `raise_debug` reflect the location where the macro is called, not where it's defined. #define arena_alloc_or_null(arena, size) \ arena_alloc_or_null__(__FILE__, __func__, __LINE__, arena, size) #define arena_init(size) \ arena_init__(__FILE__, __func__, __LINE__, size) #define arena_reset(arena) \ arena_reset__(__FILE__, __func__, __LINE__, arena) #define arena_free(arena) \ arena_free__(__FILE__, __func__, __LINE__, arena) #define arena_alloc(arena, size) \ arena_alloc__(__FILE__, __func__, __LINE__, arena, size) #define arena_strdup(arena, s) \ arena_strdup__(__FILE__, __func__, __LINE__, arena, s) #define arena_repstr(arena, src, start, len, rep) \ arena_repstr__(__FILE__, __func__, __LINE__, arena, src, start, len, rep) #define arena_realloc_copy(arena, old_ptr, old_size, new_size) \ arena_realloc_copy__(__FILE__, __func__, __LINE__, arena, old_ptr, old_size, new_size) static Arena disposable_arena __attribute__((unused)) = {0}; #define DISPOSABLE_ARENA __extension__ ({ \ if (disposable_arena.begin == NULL) { \ disposable_arena = arena_init__(__FILE__, __func__, __LINE__, MEM_MiB); \ } else { \ arena_reset(&disposable_arena); \ } \ &disposable_arena; \ }) // ---------- // -- Json -- // ---------- typedef enum { JSON_NORAW = 0, JSON_RAW = 1, } JsonRawOpt; typedef enum { JSON_NULL, JSON_BOOL, JSON_NUMBER, JSON_STRING, JSON_ARRAY, JSON_OBJECT, } JsonType; /* Full JSON structure */ typedef struct Json { struct Json *next; /* Next sibling */ struct Json *child; /* Child element (for arrays/objects) */ JsonType type; char *key; /* Key if item is in an object */ union { double number; char *string; int boolean; } JsonValue; } Json; #define json_parse(arena, s) json_parse__(__FILE__, __func__, __LINE__, arena, s) Json *json_parse__(const char* file, const char* func, int line, Arena *arena, const char **s); #define json_to_string(arena, item) json_to_string__(__FILE__, __func__, __LINE__, arena, item) char *json_to_string__(const char* file, const char* func, int line, Arena *arena, const Json * const item); #define json_to_string_with_opts(arena, item, raw) json_to_string_with_opts__(__FILE__, __func__, __LINE__, arena, item, raw) char *json_to_string_with_opts__(const char* file, const char* func, int line, Arena *arena, const Json * const item, JsonRawOpt raw); /* Retrieve an object item by key (case-sensitive) */ #define json_get_object_item(object, key) json_get_object_item__(__FILE__, __func__, __LINE__, object, key) Json *json_get_object_item__(const char* file, const char* func, int line, const Json * const object, const char * const key); // ----------- // -- Slice -- // ----------- typedef struct { void *data; size_t len; size_t isize; } Slice; // Usage: // printf("Content: %.*s\n", SLICE_ARGS(slice, char)); // printf("Content: %d\n", SLICE_ARGS(slice, int)); #define SLICE_ARGS(slice, type) ((int)((slice).len / sizeof(type))), ((type*)((slice).data)) Slice slice_create__(const char *file, const char *func, int line, size_t isize, void *array, size_t array_len, size_t start, size_t len); Slice slice_subslice__(const char *file, const char *func, int line, Slice s, size_t start, size_t len); int* arena_slice_copy__(const char *file, const char *func, int line, Arena *arena, Slice s); #define slice_create(type, array, array_len, start, len) \ slice_create__(__FILE__, __func__, __LINE__, sizeof(type), array, array_len, start, len) #define slice_subslice(s, start, len) \ slice_subslice__(__FILE__, __func__, __LINE__, s, start, len) #define arena_slice_copy(arena, s) \ arena_slice_copy__(__FILE__, __func__, __LINE__, arena, s) #define SLICE_TO_STRING(type, slice, fmt) __extension__ ({ \ size_t count = (slice).len / (slice).isize; \ size_t bufsize = count * 32 + 1; \ char *buf = malloc(bufsize); \ if (buf) { \ buf[0] = '\0'; \ for (size_t i = 0; i < count; i++) { \ char temp[32]; \ snprintf(temp, sizeof(temp), fmt " ", \ ((type *)((slice).data))[i]); \ strncat(buf, temp, bufsize - strlen(buf) - 1); \ } \ } \ buf; \ }) // ------------ // -- Debug -- // ------------ // Utility functions for debug output of Slice and Json structures char* slice_to_debug_str(Arena *arena, Slice slice); char* json_to_debug_str(Arena *arena, Json json); #define DEBUGSTR(arena, type, value) DEBUGSTR_##type(arena, value) #define DEBUGSTR_Slice(arena, value) slice_to_debug_str(arena, value) #define DEBUGSTR_Json(arena, value) json_to_debug_str(arena, value) /** * Print all current logging rules to stderr for debugging */ void logger_print_rules(); /** * Dump all active logging rules into a string * * @param arena Memory arena to allocate the string in * @return String representation of all rules, or NULL on error */ char* logger_rules_to_string(Arena *arena); // --------------- // -- Templater -- // --------------- typedef enum { TEMPLATE_NODE_TEXT, // Plain text content TEMPLATE_NODE_INTERPOLATE, // Variable interpolation TEMPLATE_NODE_SECTION, // Section (for loops) TEMPLATE_NODE_INCLUDE, // Include other templates TEMPLATE_NODE_FUNCTION // Function call (for future use) } TemplateNodeType; #define TEMPLATE_MAX_PREFIX_LEN 16 typedef struct { const char *open_brace; // Default: "{%" const char *close_brace; // Default: "%}" const char *null_handler; // Default: "%%" const char *section_prefix; // default: "for " const char *section_suffix; // default: " in " const char *section_optional_suffix; // default: " join " const char *section_post_suffix; // default: " do " const char *interpolation_prefix; // default: "" const char *include_prefix; // default: "include " const char *function_prefix; // default: "call " } TemplateConfig; typedef struct { char *variable; char *collection; char *join; struct TemplateNode *null_block; } TemplateSectionValue; typedef struct { char *variable; struct TemplateNode *null_block; } TemplateInterpolateValue; typedef struct { char *name; char *args; } TemplateFunctionValue; typedef struct { char *name; } TemplateIncludeValue; typedef struct { char *content; } TemplateTextValue; typedef union { TemplateSectionValue section; TemplateInterpolateValue interpolate; TemplateFunctionValue function; TemplateIncludeValue include; TemplateTextValue text; } TemplateValue; // template node structure typedef struct TemplateNode { TemplateNodeType type; TemplateValue value; struct TemplateNode *children; // child nodes struct TemplateNode *next; // sibling nodes } TemplateNode; #endif // EPRINTF_H