#include "ecex.h" #include #include #include #include #include #include static int ecex_env_enabled(const char *name) { const char *v; if (!name) return 0; v = getenv(name); return v && v[0] && v[0] != '0'; } static int ecex_log_enabled(void) { return ecex_env_enabled("ECEX_LOG") || ecex_env_enabled("ECEX_TRACE_EVAL") || ecex_env_enabled("ECEX_TRACE_CALLBACKS") || ecex_env_enabled("ECEX_TRACE_TETRIS"); } static char ecex_last_log_line[1024]; static unsigned long ecex_last_log_count = 0; static int ecex_last_log_depth = 0; static int ecex_log_group_depth = 0; static int ecex_frame_group_open = 0; static int ecex_frame_group_depth = 0; static char ecex_frame_group_start[1024]; static char ecex_frame_repeat_start[1024]; static char ecex_frame_repeat_end[1024]; static unsigned long ecex_frame_repeat_count = 0; static size_t ecex_strn_copy(char *out, size_t out_cap, const char *in) { size_t i = 0; if (!out || out_cap == 0) return 0; if (!in) in = "(null)"; while (i + 1 < out_cap && in[i]) { out[i] = in[i]; ++i; } out[i] = '\0'; return i; } static int ecex_streq(const char *a, const char *b) { size_t i = 0; if (!a || !b) return 0; while (a[i] && b[i]) { if (a[i] != b[i]) return 0; ++i; } return a[i] == b[i]; } static int ecex_starts_with(const char *text, const char *prefix) { size_t i = 0; if (!text || !prefix) return 0; while (prefix[i]) { if (text[i] != prefix[i]) return 0; ++i; } return 1; } static size_t ecex_append_char(char *out, size_t len, size_t cap, char c) { if (len + 1 < cap) { out[len++] = c; out[len] = '\0'; } return len; } static size_t ecex_append_cstr(char *out, size_t len, size_t cap, const char *text) { size_t i = 0; if (!text) text = "(null)"; while (text[i] && len + 1 < cap) { out[len++] = text[i++]; } if (cap) out[len < cap ? len : cap - 1] = '\0'; return len; } static size_t ecex_append_ulong(char *out, size_t len, size_t cap, unsigned long value) { char tmp[32]; size_t n = 0; if (value == 0) return ecex_append_char(out, len, cap, '0'); while (value && n < sizeof(tmp)) { tmp[n++] = (char)('0' + (value % 10)); value /= 10; } while (n > 0) len = ecex_append_char(out, len, cap, tmp[--n]); return len; } static size_t ecex_append_int(char *out, size_t len, size_t cap, int value) { unsigned int mag; if (value < 0) { len = ecex_append_char(out, len, cap, '-'); mag = (unsigned int)(-(value + 1)) + 1u; } else { mag = (unsigned int)value; } return ecex_append_ulong(out, len, cap, (unsigned long)mag); } static size_t ecex_append_ptr(char *out, size_t len, size_t cap, const void *ptr) { uintptr_t value = (uintptr_t)ptr; char tmp[sizeof(uintptr_t) * 2]; size_t n = 0; len = ecex_append_cstr(out, len, cap, "0x"); if (value == 0) return ecex_append_char(out, len, cap, '0'); while (value && n < sizeof(tmp)) { int digit = (int)(value & 0xfu); tmp[n++] = (char)(digit < 10 ? '0' + digit : 'a' + digit - 10); value >>= 4; } while (n > 0) len = ecex_append_char(out, len, cap, tmp[--n]); return len; } static size_t ecex_append_double(char *out, size_t len, size_t cap, double value) { long whole; unsigned long frac; int i; if (value < 0.0) { len = ecex_append_char(out, len, cap, '-'); value = -value; } whole = (long)value; frac = (unsigned long)((value - (double)whole) * 1000000.0 + 0.5); len = ecex_append_ulong(out, len, cap, (unsigned long)whole); len = ecex_append_char(out, len, cap, '.'); for (i = 100000; i > 0; i /= 10) { len = ecex_append_char(out, len, cap, (char)('0' + (frac / (unsigned long)i) % 10)); } return len; } static void ecex_write_all(const char *text, size_t len) { while (len > 0) { ssize_t written = write(STDERR_FILENO, text, len); if (written <= 0) return; text += written; len -= (size_t)written; } } static void ecex_log_emit_raw_depth(const char *line, int depth) { size_t len = 0; int i; while (line && line[len]) ++len; ecex_write_all("ecex-log: ", 10); for (i = 0; i < depth; i++) { ecex_write_all(" ", 2); } ecex_write_all(line ? line : "(null)", line ? len : 6); ecex_write_all("\n", 1); } static void ecex_log_emit_raw_counted(const char *line, int depth, unsigned long count) { char counted[1200]; if (count > 1) { snprintf(counted, sizeof(counted), "%s [%lu]", line ? line : "(null)", count); ecex_log_emit_raw_depth(counted, depth); } else { ecex_log_emit_raw_depth(line, depth); } } static void ecex_log_flush_frame_repeats(void) { if (ecex_frame_repeat_count == 0) return; ecex_log_emit_raw_counted(ecex_frame_repeat_start, 0, ecex_frame_repeat_count); ecex_log_emit_raw_counted(ecex_frame_repeat_end, 0, ecex_frame_repeat_count); ecex_frame_repeat_count = 0; ecex_frame_repeat_start[0] = '\0'; ecex_frame_repeat_end[0] = '\0'; } static void ecex_log_flush_open_frame_group(void) { if (!ecex_frame_group_open) return; ecex_log_emit_raw_depth(ecex_frame_group_start, ecex_frame_group_depth); ecex_frame_group_open = 0; ecex_frame_group_start[0] = '\0'; ecex_frame_group_depth = 0; } static int ecex_log_is_frame_start(const char *line) { return ecex_starts_with(line, "frame start "); } static int ecex_log_is_frame_end(const char *line) { return ecex_streq(line, "frame end"); } static void ecex_log_flush_repeat(void) { char line[64]; size_t len = 0; if (ecex_last_log_count > 1) { len = ecex_append_cstr(line, len, sizeof(line), "... ["); len = ecex_append_ulong(line, len, sizeof(line), ecex_last_log_count); len = ecex_append_char(line, len, sizeof(line), ']'); ecex_log_emit_raw_depth(line, ecex_last_log_depth); } ecex_last_log_count = 0; ecex_last_log_line[0] = '\0'; ecex_last_log_depth = 0; } void ecex_log_flush(void) { ecex_log_flush_open_frame_group(); ecex_log_flush_frame_repeats(); ecex_log_flush_repeat(); } static void ecex_log_write_line_depth(const char *line, int depth) { char safe_line[1024]; if (depth < 0) depth = 0; ecex_strn_copy(safe_line, sizeof(safe_line), line); ecex_log_flush_open_frame_group(); ecex_log_flush_frame_repeats(); if (ecex_last_log_count > 0 && ecex_last_log_depth == depth && ecex_streq(ecex_last_log_line, safe_line)) { ecex_last_log_count++; return; } ecex_log_flush_repeat(); ecex_strn_copy(ecex_last_log_line, sizeof(ecex_last_log_line), safe_line); ecex_last_log_depth = depth; ecex_last_log_count = 1; ecex_log_emit_raw_depth(safe_line, depth); } static void ecex_log_write_line(const char *line) { ecex_log_write_line_depth(line, ecex_log_group_depth); } void ecex_log(const char *message) { if (!ecex_log_enabled()) return; ecex_log_write_line(message ? message : "(null)"); } void ecex_logf(const char *fmt, ...) { char line[1024]; va_list ap; if (!ecex_log_enabled()) return; if (!fmt) { ecex_log_write_line("(null)"); return; } va_start(ap, fmt); vsnprintf(line, sizeof(line), fmt, ap); va_end(ap); ecex_log_write_line(line); } void ecex_log_group_begin(const char *message) { if (!ecex_log_enabled()) return; if (ecex_log_group_depth == 0 && ecex_log_is_frame_start(message)) { ecex_log_flush_repeat(); if (ecex_frame_repeat_count > 0 && !ecex_streq(ecex_frame_repeat_start, message ? message : "group start")) { ecex_log_flush_frame_repeats(); } ecex_strn_copy(ecex_frame_group_start, sizeof(ecex_frame_group_start), message ? message : "group start"); ecex_frame_group_depth = ecex_log_group_depth; ecex_frame_group_open = 1; ecex_log_group_depth++; return; } ecex_log_flush_open_frame_group(); ecex_log_flush_frame_repeats(); ecex_log_flush_repeat(); ecex_log_write_line_depth(message ? message : "group start", ecex_log_group_depth); ecex_log_group_depth++; } void ecex_log_group_end(const char *message) { if (!ecex_log_enabled()) return; ecex_log_flush_repeat(); if (ecex_log_group_depth > 0) ecex_log_group_depth--; if (ecex_frame_group_open && ecex_log_group_depth == ecex_frame_group_depth && ecex_log_is_frame_end(message)) { const char *end = message ? message : "group end"; if (ecex_frame_repeat_count > 0 && ecex_streq(ecex_frame_repeat_start, ecex_frame_group_start) && ecex_streq(ecex_frame_repeat_end, end)) { ecex_frame_repeat_count++; } else { ecex_log_flush_frame_repeats(); ecex_strn_copy(ecex_frame_repeat_start, sizeof(ecex_frame_repeat_start), ecex_frame_group_start); ecex_strn_copy(ecex_frame_repeat_end, sizeof(ecex_frame_repeat_end), end); ecex_frame_repeat_count = 1; } ecex_frame_group_open = 0; ecex_frame_group_start[0] = '\0'; ecex_frame_group_depth = 0; return; } ecex_log_flush_open_frame_group(); ecex_log_flush_frame_repeats(); ecex_log_write_line_depth(message ? message : "group end", ecex_log_group_depth); } void ecex_log_int(const char *message, int value) { char line[1024]; size_t len = 0; if (!ecex_log_enabled()) return; len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); ecex_append_int(line, len, sizeof(line), value); ecex_log_write_line(line); } void ecex_log_double(const char *message, double value) { char line[1024]; size_t len = 0; if (!ecex_log_enabled()) return; len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); ecex_append_double(line, len, sizeof(line), value); ecex_log_write_line(line); } void ecex_log_ptr(const char *message, const void *ptr) { char line[1024]; size_t len = 0; if (!ecex_log_enabled()) return; len = ecex_append_cstr(line, len, sizeof(line), message ? message : ""); ecex_append_ptr(line, len, sizeof(line), ptr); ecex_log_write_line(line); }