aboutsummaryrefslogtreecommitdiff
path: root/src/log.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/log.c')
-rw-r--r--src/log.c345
1 files changed, 345 insertions, 0 deletions
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..7dec534
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,345 @@
+#include "ecex.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+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);
+}