• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
xalloc(size_t size)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 
xrealloc(void * ptr,size_t size)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 
dlg_main_cleanup(void)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 
init_data_key(void)77 	static void init_data_key(void) {
78 		pthread_key_create(&dlg_data_key, dlg_free_data);
79 		atexit(dlg_main_cleanup);
80 	}
81 
dlg_data(void)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 
lock_file(FILE * file)95 	static void lock_file(FILE* file) {
96 		flockfile(file);
97 	}
98 
unlock_file(FILE * file)99 	static void unlock_file(FILE* file) {
100 		funlockfile(file);
101 	}
102 
dlg_is_tty(FILE * stream)103 	bool dlg_is_tty(FILE* stream) {
104 		return isatty(fileno(stream));
105 	}
106 
get_msecs(void)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 
dlg_fls_destructor(void * data)129 	static void WINAPI dlg_fls_destructor(void* data) {
130 		dlg_free_data(data);
131 	}
132 
133 	// TODO: error handling
dlg_init_fls(PINIT_ONCE io,void * param,void ** lpContext)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 
dlg_data(void)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 
lock_file(FILE * file)155 	static void lock_file(FILE* file) {
156 		_lock_file(file);
157 	}
158 
unlock_file(FILE * file)159 	static void unlock_file(FILE* file) {
160 		_unlock_file(file);
161 	}
162 
dlg_is_tty(FILE * stream)163 	bool dlg_is_tty(FILE* stream) {
164 		return _isatty(_fileno(stream));
165 	}
166 
167 #ifdef DLG_WIN_CONSOLE
init_ansi_console(void)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 
win_write_heap(void * handle,int needed,const char * format,va_list args)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 
win_write_stack(void * handle,int needed,const char * format,va_list args)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 
get_msecs()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
dlg_escape_sequence(struct dlg_style style,char buf[12])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 
dlg_vfprintf(FILE * stream,const char * format,va_list args)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 
dlg_fprintf(FILE * stream,const char * format,...)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 
dlg_styled_fprintf(FILE * stream,struct dlg_style style,const char * format,...)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 
dlg_generic_output(dlg_generic_output_handler output,void * data,unsigned int features,const struct dlg_origin * origin,const char * string,const struct dlg_style styles[6])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 
dlg_generic_outputf(dlg_generic_output_handler output,void * data,const char * format_string,const struct dlg_origin * origin,const char * string,const struct dlg_style styles[6])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 }
804