#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, bool expand); // FIXME(yukkop): ptr % 8 == 0 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_strdup_fmt__(const char *file, const char *func, int line, Arena *arena, const char *fmt, ...); 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__(const char *file, const char *func, int line, Arena *arena, void *ptr, size_t size, size_t new_size); char* arena_strncpy__(const char *file, const char *func, int line, Arena *arena, const char *start, size_t len); // 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, false) #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_strdup_fmt(arena, ...) \ arena_strdup_fmt__(__FILE__, __func__, __LINE__, arena, ##__VA_ARGS__) #define arena_repstr(arena, src, start, len, rep) \ arena_repstr__(__FILE__, __func__, __LINE__, arena, src, start, len, rep) #define arena_realloc(arena, ptr, size, new_size) \ arena_realloc__(__FILE__, __func__, __LINE__, arena, ptr, size, new_size) #define arena_strncpy(arena, src, len) \ arena_strncpy__(__FILE__, __func__, __LINE__, arena, src, len) 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; \ }) // ------------ // -- Debug -- // ------------ /* * Set of pointers to track visited objects * Used to detect cycles in debug strings */ typedef struct PtrSet { void **data; size_t size; size_t capacity; } PtrSet; PtrSet *ptrset_init(Arena *arena); bool debug_ptrset_contains__(PtrSet *set, void *ptr); void debug_ptrset_add__(const char *file, const char *func, int line, Arena *arena, PtrSet *set, void *ptr); #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); char *string_to_debug_str__(const char *file, const char *func, int line, Arena *arena, const char *name, const char *string); char *number_to_debug_str__(const char *file, const char *func, int line, Arena *arena, const char *name, int number); char *struct_to_debug_str__(const char *file, const char *func, int line, Arena *arena, const char *type, const char *name, void *ptr, int count, ...); #define STRING_TO_DEBUG_STR(arena, name, string) \ string_to_debug_str__(__FILE__, __func__, __LINE__, arena, name, string) #define NUMBER_TO_DEBUG_STR(arena, name, number) \ number_to_debug_str__(__FILE__, __func__, __LINE__, arena, name, number) #define STRUCT_TO_DEBUG_STR(arena, type, name, self, count, ...) \ struct_to_debug_str__(__FILE__, __func__, __LINE__, arena, #type, name, self, count, ##__VA_ARGS__) bool debug_ptrset_contains(PtrSet *set, void *ptr); #define debug_check_cycle__(file, func, line, arena, type, name, self, visited) __extension__ ({ \ if (debug_ptrset_contains__(visited, self)) \ return struct_to_debug_str__(file, func, line, arena, \ #type, name, self, 1, "cycle detected"); \ debug_ptrset_add__(file, func, line, arena, visited, self); \ }) #define DEBUG_CHECK_CYCLE(arena, type, name, self, visited) \ debug_check_cycle__(__FILE__, __func__, __LINE__, arena, type, name, self, visited) // ---------- // -- 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) */ Json *json_get_object_item__(const char* file, const char* func, int line, const Json * const object, const char * const key); #define json_get_object_item(object, key) json_get_object_item__(__FILE__, __func__, __LINE__, object, key) char* json_to_debug_str__(const char* file, const char* func, int line, Arena *arena, Json json); #define json_to_debug_str(arena, json) json_to_debug_str__(__FILE__, __func__, __LINE__, arena, json) char *json_to_pretty_str__(const char* file, const char* func, int line, Arena *arena, const Json * const item, int indent_level); #define json_to_pretty_str(arena, json) json_to_pretty_str__(__FILE__, __func__, __LINE__, arena, json, 0) // ----------- // -- 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; \ }) char* slice_to_debug_str__(const char* file, const char* func, int line, Arena *arena, Slice slice); #define slice_to_debug_str(arena, slice) slice_to_debug_str__(__FILE__, __func__, __LINE__, arena, slice) // --------------- // -- 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_EXECUTE, // Execute code } TemplateNodeType; #define TEMPLATE_MAX_PREFIX_LEN 16 typedef struct { struct { struct { const char *open; // Default: "{%" const char *close; // Default: "%}" } Braces; struct { const char *control; // default: "for " const char *source; // default: " in " const char *begin; // default: " do " } Section; struct { const char *invoke; // default: "" } Interpolate; struct { const char *invoke; // default: "include " } Include; struct { const char *invoke; // default: "exec " } Execute; const char *nesting; // default: "->" } Syntax; } TemplateConfig; typedef struct TemplateNode TemplateNode; // forward declaration typedef struct { char *iterator; char *collection; TemplateNode *body; } TemplateSectionValue; typedef struct { char *key; } TemplateInterpolateValue; typedef struct { char *code; } TemplateExecuteValue; typedef struct { char *key; } TemplateIncludeValue; typedef struct { char *content; } TemplateTextValue; typedef union { TemplateSectionValue section; TemplateInterpolateValue interpolate; TemplateExecuteValue execute; TemplateIncludeValue include; TemplateTextValue text; } TemplateValue; typedef enum { TEMPLATE_ERROR_NONE, TEMPLATE_ERROR_UNKNOWN_TAG, TEMPLATE_ERROR_NESTED_INTERPOLATION, TEMPLATE_ERROR_NESTED_SECTION_ITERATOR, TEMPLATE_ERROR_UNEXPECTED_SECTION_END, TEMPLATE_ERROR_NESTED_INCLUDE, TEMPLATE_ERROR_NESTED_EXECUTE, } TemplateErrorCode; typedef struct { TemplateErrorCode code; char *message; } TemplateError; struct TemplateNode { TemplateError error; TemplateNodeType type; TemplateValue value; TemplateNode *children; // child nodes TemplateNode *next; // sibling nodes }; typedef enum { TEMPLATE_RESULT_ERROR, TEMPLATE_RESULT_NODE, } TemplateResultType; typedef struct { TemplateResultType type; union { TemplateError error; TemplateNode node; } Result; } TemplateResult; TemplateResult *template_parse__(const char *file, const char *func, int line, Arena *arena, const char **s, const TemplateConfig *config); char *template_node_to_debug_str__(const char *file, const char *func, int line, Arena *arena, const TemplateNode *node, int depth); TemplateConfig template_default_config__(const char *file, const char *func, int line); #define template_parse(arena, s, config) template_parse__(__FILE__, __func__, __LINE__, arena, s, config) #define template_node_to_debug_str(arena, node) template_node_to_debug_str__(__FILE__, __func__, __LINE__, arena, node, 0) #define template_default_config() template_default_config__(__FILE__, __func__, __LINE__) #endif // EPRINTF_H