(root)/
freetype-2.13.2/
src/
dlg/
dlg.c
       1  // Copyright (c) 2019 nyorain
       2  // Distributed under the Boost Software License, Version 1.0.
       3  // See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt
       4  
       5  #define _XOPEN_SOURCE 600
       6  #define _POSIX_C_SOURCE 200809L
       7  #define _WIN32_WINNT 0x0600
       8  
       9  // Needed on windows so that we can use sprintf without warning.
      10  #define _CRT_SECURE_NO_WARNINGS
      11  
      12  #include <dlg/output.h>
      13  #include <dlg/dlg.h>
      14  #include <wchar.h>
      15  #include <time.h>
      16  #include <stdio.h>
      17  #include <stdlib.h>
      18  #include <string.h>
      19  
      20  const char* const dlg_reset_sequence = "\033[0m";
      21  const struct dlg_style dlg_default_output_styles[] = {
      22  	{dlg_text_style_italic, dlg_color_green, dlg_color_none},
      23  	{dlg_text_style_dim, dlg_color_gray, dlg_color_none},
      24  	{dlg_text_style_none, dlg_color_cyan, dlg_color_none},
      25  	{dlg_text_style_none, dlg_color_yellow, dlg_color_none},
      26  	{dlg_text_style_none, dlg_color_red, dlg_color_none},
      27  	{dlg_text_style_bold, dlg_color_red, dlg_color_none}
      28  };
      29  
      30  static void* xalloc(size_t size) {
      31  	void* ret = calloc(size, 1);
      32  	if(!ret) fprintf(stderr, "dlg: calloc returned NULL, probably crashing (size: %zu)\n", size);
      33  	return ret;
      34  }
      35  
      36  static void* xrealloc(void* ptr, size_t size) {
      37  	void* ret = realloc(ptr, size);
      38  	if(!ret) fprintf(stderr, "dlg: realloc returned NULL, probably crashing (size: %zu)\n", size);
      39  	return ret;
      40  }
      41  
      42  struct dlg_tag_func_pair {
      43  	const char* tag;
      44  	const char* func;
      45  };
      46  
      47  struct dlg_data {
      48  	const char** tags; // vec
      49  	struct dlg_tag_func_pair* pairs; // vec
      50  	char* buffer;
      51  	size_t buffer_size;
      52  };
      53  
      54  static dlg_handler g_handler = dlg_default_output;
      55  static void* g_data = NULL;
      56  
      57  static void dlg_free_data(void* data);
      58  static struct dlg_data* dlg_create_data(void);
      59  
      60  // platform-specific
      61  #if defined(__unix__) || defined(__unix) || defined(__linux__) || defined(__APPLE__) || defined(__MACH__)
      62  	#define DLG_OS_UNIX
      63  	#include <unistd.h>
      64  	#include <pthread.h>
      65  	#include <sys/time.h>
      66  
      67  	static pthread_key_t dlg_data_key;
      68  
      69  	static void dlg_main_cleanup(void) {
      70  		void* data = pthread_getspecific(dlg_data_key);
      71  		if(data) {
      72  			dlg_free_data(data);
      73  			pthread_setspecific(dlg_data_key, NULL);
      74  		}
      75  	}
      76  
      77  	static void init_data_key(void) {
      78  		pthread_key_create(&dlg_data_key, dlg_free_data);
      79  		atexit(dlg_main_cleanup);
      80  	}
      81  
      82  	static struct dlg_data* dlg_data(void) {
      83  		static pthread_once_t key_once = PTHREAD_ONCE_INIT;
      84  		pthread_once(&key_once, init_data_key);
      85  
      86  		void* data = pthread_getspecific(dlg_data_key);
      87  		if(!data) {
      88  			data = dlg_create_data();
      89  			pthread_setspecific(dlg_data_key, data);
      90  		}
      91  
      92  		return (struct dlg_data*) data;
      93  	}
      94  
      95  	static void lock_file(FILE* file) {
      96  		flockfile(file);
      97  	}
      98  
      99  	static void unlock_file(FILE* file) {
     100  		funlockfile(file);
     101  	}
     102  
     103  	bool dlg_is_tty(FILE* stream) {
     104  		return isatty(fileno(stream));
     105  	}
     106  
     107  	static unsigned get_msecs(void) {
     108  		struct timeval tv;
     109  		gettimeofday(&tv, NULL);
     110  		return tv.tv_usec;
     111  	}
     112  
     113  // platform switch -- end unix
     114  #elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
     115  	#define DLG_OS_WIN
     116  	#define WIN32_LEAN_AND_MEAN
     117  	#define DEFINE_CONSOLEV2_PROPERTIES
     118  	#include <windows.h>
     119  	#include <io.h>
     120  
     121  	// thanks for nothing, microsoft
     122  	#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
     123  	#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
     124  	#endif
     125  
     126  	// the max buffer size we will convert on the stack
     127  	#define DLG_MAX_STACK_BUF_SIZE 1024
     128  
     129  	static void WINAPI dlg_fls_destructor(void* data) {
     130  		dlg_free_data(data);
     131  	}
     132  
     133  	// TODO: error handling
     134  	static BOOL CALLBACK dlg_init_fls(PINIT_ONCE io, void* param, void** lpContext) {
     135  		(void) io;
     136  		(void) param;
     137  		**((DWORD**) lpContext) = FlsAlloc(dlg_fls_destructor);
     138  		return true;
     139  	}
     140  
     141  	static struct dlg_data* dlg_data(void) {
     142  		static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT;
     143  		static DWORD fls = 0;
     144  		void* flsp = (void*) &fls;
     145  		InitOnceExecuteOnce(&init_once, dlg_init_fls, NULL, &flsp);
     146  		void* data = FlsGetValue(fls);
     147  		if(!data) {
     148  			data = dlg_create_data();
     149  			FlsSetValue(fls, data);
     150  		}
     151  
     152  		return (struct dlg_data*) data;
     153  	}
     154  
     155  	static void lock_file(FILE* file) {
     156  		_lock_file(file);
     157  	}
     158  
     159  	static void unlock_file(FILE* file) {
     160  		_unlock_file(file);
     161  	}
     162  
     163  	bool dlg_is_tty(FILE* stream) {
     164  		return _isatty(_fileno(stream));
     165  	}
     166  
     167  #ifdef DLG_WIN_CONSOLE
     168  	static bool init_ansi_console(void) {
     169  		HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
     170  		HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
     171  		if(out == INVALID_HANDLE_VALUE || err == INVALID_HANDLE_VALUE)
     172  			return false;
     173  
     174  		DWORD outMode, errMode;
     175  		if(!GetConsoleMode(out, &outMode) || !GetConsoleMode(err, &errMode))
     176  		   return false;
     177  
     178  		outMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
     179  		errMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
     180  		if(!SetConsoleMode(out, outMode) || !SetConsoleMode(out, errMode))
     181  			return false;
     182  
     183  		return true;
     184  	}
     185  
     186  	static bool win_write_heap(void* handle, int needed, const char* format, va_list args) {
     187  		char* buf1 = xalloc(3 * needed + 3 + (needed % 2));
     188  		wchar_t* buf2 = (wchar_t*) (buf1 + needed + 1 + (needed % 2));
     189  		vsnprintf(buf1, needed + 1, format, args);
     190  	    needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
     191  		bool ret = (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
     192  		free(buf1);
     193  		return ret;
     194  	}
     195  
     196  	static bool win_write_stack(void* handle, int needed, const char* format, va_list args) {
     197  		char buf1[DLG_MAX_STACK_BUF_SIZE];
     198  		wchar_t buf2[DLG_MAX_STACK_BUF_SIZE];
     199  		vsnprintf(buf1, needed + 1, format, args);
     200  	    needed = MultiByteToWideChar(CP_UTF8, 0, buf1, needed, buf2, needed + 1);
     201  		return (needed != 0 && WriteConsoleW(handle, buf2, needed, NULL, NULL) != 0);
     202  	}
     203  #endif // DLG_WIN_CONSOLE
     204  
     205  	static unsigned get_msecs() {
     206  		SYSTEMTIME st;
     207  		GetSystemTime(&st);
     208  		return st.wMilliseconds;
     209  	}
     210  
     211  #else // platform switch -- end windows
     212  	#error Cannot determine platform (needed for color and utf-8 and stuff)
     213  #endif
     214  
     215  // general
     216  void dlg_escape_sequence(struct dlg_style style, char buf[12]) {
     217  	int nums[3];
     218  	unsigned int count = 0;
     219  
     220  	if(style.fg != dlg_color_none) {
     221  		nums[count++] = style.fg + 30;
     222  	}
     223  
     224  	if(style.bg != dlg_color_none) {
     225  		nums[count++] = style.fg + 40;
     226  	}
     227  
     228  	if(style.style != dlg_text_style_none) {
     229  		nums[count++] = style.style;
     230  	}
     231  
     232  	switch(count) {
     233  		case 1: snprintf(buf, 12, "\033[%dm", nums[0]); break;
     234  		case 2: snprintf(buf, 12, "\033[%d;%dm", nums[0], nums[1]); break;
     235  		case 3: snprintf(buf, 12, "\033[%d;%d;%dm", nums[0], nums[1], nums[2]); break;
     236  		default: buf[0] = '\0'; break;
     237  	}
     238  }
     239  
     240  int dlg_vfprintf(FILE* stream, const char* format, va_list args) {
     241  #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
     242  	void* handle = NULL;
     243  	if(stream == stdout) {
     244  		handle = GetStdHandle(STD_OUTPUT_HANDLE);
     245  	} else if(stream == stderr) {
     246  		handle = GetStdHandle(STD_ERROR_HANDLE);
     247  	}
     248  
     249  	if(handle) {
     250  		va_list args_copy;
     251  		va_copy(args_copy, args);
     252  		int needed = vsnprintf(NULL, 0, format, args_copy);
     253  		va_end(args_copy);
     254  
     255  		if(needed < 0) {
     256  			return needed;
     257  		}
     258  
     259  		// We don't allocate too much on the stack
     260  		// but we also don't want to call alloc every logging call
     261  		// or use another cached buffer
     262  		if(needed >= DLG_MAX_STACK_BUF_SIZE) {
     263  			if(win_write_heap(handle, needed, format, args)) {
     264  				return needed;
     265  			}
     266  		} else {
     267  			if(win_write_stack(handle, needed, format, args)) {
     268  				return needed;
     269  			}
     270  		}
     271  	}
     272  #endif
     273  
     274  	return vfprintf(stream, format, args);
     275  }
     276  
     277  int dlg_fprintf(FILE* stream, const char* format, ...) {
     278  	va_list args;
     279  	va_start(args, format);
     280  	int ret = dlg_vfprintf(stream, format, args);
     281  	va_end(args);
     282  	return ret;
     283  }
     284  
     285  int dlg_styled_fprintf(FILE* stream, struct dlg_style style, const char* format, ...) {
     286  	char buf[12];
     287  	dlg_escape_sequence(style, buf);
     288  
     289  	fprintf(stream, "%s", buf);
     290  	va_list args;
     291  	va_start(args, format);
     292  	int ret = dlg_vfprintf(stream, format, args);
     293  	va_end(args);
     294  	fprintf(stream, "%s", dlg_reset_sequence);
     295  	return ret;
     296  }
     297  
     298  void dlg_generic_output(dlg_generic_output_handler output, void* data,
     299  		unsigned int features, const struct dlg_origin* origin, const char* string,
     300  		const struct dlg_style styles[6]) {
     301  	// We never print any dynamic content below so we can be sure at compile
     302  	// time that a buffer of size 64 is large enough.
     303  	char format_buf[64];
     304  	char* format = format_buf;
     305  
     306  	if(features & dlg_output_style) {
     307  		format += sprintf(format, "%%s");
     308  	}
     309  
     310  	if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
     311  		format += sprintf(format, "[");
     312  	}
     313  
     314  	bool first_meta = true;
     315  	if(features & dlg_output_time) {
     316  		format += sprintf(format, "%%h");
     317  		first_meta = false;
     318  	}
     319  
     320  	if(features & dlg_output_time_msecs) {
     321  		if(!first_meta) {
     322  			format += sprintf(format, ":");
     323  		}
     324  
     325  		format += sprintf(format, "%%m");
     326  		first_meta = false;
     327  	}
     328  
     329  	if(features & dlg_output_file_line) {
     330  		if(!first_meta) {
     331  			format += sprintf(format, " ");
     332  		}
     333  
     334  		format += sprintf(format, "%%o");
     335  		first_meta = false;
     336  	}
     337  
     338  	if(features & dlg_output_func) {
     339  		if(!first_meta) {
     340  			format += sprintf(format, " ");
     341  		}
     342  
     343  		format += sprintf(format, "%%f");
     344  		first_meta = false;
     345  	}
     346  
     347  	if(features & dlg_output_tags) {
     348  		if(!first_meta) {
     349  			format += sprintf(format, " ");
     350  		}
     351  
     352  		format += sprintf(format, "{%%t}");
     353  		first_meta = false;
     354  	}
     355  
     356  	if(features & (dlg_output_time | dlg_output_file_line | dlg_output_tags | dlg_output_func)) {
     357  		format += sprintf(format, "] ");
     358  	}
     359  
     360  	format += sprintf(format, "%%c");
     361  
     362  	if(features & dlg_output_newline) {
     363  		format += sprintf(format, "\n");
     364  	}
     365  
     366  	*format = '\0';
     367  	dlg_generic_outputf(output, data, format_buf, origin, string, styles);
     368  }
     369  
     370  void dlg_generic_outputf(dlg_generic_output_handler output, void* data,
     371  		const char* format_string, const struct dlg_origin* origin, const char* string,
     372  		const struct dlg_style styles[6]) {
     373  	bool reset_style = false;
     374  	for(const char* it = format_string; *it; it++) {
     375  		if(*it != '%') {
     376  			output(data, "%c", *it);
     377  			continue;
     378  		}
     379  
     380  		char next = *(it + 1); // must be valid since *it is not '\0'
     381  		if(next == 'h') {
     382  			time_t t = time(NULL);
     383  			struct tm tm_info;
     384  
     385  	#ifdef DLG_OS_WIN
     386  			if(localtime_s(&tm_info, &t)) {
     387  	#else
     388  			if(!localtime_r(&t, &tm_info)) {
     389  	#endif
     390  				output(data, "<DATE ERROR>");
     391  			} else {
     392  				char timebuf[32];
     393  				strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info);
     394  				output(data, "%s", timebuf);
     395  			}
     396  			it++;
     397  		} else if(next == 'm') {
     398  			output(data, "%06d", get_msecs());
     399  			it++;
     400  		} else if(next == 't') {
     401  			bool first_tag = true;
     402  			for(const char** tags = origin->tags; *tags; ++tags) {
     403  				if(!first_tag) {
     404  					output(data, ", ");
     405  				}
     406  
     407  				output(data, "%s", *tags);
     408  				first_tag = false;
     409  			}
     410  			++it;
     411  		} else if(next == 'f') {
     412  			output(data, "%s", origin->func);
     413  			++it;
     414  		} else if(next == 'o') {
     415  			output(data, "%s:%u", origin->file, origin->line);
     416  			++it;
     417  		} else if(next == 's') {
     418  			char buf[12];
     419  			dlg_escape_sequence(styles[origin->level], buf);
     420  			output(data, "%s", buf);
     421  			reset_style = true;
     422  			++it;
     423  		} else if(next == 'r') {
     424  			output(data, "%s", dlg_reset_sequence);
     425  			reset_style = false;
     426  			++it;
     427  		} else if(next == 'c') {
     428  			if(origin->expr && string) {
     429  				output(data, "assertion '%s' failed: '%s'", origin->expr, string);
     430  			} else if(origin->expr) {
     431  				output(data, "assertion '%s' failed", origin->expr);
     432  			} else if(string) {
     433  				output(data, "%s", string);
     434  			}
     435  			++it;
     436  		} else if(next == '%') {
     437  			output(data, "%s", "%");
     438  			++it;
     439  		} else {
     440  			// in this case it's a '%' without known format specifier following
     441  			output(data, "%s", "%");
     442  		}
     443  	}
     444  
     445  	if(reset_style) {
     446  		output(data, "%s", dlg_reset_sequence);
     447  	}
     448  }
     449  
     450  struct buf {
     451  	char* buf;
     452  	size_t* size;
     453  };
     454  
     455  static void print_size(void* size, const char* format, ...) {
     456  	va_list args;
     457  	va_start(args, format);
     458  
     459  	int ret = vsnprintf(NULL, 0, format, args);
     460  	va_end(args);
     461  
     462  	if(ret > 0) {
     463  		*((size_t*) size) += ret;
     464  	}
     465  }
     466  
     467  static void print_buf(void* dbuf, const char* format, ...) {
     468  	struct buf* buf = (struct buf*) dbuf;
     469  	va_list args;
     470  	va_start(args, format);
     471  
     472  	int printed = vsnprintf(buf->buf, *buf->size, format, args);
     473  	va_end(args);
     474  
     475  	if(printed > 0) {
     476  		*buf->size -= printed;
     477  		buf->buf += printed;
     478  	}
     479  }
     480  
     481  void dlg_generic_output_buf(char* buf, size_t* size, unsigned int features,
     482  		const struct dlg_origin* origin, const char* string,
     483  		const struct dlg_style styles[6]) {
     484  	if(buf) {
     485  		struct buf mbuf;
     486  		mbuf.buf = buf;
     487  		mbuf.size = size;
     488  		dlg_generic_output(print_buf, &mbuf, features, origin, string, styles);
     489  	} else {
     490  		*size = 0;
     491  		dlg_generic_output(print_size, size, features, origin, string, styles);
     492  	}
     493  }
     494  
     495  void dlg_generic_outputf_buf(char* buf, size_t* size, const char* format_string,
     496  		const struct dlg_origin* origin, const char* string,
     497  		const struct dlg_style styles[6]) {
     498  	if(buf) {
     499  		struct buf mbuf;
     500  		mbuf.buf = buf;
     501  		mbuf.size = size;
     502  		dlg_generic_outputf(print_buf, &mbuf, format_string, origin, string, styles);
     503  	} else {
     504  		*size = 0;
     505  		dlg_generic_outputf(print_size, size, format_string, origin, string, styles);
     506  	}
     507  }
     508  
     509  static void print_stream(void* stream, const char* format, ...) {
     510  	va_list args;
     511  	va_start(args, format);
     512  	dlg_vfprintf((FILE*) stream, format, args);
     513  	va_end(args);
     514  }
     515  
     516  void dlg_generic_output_stream(FILE* stream, unsigned int features,
     517  		const struct dlg_origin* origin, const char* string,
     518  		const struct dlg_style styles[6]) {
     519  	stream = stream ? stream : stdout;
     520  	if(features & dlg_output_threadsafe) {
     521  		lock_file(stream);
     522  	}
     523  
     524  	dlg_generic_output(print_stream, stream, features, origin, string, styles);
     525  	if(features & dlg_output_threadsafe) {
     526  		unlock_file(stream);
     527  	}
     528  }
     529  
     530  void dlg_generic_outputf_stream(FILE* stream, const char* format_string,
     531  		const struct dlg_origin* origin, const char* string,
     532  		const struct dlg_style styles[6], bool lock_stream) {
     533  	stream = stream ? stream : stdout;
     534  	if(lock_stream) {
     535  		lock_file(stream);
     536  	}
     537  
     538  	dlg_generic_outputf(print_stream, stream, format_string, origin, string, styles);
     539  	if(lock_stream) {
     540  		unlock_file(stream);
     541  	}
     542  }
     543  
     544  void dlg_default_output(const struct dlg_origin* origin, const char* string, void* data) {
     545  	FILE* stream = data ? (FILE*) data : stdout;
     546  	unsigned int features = dlg_output_file_line |
     547  		dlg_output_newline |
     548  		dlg_output_threadsafe;
     549  
     550  #ifdef DLG_DEFAULT_OUTPUT_ALWAYS_COLOR
     551  	dlg_win_init_ansi();
     552  	features |= dlg_output_style;
     553  #else
     554  	if(dlg_is_tty(stream) && dlg_win_init_ansi()) {
     555  		features |= dlg_output_style;
     556  	}
     557  #endif
     558  
     559  	dlg_generic_output_stream(stream, features, origin, string, dlg_default_output_styles);
     560  	fflush(stream);
     561  }
     562  
     563  bool dlg_win_init_ansi(void) {
     564  #if defined(DLG_OS_WIN) && defined(DLG_WIN_CONSOLE)
     565  	// TODO: use init once
     566  	static volatile LONG status = 0;
     567  	LONG res = InterlockedCompareExchange(&status, 1, 0);
     568  	if(res == 0) { // not initialized
     569  		InterlockedExchange(&status, 3 + init_ansi_console());
     570  	}
     571  
     572  	while(status == 1); // currently initialized in another thread, spinlock
     573  	return (status == 4);
     574  #else
     575  	return true;
     576  #endif
     577  }
     578  
     579  // small dynamic vec/array implementation
     580  // Since the macros vec_init and vec_add[c]/vec_push might
     581  // change the pointers value it must not be referenced somewhere else.
     582  #define vec__raw(vec) (((unsigned int*) vec) - 2)
     583  
     584  static void* vec_do_create(unsigned int typesize, unsigned int cap, unsigned int size) {
     585  	unsigned long a = (size > cap) ? size : cap;
     586  	void* ptr = xalloc(2 * sizeof(unsigned int) + a * typesize);
     587  	unsigned int* begin = (unsigned int*) ptr;
     588  	begin[0] = size * typesize;
     589  	begin[1] = a * typesize;
     590  	return begin + 2;
     591  }
     592  
     593  // NOTE: can be more efficient if we are allowed to reorder vector
     594  static void vec_do_erase(void* vec, unsigned int pos, unsigned int size) {
     595  	unsigned int* begin = vec__raw(vec);
     596  	begin[0] -= size;
     597  	char* buf = (char*) vec;
     598  	memcpy(buf + pos, buf + pos + size, size);
     599  }
     600  
     601  static void* vec_do_add(void** vec, unsigned int size) {
     602  	unsigned int* begin = vec__raw(*vec);
     603  	unsigned int needed = begin[0] + size;
     604  	if(needed >= begin[1]) {
     605  		void* ptr = xrealloc(begin, sizeof(unsigned int) * 2 + needed * 2);
     606  		begin = (unsigned int*) ptr;
     607  		begin[1] = needed * 2;
     608  		(*vec) = begin + 2;
     609  	}
     610  
     611  	void* ptr = ((char*) (*vec)) + begin[0];
     612  	begin[0] += size;
     613  	return ptr;
     614  }
     615  
     616  #define vec_create(type, size) (type*) vec_do_create(sizeof(type), size * 2, size)
     617  #define vec_create_reserve(type, size, capacity) (type*) vec_do_create(sizeof(type), capcity, size)
     618  #define vec_init(array, size) array = vec_do_create(sizeof(*array), size * 2, size)
     619  #define vec_init_reserve(array, size, capacity) *((void**) &array) = vec_do_create(sizeof(*array), capacity, size)
     620  #define vec_free(vec) (free((vec) ? vec__raw(vec) : NULL), vec = NULL)
     621  #define vec_erase_range(vec, pos, count) vec_do_erase(vec, pos * sizeof(*vec), count * sizeof(*vec))
     622  #define vec_erase(vec, pos) vec_do_erase(vec, pos * sizeof(*vec), sizeof(*vec))
     623  #define vec_size(vec) (vec__raw(vec)[0] / sizeof(*vec))
     624  #define vec_capacity(vec) (vec_raw(vec)[1] / sizeof(*vec))
     625  #define vec_add(vec) vec_do_add((void**) &vec, sizeof(*vec))
     626  #define vec_addc(vec, count) (vec_do_add((void**) &vec, sizeof(*vec) * count))
     627  #define vec_push(vec, value) (vec_do_add((void**) &vec, sizeof(*vec)), vec_last(vec) = (value))
     628  #define vec_pop(vec) (vec__raw(vec)[0] -= sizeof(*vec))
     629  #define vec_popc(vec, count) (vec__raw(vec)[0] -= sizeof(*vec) * count)
     630  #define vec_clear(vec) (vec__raw(vec)[0] = 0)
     631  #define vec_last(vec) (vec[vec_size(vec) - 1])
     632  
     633  static struct dlg_data* dlg_create_data(void) {
     634  	struct dlg_data* data = (struct dlg_data*) xalloc(sizeof(struct dlg_data));
     635  	vec_init_reserve(data->tags, 0, 20);
     636  	vec_init_reserve(data->pairs, 0, 20);
     637  	data->buffer_size = 100;
     638  	data->buffer = (char*) xalloc(data->buffer_size);
     639  	return data;
     640  }
     641  
     642  static void dlg_free_data(void* ddata) {
     643  	struct dlg_data* data = (struct dlg_data*) ddata;
     644  	if(data) {
     645  		vec_free(data->pairs);
     646  		vec_free(data->tags);
     647  		free(data->buffer);
     648  		free(data);
     649  	}
     650  }
     651  
     652  void dlg_add_tag(const char* tag, const char* func) {
     653  	struct dlg_data* data = dlg_data();
     654  	struct dlg_tag_func_pair* pair =
     655  		(struct dlg_tag_func_pair*) vec_add(data->pairs);
     656  	pair->tag = tag;
     657  	pair->func = func;
     658  }
     659  
     660  bool dlg_remove_tag(const char* tag, const char* func) {
     661  	struct dlg_data* data = dlg_data();
     662  	for(unsigned int i = 0; i < vec_size(data->pairs); ++i) {
     663  		if(data->pairs[i].func == func && data->pairs[i].tag == tag) {
     664  			vec_erase(data->pairs, i);
     665  			return true;
     666  		}
     667  	}
     668  
     669  	return false;
     670  }
     671  
     672  char** dlg_thread_buffer(size_t** size) {
     673  	struct dlg_data* data = dlg_data();
     674  	if(size) {
     675  		*size = &data->buffer_size;
     676  	}
     677  	return &data->buffer;
     678  }
     679  
     680  void dlg_set_handler(dlg_handler handler, void* data) {
     681  	g_handler = handler;
     682  	g_data = data;
     683  }
     684  
     685  dlg_handler dlg_get_handler(void** data) {
     686  	*data = g_data;
     687  	return g_handler;
     688  }
     689  
     690  const char* dlg__printf_format(const char* str, ...) {
     691  	va_list vlist;
     692  	va_start(vlist, str);
     693  
     694  	va_list vlistcopy;
     695  	va_copy(vlistcopy, vlist);
     696  	int needed = vsnprintf(NULL, 0, str, vlist);
     697  	if(needed < 0) {
     698  		printf("dlg__printf_format: invalid format given\n");
     699  		va_end(vlist);
     700  		va_end(vlistcopy);
     701  		return NULL;
     702  	}
     703  
     704  	va_end(vlist);
     705  
     706  	size_t* buf_size;
     707  	char** buf = dlg_thread_buffer(&buf_size);
     708  	if(*buf_size <= (unsigned int) needed) {
     709  		*buf_size = (needed + 1) * 2;
     710  		*buf = (char*) xrealloc(*buf, *buf_size);
     711  	}
     712  
     713  	vsnprintf(*buf, *buf_size, str, vlistcopy);
     714  	va_end(vlistcopy);
     715  
     716  	return *buf;
     717  }
     718  
     719  void dlg__do_log(enum dlg_level lvl, const char* const* tags, const char* file, int line,
     720  		const char* func, const char* string, const char* expr) {
     721  	struct dlg_data* data = dlg_data();
     722  	unsigned int tag_count = 0;
     723  
     724  	// push default tags
     725  	while(tags[tag_count]) {
     726  		vec_push(data->tags, tags[tag_count++]);
     727  	}
     728  
     729  	// push current global tags
     730  	for(size_t i = 0; i < vec_size(data->pairs); ++i) {
     731  		const struct dlg_tag_func_pair pair = data->pairs[i];
     732  		if(pair.func == NULL || !strcmp(pair.func, func)) {
     733  			vec_push(data->tags, pair.tag);
     734  		}
     735  	}
     736  
     737  	// push call-specific tags, skip first terminating NULL
     738  	++tag_count;
     739  	while(tags[tag_count]) {
     740  		vec_push(data->tags, tags[tag_count++]);
     741  	}
     742  
     743  	vec_push(data->tags, NULL); // terminating NULL
     744  	struct dlg_origin origin;
     745  	origin.level = lvl;
     746  	origin.file = file;
     747  	origin.line = line;
     748  	origin.func = func;
     749  	origin.expr = expr;
     750  	origin.tags = data->tags;
     751  
     752  	g_handler(&origin, string, g_data);
     753  	vec_clear(data->tags);
     754  }
     755  
     756  #ifdef _MSC_VER
     757  // shitty msvc compatbility
     758  // meson gives us sane paths (separated by '/') while on MSVC,
     759  // __FILE__ contains a '\\' separator.
     760  static bool path_same(char a, char b) {
     761  	return (a == b) ||
     762  		(a == '/' && b == '\\') ||
     763  		(a == '\\' && b == '/');
     764  }
     765  #else
     766  
     767  static inline bool path_same(char a, char b) {
     768  	return a == b;
     769  }
     770  
     771  #endif
     772  
     773  const char* dlg__strip_root_path(const char* file, const char* base) {
     774  	if(!file) {
     775  		return NULL;
     776  	}
     777  
     778  	const char* saved = file;
     779  	if(*file == '.') { // relative path detected
     780  		while(*(++file) == '.' || *file == '/' || *file == '\\');
     781  		if(*file == '\0') { // weird case: purely relative path without file
     782  			return saved;
     783  		}
     784  
     785  		return file;
     786  	}
     787  
     788  	// strip base from file if it is given
     789  	if(base) {
     790  		char fn = *file;
     791  		char bn = *base;
     792  		while(bn != '\0' && path_same(fn, bn)) {
     793  			fn = *(++file);
     794  			bn = *(++base);
     795  		}
     796  
     797  		if(fn == '\0' || bn != '\0') { // weird case: base isn't prefix of file
     798  			return saved;
     799  		}
     800  	}
     801  
     802  	return file;
     803  }